Merge branch 'dev' of https://gitlab.com/Shinobi-Systems/Shinobi into dev
commit
f621e85f34
|
|
@ -0,0 +1,4 @@
|
|||
conf.json
|
||||
dist
|
||||
models
|
||||
node_modules
|
||||
|
|
@ -0,0 +1,31 @@
|
|||
FROM node:16.13-buster-slim
|
||||
|
||||
RUN apt update -y
|
||||
RUN apt install wget curl net-tools -y
|
||||
|
||||
RUN mkdir -p /config
|
||||
RUN mkdir -p /home/Shinobi/plugins/deepstack-face
|
||||
WORKDIR /home/Shinobi/plugins/deepstack-face
|
||||
|
||||
COPY . /home/Shinobi/plugins/deepstack-face
|
||||
RUN wget https://gitlab.com/Shinobi-Systems/Shinobi/-/raw/dev/plugins/pluginBase.js -O /home/Shinobi/plugins/deepstack-face/pluginBase.js
|
||||
RUN wget https://gitlab.com/Shinobi-Systems/Shinobi/-/raw/dev/tools/modifyConfigurationForPlugin.js -O /home/Shinobi/plugins/deepstack-face/modifyConfigurationForPlugin.js
|
||||
RUN wget https://gitlab.com/Shinobi-Systems/Shinobi/-/raw/dev/plugins/pluginCheck.js -O /home/Shinobi/plugins/pluginCheck.js
|
||||
RUN ls /home/Shinobi/plugins/deepstack-face
|
||||
|
||||
RUN apt install -y sudo dos2unix
|
||||
|
||||
RUN npm install pm2 -g
|
||||
|
||||
RUN npm install --unsafe-perm
|
||||
|
||||
RUN dos2unix /home/Shinobi/plugins/deepstack-face/init.sh
|
||||
RUN dos2unix /home/Shinobi/plugins/deepstack-face/pm2.yml
|
||||
RUN chmod -f +x /home/Shinobi/plugins/deepstack-face/init.sh
|
||||
RUN chmod -f +x /home/Shinobi/plugins/deepstack-face/pm2.yml
|
||||
|
||||
EXPOSE 8082
|
||||
|
||||
ENTRYPOINT ["/home/Shinobi/plugins/deepstack-face/init.sh"]
|
||||
|
||||
CMD [ "pm2-docker", "/home/Shinobi/plugins/deepstack-face/pm2.yml" ]
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
#!/bin/bash
|
||||
DIR=$(dirname $0)
|
||||
echo "Removing existing Node.js modules..."
|
||||
rm -rf $DIR/node_modules
|
||||
|
||||
nonInteractiveFlag=false
|
||||
|
||||
if [ ! -e "$DIR/conf.json" ]; then
|
||||
dontCreateKeyFlag=false
|
||||
echo "Creating conf.json"
|
||||
sudo cp $DIR/conf.sample.json $DIR/conf.json
|
||||
else
|
||||
echo "conf.json already exists..."
|
||||
fi
|
||||
|
||||
if [ "$dontCreateKeyFlag" = false ]; then
|
||||
echo "Adding Random Plugin Key to Main Configuration"
|
||||
node $DIR/../../tools/modifyConfigurationForPlugin.js deepstack-face key=$(head -c 64 < /dev/urandom | sha256sum | awk '{print substr($1,1,60)}')
|
||||
fi
|
||||
|
|
@ -0,0 +1,70 @@
|
|||
# Shinobi Video plugin for DeepStack / CodeProject AI Face Recognition
|
||||
|
||||
### How to Install plugin on GPU
|
||||
|
||||
> [This document has been rewritten over on ShinobiHub Articles.](https://hub.shinobi.video/articles/view/PcBtEgGuWuEL529)
|
||||
|
||||
# Docker Installation
|
||||
> Install Shinobi Plugin with Docker
|
||||
|
||||
> Image is based on `node:12.22.1-buster-slim`.
|
||||
|
||||
1. Enter plugin directory. Default Shinobi installation location is `/home/Shinobi`.
|
||||
|
||||
```
|
||||
cd /home/Shinobi/plugins/deepstack-face
|
||||
```
|
||||
|
||||
2. Build Image.
|
||||
|
||||
```
|
||||
docker build --tag shinobi-deepstack-face-image:1.0 .
|
||||
```
|
||||
|
||||
3. Launch the plugin.
|
||||
|
||||
- `-e ADD_CONFIG='{"key":"123mypluginkey","host":"172.16.100.238","port":8080,"deepStack":{"host":"172.16.100.238","port":5000,"isSSL":false,"apiKey":"123"}}'` Adds any configuration parameters to the plugin's conf.json file.
|
||||
- `-p '8082:8082/tcp'` is an optional flag if you decide to run the plugin in host mode.
|
||||
|
||||
```
|
||||
docker run -d --name='shinobi-deepstack-face' -e ADD_CONFIG='{"key":"123mypluginkey","host":"172.16.100.238","port":8080,"deepStack":{"host":"172.16.100.238","port":5000,"isSSL":false,"apiKey":"123"}}' shinobi-deepstack-face-image:1.0
|
||||
```
|
||||
|
||||
** Logs **
|
||||
|
||||
```
|
||||
docker logs /shinobi-deepstack-face
|
||||
```
|
||||
|
||||
** Stop and Remove **
|
||||
|
||||
```
|
||||
docker stop /shinobi-deepstack-face && docker rm /shinobi-deepstack-face
|
||||
```
|
||||
|
||||
### Options (Environment Variables)
|
||||
|
||||
| Option | Description | Default |
|
||||
|------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|------------|
|
||||
| ADD_CONFIG | The plugin's name. | DeepStack-Face |
|
||||
|
||||
|
||||
# Additional Information
|
||||
|
||||
Docker - [Get docker](https://docs.docker.com/get-docker/)
|
||||
|
||||
DeepStack - [Getting started](https://docs.deepstack.cc/getting-started/index.html#setting-up-deepstack)
|
||||
|
||||
Run DeepStack CPU docker image:
|
||||
```
|
||||
sudo docker run -e VISION-FACE=True -e VISION-DETECTION=True -v localstorage:/datastore -p 80:5000 deepquestai/deepstack
|
||||
```
|
||||
|
||||
GPU [installation guide](https://docs.deepstack.cc/using-deepstack-with-nvidia-gpus/#step-1-install-docker)
|
||||
|
||||
#### More installation options
|
||||
[Windows (CPU / GPU support)](https://docs.deepstack.cc/windows/index.html)
|
||||
|
||||
[nVidia Jetson](https://docs.deepstack.cc/nvidia-jetson/index.html#using-deepstack-with-nvidia-jetson)
|
||||
|
||||
[Raspberry PI](https://docs.deepstack.cc/raspberry-pi/index.html#using-deepstack-on-raspberry-pi-alpha)
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
{
|
||||
"plug": "DeepStack-Face",
|
||||
"host": "localhost",
|
||||
"tfjsBuild": "cpu",
|
||||
"port": 8080,
|
||||
"hostPort": 58083,
|
||||
"key": "DeepStack-Face",
|
||||
"mode": "client",
|
||||
"type": "detector",
|
||||
"deepStack": {
|
||||
"host": "HOSTNAME OR IP",
|
||||
"port": 5000,
|
||||
"isSSL": false,
|
||||
"apiKey": "API KEY IF SET"
|
||||
},
|
||||
"persons": [
|
||||
"Array of Names images begin with"
|
||||
]
|
||||
}
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
#!/bin/sh
|
||||
cd /home/Shinobi/plugins/deepstack-object
|
||||
if [ ! -e "./conf.json" ]; then
|
||||
echo "Creating conf.json"
|
||||
sudo cp conf.sample.json conf.json
|
||||
else
|
||||
echo "conf.json already exists..."
|
||||
fi
|
||||
|
||||
if [ -n "$ADD_CONFIG" ]; then
|
||||
echo ""
|
||||
else
|
||||
ADD_CONFIG="{}"
|
||||
fi
|
||||
node ./modifyConfigurationForPlugin.js deepstack-face addToConfig=$ADD_CONFIG maxRetryConnection=100
|
||||
|
||||
# Execute Command
|
||||
echo "Starting $PLUGIN_NAME plugin for Shinobi ..."
|
||||
exec "$@"
|
||||
File diff suppressed because it is too large
Load Diff
|
|
@ -0,0 +1,26 @@
|
|||
{
|
||||
"name": "shinobi-deepstack-face",
|
||||
"author": "Elad Bar",
|
||||
"version": "1.0.0",
|
||||
"description": "Face Recognition plugin for DeepStack / CodeProject AI",
|
||||
"main": "shinobi-deepstack-face.js",
|
||||
"dependencies": {
|
||||
"request": "^2.88.0",
|
||||
"express": "^4.16.2",
|
||||
"moment": "^2.19.2",
|
||||
"socket.io": "^4.4.1",
|
||||
"socket.io-client": "^4.4.1"
|
||||
},
|
||||
"devDependencies": {},
|
||||
"bin": "shinobi-deepstack-face.js",
|
||||
"pkg": {
|
||||
"targets": [
|
||||
"node12"
|
||||
],
|
||||
"scripts": [
|
||||
"../pluginBase.js"
|
||||
],
|
||||
"assets": []
|
||||
},
|
||||
"disabled": true
|
||||
}
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
apps:
|
||||
- script : '/home/Shinobi/plugins/deepstack-face/shinobi-deepstack-face.js'
|
||||
name : 'shinobi-deepstack-face'
|
||||
kill_timeout : 5000
|
||||
|
|
@ -0,0 +1,412 @@
|
|||
//
|
||||
// Shinobi - DeepStack Face Recognition Plugin
|
||||
// Copyright (C) 2021 Elad Bar
|
||||
//
|
||||
// Base Init >>
|
||||
const { spawn } = require('child_process');
|
||||
const fs = require('fs');
|
||||
const request = require("request");
|
||||
const moment = require('moment');
|
||||
const config = require('./conf.json');
|
||||
|
||||
let s = null;
|
||||
|
||||
const {
|
||||
workerData
|
||||
} = require('worker_threads');
|
||||
|
||||
const isWorker = workerData && workerData.ok === true;
|
||||
const pluginBasePath = isWorker ? "pluginWorkerBase.js" : "pluginBase.js";
|
||||
|
||||
for(let i = 0; i < 2; i++) {
|
||||
try {
|
||||
s = require(`../${pluginBasePath}`)(__dirname, config);
|
||||
|
||||
} catch(err) {
|
||||
console.log(err);
|
||||
|
||||
s = null;
|
||||
}
|
||||
}
|
||||
|
||||
if(s === null) {
|
||||
console.log(config.plug, `Plugin start has failed. ${pluginBasePath} was not found.`);
|
||||
} else {
|
||||
if(!isWorker) {
|
||||
const {
|
||||
haltMessage,
|
||||
checkStartTime,
|
||||
setStartTime,
|
||||
} = require('../pluginCheck.js');
|
||||
|
||||
if(!checkStartTime()) {
|
||||
console.log(haltMessage, new Date());
|
||||
s.disconnectWebSocket();
|
||||
}
|
||||
|
||||
setStartTime();
|
||||
}
|
||||
}
|
||||
// Base Init />>
|
||||
|
||||
let detectorSettings = null;
|
||||
|
||||
const DETECTOR_TYPE_FACE = 'face';
|
||||
const DETECTOR_TYPE_OBJECT = 'object';
|
||||
|
||||
const FACE_UNKNOWN = 'unknown';
|
||||
const DEEPSTACK_API_KEY = 'api_key';
|
||||
|
||||
const DETECTOR_CONFIGUTATION = {
|
||||
face: {
|
||||
detectEndpoint: '/vision/face/recognize',
|
||||
startupEndpoint: '/vision/face/list',
|
||||
key: 'userid'
|
||||
},
|
||||
object: {
|
||||
detectEndpoint: '/vision/detection',
|
||||
key: 'label'
|
||||
}
|
||||
}
|
||||
|
||||
const PROTOCOLS = {
|
||||
true: "https",
|
||||
false: "http"
|
||||
};
|
||||
|
||||
const log = (logger, message) => {
|
||||
logger(`${moment().format()} [${config.plug}] ${message}`);
|
||||
}
|
||||
|
||||
const logError = (message) => {
|
||||
log(console.error, message);
|
||||
};
|
||||
|
||||
const logWarn = (message) => {
|
||||
log(console.warn, message);
|
||||
};
|
||||
|
||||
const logInfo = (message) => {
|
||||
log(console.info, message);
|
||||
};
|
||||
|
||||
const logDebug = (message) => {
|
||||
log(console.debug, message);
|
||||
};
|
||||
|
||||
const postMessage = (data) => {
|
||||
const message = JSON.stringify(data);
|
||||
|
||||
logInfo(message);
|
||||
};
|
||||
|
||||
const initialize = () => {
|
||||
const deepStackProtocol = PROTOCOLS[config.deepStack.isSSL];
|
||||
|
||||
baseUrl = `${deepStackProtocol}://${config.deepStack.host}:${config.deepStack.port}/v1`;
|
||||
|
||||
const detectionType = config.plug.split("-")[1].toLowerCase();
|
||||
const detectorConfig = DETECTOR_CONFIGUTATION[detectionType];
|
||||
const detectorConfigKeys = Object.keys(detectorConfig);
|
||||
|
||||
detectorSettings = {
|
||||
type: detectionType,
|
||||
active: false,
|
||||
baseUrl: baseUrl,
|
||||
apiKey: config.deepStack.apiKey
|
||||
};
|
||||
|
||||
if(detectionType === DETECTOR_TYPE_FACE) {
|
||||
detectorSettings["registeredPersons"] = config.persons === undefined ? [] : config.persons;
|
||||
}
|
||||
|
||||
detectorConfigKeys.forEach(k => detectorSettings[k] = detectorConfig[k]);
|
||||
|
||||
const testRequestData = getFormData(detectorSettings.detectEndpoint);
|
||||
|
||||
request.post(testRequestData, (err, res, body) => {
|
||||
try {
|
||||
if(err) {
|
||||
throw err;
|
||||
}
|
||||
|
||||
const response = JSON.parse(body);
|
||||
|
||||
if(response.error) {
|
||||
detectorSettings.active = !response.error.endsWith('endpoint not activated');
|
||||
} else {
|
||||
detectorSettings.active = response.success;
|
||||
}
|
||||
|
||||
const detectorSettingsKeys = Object.keys(detectorSettings);
|
||||
|
||||
const pluginMessageHeader = [];
|
||||
pluginMessageHeader.push(`${config.plug} loaded`);
|
||||
|
||||
const configMessage = detectorSettingsKeys.map(k => `${k}: ${detectorSettings[k]}`);
|
||||
|
||||
const fullPluginMessage = pluginMessageHeader.concat(configMessage);
|
||||
|
||||
const pluginMessage = fullPluginMessage.join(", ");
|
||||
|
||||
logInfo(pluginMessage);
|
||||
|
||||
if (detectorSettings.active) {
|
||||
s.detectObject = detectObject;
|
||||
|
||||
if(detectionType === DETECTOR_TYPE_FACE) {
|
||||
const requestData = getFormData(detectorSettings.startupEndpoint);
|
||||
const requestTime = getCurrentTimestamp();
|
||||
|
||||
request.post(requestData, (errStartup, resStartup, bodyStartup) => {
|
||||
if (!!resStartup) {
|
||||
resStartup.duration = getDuration(requestTime);
|
||||
}
|
||||
|
||||
onFaceListResult(errStartup, resStartup, bodyStartup);
|
||||
});
|
||||
}
|
||||
}
|
||||
} catch(ex) {
|
||||
logError(`Failed to initialize ${config.plug} plugin, Error: ${ex}`)
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const processImage = (imageB64, d, tx, frameLocation, callback) => {
|
||||
if(!detectorSettings.active) {
|
||||
return;
|
||||
}
|
||||
|
||||
try{
|
||||
const imageStream = fs.createReadStream(frameLocation);
|
||||
|
||||
const form = {
|
||||
image: imageStream,
|
||||
min_confidence: 0.7
|
||||
};
|
||||
|
||||
const requestData = getFormData(detectorSettings.detectEndpoint, form);
|
||||
|
||||
const requestTime = getCurrentTimestamp();
|
||||
|
||||
request.post(requestData, (err, res, body) => {
|
||||
if (!!res) {
|
||||
res.duration = getDuration(requestTime);
|
||||
}
|
||||
|
||||
onImageProcessed(d, tx, err, res, body, imageB64);
|
||||
|
||||
fs.unlinkSync(frameLocation);
|
||||
});
|
||||
}catch(ex){
|
||||
logError(`Failed to process image, Error: ${ex}`);
|
||||
|
||||
if(fs.existsSync(frameLocation)) {
|
||||
fs.unlinkSync(frameLocation);
|
||||
}
|
||||
}
|
||||
|
||||
callback();
|
||||
};
|
||||
|
||||
const detectObject = (frameBuffer, d, tx, frameLocation, callback) => {
|
||||
if(!detectorSettings.active) {
|
||||
return;
|
||||
}
|
||||
|
||||
const dirCreationOptions = {
|
||||
recursive: true
|
||||
};
|
||||
|
||||
d.dir = `${s.dir.streams}${d.ke}/${d.id}/`;
|
||||
|
||||
frameLocation = `${d.dir}${s.gid(5)}.jpg`;
|
||||
|
||||
if(!fs.existsSync(d.dir)) {
|
||||
fs.mkdirSync(d.dir, dirCreationOptions);
|
||||
}
|
||||
|
||||
fs.writeFile(frameLocation, frameBuffer, function(err) {
|
||||
if(err) {
|
||||
return s.systemLog(err);
|
||||
}
|
||||
|
||||
try {
|
||||
const imageB64 = frameBuffer.toString('base64');
|
||||
|
||||
processImage(imageB64, d, tx, frameLocation, callback);
|
||||
|
||||
} catch(ex) {
|
||||
logError(`Detector failed to parse frame, Error: ${ex}`);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const getCurrentTimestamp = () => {
|
||||
const currentTime = new Date();
|
||||
const currentTimestamp = currentTime.getTime();
|
||||
|
||||
return currentTimestamp
|
||||
};
|
||||
|
||||
const getDuration = (requestTime) => {
|
||||
const currentTime = new Date();
|
||||
const currentTimestamp = currentTime.getTime();
|
||||
|
||||
const duration = currentTimestamp - requestTime;
|
||||
|
||||
return duration;
|
||||
};
|
||||
|
||||
const onFaceListResult = (err, res, body) => {
|
||||
const duration = !!res ? res.duration : 0;
|
||||
|
||||
try {
|
||||
const response = JSON.parse(body);
|
||||
|
||||
const success = response.success;
|
||||
const facesArr = response.faces;
|
||||
const faceStr = facesArr.join(",");
|
||||
|
||||
if(success) {
|
||||
logInfo(`DeepStack loaded with the following faces: ${faceStr}, Response time: ${duration} ms`);
|
||||
} else {
|
||||
logWarn(`Failed to connect to DeepStack server, Error: ${err}, Response time: ${duration} ms`);
|
||||
}
|
||||
} catch(ex) {
|
||||
logError(`Error while connecting to DeepStack server, Error: ${ex} | ${err}, Response time: ${duration} ms`);
|
||||
}
|
||||
};
|
||||
|
||||
const onImageProcessed = (d, tx, err, res, body, imageStream) => {
|
||||
const duration = !!res ? res.duration : 0;
|
||||
|
||||
let objects = [];
|
||||
|
||||
try {
|
||||
if(err) {
|
||||
throw err;
|
||||
}
|
||||
|
||||
const response = JSON.parse(body);
|
||||
|
||||
const success = response.success;
|
||||
|
||||
if(success) {
|
||||
const predictions = response.predictions;
|
||||
|
||||
if(predictions !== null && predictions.length > 0) {
|
||||
objects = predictions.map(p => getDeepStackObject(p)).filter(p => !!p);
|
||||
|
||||
if(objects.length > 0) {
|
||||
const identified = objects.filter(p => p.tag !== FACE_UNKNOWN);
|
||||
const unknownCount = objects.length - identified.length;
|
||||
|
||||
if(unknownCount > 0) {
|
||||
logInfo(`${d.id} detected ${unknownCount} unknown ${detectorSettings.type}s, Response time: ${duration} ms`);
|
||||
}
|
||||
|
||||
if(identified.length > 0) {
|
||||
const detectedObjectsStrArr = [];
|
||||
|
||||
if (detectorSettings.type === DETECTOR_TYPE_FACE) {
|
||||
identified.forEach(f => detectedObjectsStrArr.push(`${f.tag} (${f.person}) [${(f.confidence * 100).toFixed(2)}]`));
|
||||
} else {
|
||||
identified.forEach(f => detectedObjectsStrArr.push(`${f.tag} [${(f.confidence * 100).toFixed(2)}]`));
|
||||
}
|
||||
|
||||
const detectedObjectsStr = detectedObjectsStrArr.join(", ");
|
||||
|
||||
logInfo(`${d.id} detected ${detectorSettings.type}s: ${detectedObjectsStr}, Response time: ${duration} ms`);
|
||||
}
|
||||
|
||||
const isObjectDetectionSeparate = d.mon.detector_pam === '1' && d.mon.detector_use_detect_object === '1';
|
||||
const width = parseFloat(isObjectDetectionSeparate && d.mon.detector_scale_y_object ? d.mon.detector_scale_y_object : d.mon.detector_scale_y);
|
||||
const height = parseFloat(isObjectDetectionSeparate && d.mon.detector_scale_x_object ? d.mon.detector_scale_x_object : d.mon.detector_scale_x);
|
||||
|
||||
const eventData = {
|
||||
f: 'trigger',
|
||||
id: d.id,
|
||||
ke: d.ke,
|
||||
details: {
|
||||
plug: config.plug,
|
||||
name: d.id,
|
||||
reason: detectorSettings.type,
|
||||
matrices: objects,
|
||||
imgHeight: width,
|
||||
imgWidth: height,
|
||||
time: duration
|
||||
},
|
||||
frame: imageStream
|
||||
};
|
||||
|
||||
tx(eventData);
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch(ex) {
|
||||
logError(`Error while processing image, Error: ${ex} | ${err},, Response time: ${duration} ms, Body: ${body}`);
|
||||
}
|
||||
|
||||
return objects
|
||||
};
|
||||
|
||||
const getFormData = (endpoint, additionalParameters) => {
|
||||
const formData = {};
|
||||
|
||||
if(detectorSettings.apiKey) {
|
||||
formData[DEEPSTACK_API_KEY] = detectorSettings.apiKey;
|
||||
}
|
||||
|
||||
if(additionalParameters !== undefined && additionalParameters !== null) {
|
||||
const keys = Object.keys(additionalParameters);
|
||||
|
||||
keys.forEach(k => formData[k] = additionalParameters[k]);
|
||||
}
|
||||
|
||||
const requestData = {
|
||||
url: `${detectorSettings.baseUrl}${endpoint}`,
|
||||
time: true,
|
||||
formData: formData
|
||||
};
|
||||
|
||||
return requestData;
|
||||
};
|
||||
|
||||
const getDeepStackObject = (prediction) => {
|
||||
if(prediction === undefined) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const tag = prediction[detectorSettings.key];
|
||||
|
||||
const confidence = prediction.confidence ?? 0;
|
||||
const y_min = prediction.y_min ?? 0;
|
||||
const x_min = prediction.x_min ?? 0;
|
||||
const y_max = prediction.y_max ?? 0;
|
||||
const x_max = prediction.x_max ?? 0;
|
||||
const width = x_max - x_min;
|
||||
const height = y_max - y_min;
|
||||
|
||||
const obj = {
|
||||
x: x_min,
|
||||
y: y_min,
|
||||
width: width,
|
||||
height: height,
|
||||
tag: tag,
|
||||
confidence: confidence
|
||||
};
|
||||
|
||||
if (detectorSettings.type === DETECTOR_TYPE_FACE) {
|
||||
const matchingPersons = detectorSettings.registeredPersons.filter(p => tag.startsWith(p))
|
||||
const person = matchingPersons.length > 0 ? matchingPersons[0] : null;
|
||||
|
||||
obj["person"] = person;
|
||||
}
|
||||
|
||||
|
||||
return obj;
|
||||
};
|
||||
|
||||
initialize();
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
FROM node:12.22.1-buster-slim
|
||||
FROM node:16.13-buster-slim
|
||||
|
||||
RUN apt update -y
|
||||
RUN apt install wget curl net-tools -y
|
||||
|
|
|
|||
|
|
@ -12,6 +12,5 @@
|
|||
"port": 5000,
|
||||
"isSSL": false,
|
||||
"apiKey": "api key as defined in DeepStack"
|
||||
},
|
||||
"enabled": false
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@
|
|||
"express": "^4.16.2",
|
||||
"moment": "^2.19.2",
|
||||
"socket.io": "^2.0.4",
|
||||
"socket.io-client": "^4.5.3"
|
||||
"socket.io-client": "^1.7.4"
|
||||
},
|
||||
"bin": {
|
||||
"shinobi-deepstack-object": "shinobi-deepstack-object.js"
|
||||
|
|
|
|||
|
|
@ -2,14 +2,14 @@
|
|||
"name": "shinobi-deepstack-object",
|
||||
"author": "Elad Bar",
|
||||
"version": "1.0.0",
|
||||
"description": "Object Detection plugin for DeepStack",
|
||||
"description": "Object Detection plugin for DeepStack / CodeProject AI",
|
||||
"main": "shinobi-deepstack-object.js",
|
||||
"dependencies": {
|
||||
"request": "^2.88.0",
|
||||
"express": "^4.16.2",
|
||||
"moment": "^2.19.2",
|
||||
"socket.io": "^4.4.1",
|
||||
"socket.io-client": "^4.5.3"
|
||||
"socket.io-client": "^4.4.1"
|
||||
},
|
||||
"devDependencies": {},
|
||||
"bin": "shinobi-deepstack-object.js",
|
||||
|
|
|
|||
|
|
@ -1,148 +1,412 @@
|
|||
//
|
||||
// Shinobi - Tensorflow Plugin
|
||||
// Copyright (C) 2016-2025 Elad Bar, Moe Alam
|
||||
// Shinobi - DeepStack Face Recognition Plugin
|
||||
// Copyright (C) 2021 Elad Bar
|
||||
//
|
||||
// Base Init >>
|
||||
const { spawn } = require('child_process');
|
||||
const fs = require('fs');
|
||||
const config = require('./conf.json')
|
||||
const request = require("request")
|
||||
var s
|
||||
const request = require("request");
|
||||
const moment = require('moment');
|
||||
const config = require('./conf.json');
|
||||
|
||||
let s = null;
|
||||
|
||||
const {
|
||||
workerData
|
||||
} = require('worker_threads');
|
||||
|
||||
if(workerData && workerData.ok === true){
|
||||
try{
|
||||
s = require('../pluginWorkerBase.js')(__dirname,config)
|
||||
}catch(err){
|
||||
console.log(err)
|
||||
try{
|
||||
s = require('./pluginWorkerBase.js')(__dirname,config)
|
||||
}catch(err){
|
||||
console.log(err)
|
||||
return console.log(config.plug,'WORKER : Plugin start has failed. pluginBase.js was not found.')
|
||||
}
|
||||
}
|
||||
}else{
|
||||
try{
|
||||
s = require('../pluginBase.js')(__dirname,config)
|
||||
}catch(err){
|
||||
console.log(err)
|
||||
try{
|
||||
s = require('./pluginBase.js')(__dirname,config)
|
||||
}catch(err){
|
||||
console.log(err)
|
||||
return console.log(config.plug,'Plugin start has failed. pluginBase.js was not found.')
|
||||
}
|
||||
}
|
||||
try{
|
||||
s = require('../pluginBase.js')(__dirname,config)
|
||||
}catch(err){
|
||||
console.log(err)
|
||||
try{
|
||||
const {
|
||||
haltMessage,
|
||||
checkStartTime,
|
||||
setStartTime,
|
||||
} = require('../pluginCheck.js')
|
||||
const isWorker = workerData && workerData.ok === true;
|
||||
const pluginBasePath = isWorker ? "pluginWorkerBase.js" : "pluginBase.js";
|
||||
|
||||
if(!checkStartTime()){
|
||||
console.log(haltMessage,new Date())
|
||||
s.disconnectWebSocket()
|
||||
return
|
||||
}
|
||||
setStartTime()
|
||||
}catch(err){
|
||||
console.log(`pluginCheck failed`)
|
||||
}
|
||||
}
|
||||
for(let i = 0; i < 2; i++) {
|
||||
try {
|
||||
s = require(`../${pluginBasePath}`)(__dirname, config);
|
||||
|
||||
} catch(err) {
|
||||
console.log(err);
|
||||
|
||||
s = null;
|
||||
}
|
||||
}
|
||||
|
||||
if(s === null) {
|
||||
console.log(config.plug, `Plugin start has failed. ${pluginBasePath} was not found.`);
|
||||
} else {
|
||||
if(!isWorker) {
|
||||
const {
|
||||
haltMessage,
|
||||
checkStartTime,
|
||||
setStartTime,
|
||||
} = require('../pluginCheck.js');
|
||||
|
||||
if(!checkStartTime()) {
|
||||
console.log(haltMessage, new Date());
|
||||
s.disconnectWebSocket();
|
||||
}
|
||||
|
||||
setStartTime();
|
||||
}
|
||||
}
|
||||
// Base Init />>
|
||||
|
||||
const deepStackHost = config.deepStack["host"]
|
||||
const deepStackPort = config.deepStack["port"]
|
||||
const deepStackIsSSL = config.deepStack["isSSL"]
|
||||
const deepStackApiKey = config.deepStack["apiKey"]
|
||||
const deepStackProtocol = deepStackIsSSL ? "https" : "http"
|
||||
let detectorSettings = null;
|
||||
|
||||
const baseUrl = `${deepStackProtocol}://${deepStackHost}:${deepStackPort}/v1`
|
||||
const DETECTOR_TYPE_FACE = 'face';
|
||||
const DETECTOR_TYPE_OBJECT = 'object';
|
||||
|
||||
function deepStackRequest(requestEndpoint,frameBuffer){
|
||||
const fullEndPointUrl = `${baseUrl}${requestEndpoint || `/vision/detection`}`
|
||||
return new Promise((resolve,reject) => {
|
||||
try{
|
||||
const form = {
|
||||
"image": {
|
||||
value: frameBuffer,
|
||||
options: {
|
||||
filename: 'frame.jpg'
|
||||
}
|
||||
}
|
||||
}
|
||||
if(deepStackApiKey) {
|
||||
form["api_key"] = deepStackApiKey
|
||||
}
|
||||
request.post({url:fullEndPointUrl, formData:form}, function(err,res,body){
|
||||
let predictions = []
|
||||
try{
|
||||
const response = JSON.parse(body || {predictions: []})
|
||||
predictions = response["predictions"] || []
|
||||
}catch(err){
|
||||
console.log(res)
|
||||
console.log(err)
|
||||
console.log(body)
|
||||
}
|
||||
resolve(predictions);
|
||||
})
|
||||
}catch(err){
|
||||
resolve([])
|
||||
console.log(err)
|
||||
}
|
||||
})
|
||||
const FACE_UNKNOWN = 'unknown';
|
||||
const DEEPSTACK_API_KEY = 'api_key';
|
||||
|
||||
const DETECTOR_CONFIGUTATION = {
|
||||
face: {
|
||||
detectEndpoint: '/vision/face/recognize',
|
||||
startupEndpoint: '/vision/face/list',
|
||||
key: 'userid'
|
||||
},
|
||||
object: {
|
||||
detectEndpoint: '/vision/detection',
|
||||
key: 'label'
|
||||
}
|
||||
}
|
||||
|
||||
s.detectObject = async function(frameBuffer,d,tx,frameLocation,callback){
|
||||
const timeStart = new Date()
|
||||
const predictions = await deepStackRequest(`/vision/detection`,frameBuffer)
|
||||
if(predictions.length > 0) {
|
||||
const mats = []
|
||||
predictions.forEach(function(v){
|
||||
const label = v["label"]
|
||||
const confidence = v["confidence"]
|
||||
const y_min = v["y_min"]
|
||||
const x_min = v["x_min"]
|
||||
const y_max = v["y_max"]
|
||||
const x_max = v["x_max"]
|
||||
const width = x_max - x_min
|
||||
const height = y_max - y_min
|
||||
mats.push({
|
||||
x: x_min,
|
||||
y: y_min,
|
||||
width: width,
|
||||
height: height,
|
||||
tag: label,
|
||||
confidence: confidence,
|
||||
})
|
||||
})
|
||||
const isObjectDetectionSeparate = d.mon.detector_pam === '1' && d.mon.detector_use_detect_object === '1'
|
||||
const width = parseFloat(isObjectDetectionSeparate && d.mon.detector_scale_y_object ? d.mon.detector_scale_y_object : d.mon.detector_scale_y)
|
||||
const height = parseFloat(isObjectDetectionSeparate && d.mon.detector_scale_x_object ? d.mon.detector_scale_x_object : d.mon.detector_scale_x)
|
||||
const PROTOCOLS = {
|
||||
true: "https",
|
||||
false: "http"
|
||||
};
|
||||
|
||||
tx({
|
||||
f:'trigger',
|
||||
id:d.id,
|
||||
ke:d.ke,
|
||||
details:{
|
||||
plug: config.plug,
|
||||
name: `DeepStack-Object`,
|
||||
reason: 'object',
|
||||
matrices: mats,
|
||||
imgHeight: width,
|
||||
imgWidth: height,
|
||||
},
|
||||
frame: frameBuffer
|
||||
})
|
||||
const log = (logger, message) => {
|
||||
logger(`${moment().format()} [${config.plug}] ${message}`);
|
||||
}
|
||||
|
||||
const logError = (message) => {
|
||||
log(console.error, message);
|
||||
};
|
||||
|
||||
const logWarn = (message) => {
|
||||
log(console.warn, message);
|
||||
};
|
||||
|
||||
const logInfo = (message) => {
|
||||
log(console.info, message);
|
||||
};
|
||||
|
||||
const logDebug = (message) => {
|
||||
log(console.debug, message);
|
||||
};
|
||||
|
||||
const postMessage = (data) => {
|
||||
const message = JSON.stringify(data);
|
||||
|
||||
logInfo(message);
|
||||
};
|
||||
|
||||
const initialize = () => {
|
||||
const deepStackProtocol = PROTOCOLS[config.deepStack.isSSL];
|
||||
|
||||
baseUrl = `${deepStackProtocol}://${config.deepStack.host}:${config.deepStack.port}/v1`;
|
||||
|
||||
const detectionType = config.plug.split("-")[1].toLowerCase();
|
||||
const detectorConfig = DETECTOR_CONFIGUTATION[detectionType];
|
||||
const detectorConfigKeys = Object.keys(detectorConfig);
|
||||
|
||||
detectorSettings = {
|
||||
type: detectionType,
|
||||
active: false,
|
||||
baseUrl: baseUrl,
|
||||
apiKey: config.deepStack.apiKey
|
||||
};
|
||||
|
||||
if(detectionType === DETECTOR_TYPE_FACE) {
|
||||
detectorSettings["registeredPersons"] = config.persons === undefined ? [] : config.persons;
|
||||
}
|
||||
|
||||
detectorConfigKeys.forEach(k => detectorSettings[k] = detectorConfig[k]);
|
||||
|
||||
const testRequestData = getFormData(detectorSettings.detectEndpoint);
|
||||
|
||||
request.post(testRequestData, (err, res, body) => {
|
||||
try {
|
||||
if(err) {
|
||||
throw err;
|
||||
}
|
||||
|
||||
const response = JSON.parse(body);
|
||||
|
||||
if(response.error) {
|
||||
detectorSettings.active = !response.error.endsWith('endpoint not activated');
|
||||
} else {
|
||||
detectorSettings.active = response.success;
|
||||
}
|
||||
|
||||
const detectorSettingsKeys = Object.keys(detectorSettings);
|
||||
|
||||
const pluginMessageHeader = [];
|
||||
pluginMessageHeader.push(`${config.plug} loaded`);
|
||||
|
||||
const configMessage = detectorSettingsKeys.map(k => `${k}: ${detectorSettings[k]}`);
|
||||
|
||||
const fullPluginMessage = pluginMessageHeader.concat(configMessage);
|
||||
|
||||
const pluginMessage = fullPluginMessage.join(", ");
|
||||
|
||||
logInfo(pluginMessage);
|
||||
|
||||
if (detectorSettings.active) {
|
||||
s.detectObject = detectObject;
|
||||
|
||||
if(detectionType === DETECTOR_TYPE_FACE) {
|
||||
const requestData = getFormData(detectorSettings.startupEndpoint);
|
||||
const requestTime = getCurrentTimestamp();
|
||||
|
||||
request.post(requestData, (errStartup, resStartup, bodyStartup) => {
|
||||
if (!!resStartup) {
|
||||
resStartup.duration = getDuration(requestTime);
|
||||
}
|
||||
|
||||
onFaceListResult(errStartup, resStartup, bodyStartup);
|
||||
});
|
||||
}
|
||||
}
|
||||
} catch(ex) {
|
||||
logError(`Failed to initialize ${config.plug} plugin, Error: ${ex}`)
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const processImage = (imageB64, d, tx, frameLocation, callback) => {
|
||||
if(!detectorSettings.active) {
|
||||
return;
|
||||
}
|
||||
|
||||
try{
|
||||
const imageStream = fs.createReadStream(frameLocation);
|
||||
|
||||
const form = {
|
||||
image: imageStream,
|
||||
min_confidence: 0.7
|
||||
};
|
||||
|
||||
const requestData = getFormData(detectorSettings.detectEndpoint, form);
|
||||
|
||||
const requestTime = getCurrentTimestamp();
|
||||
|
||||
request.post(requestData, (err, res, body) => {
|
||||
if (!!res) {
|
||||
res.duration = getDuration(requestTime);
|
||||
}
|
||||
|
||||
onImageProcessed(d, tx, err, res, body, imageB64);
|
||||
|
||||
fs.unlinkSync(frameLocation);
|
||||
});
|
||||
}catch(ex){
|
||||
logError(`Failed to process image, Error: ${ex}`);
|
||||
|
||||
if(fs.existsSync(frameLocation)) {
|
||||
fs.unlinkSync(frameLocation);
|
||||
}
|
||||
}
|
||||
callback()
|
||||
}
|
||||
|
||||
callback();
|
||||
};
|
||||
|
||||
const detectObject = (frameBuffer, d, tx, frameLocation, callback) => {
|
||||
if(!detectorSettings.active) {
|
||||
return;
|
||||
}
|
||||
|
||||
const dirCreationOptions = {
|
||||
recursive: true
|
||||
};
|
||||
|
||||
d.dir = `${s.dir.streams}${d.ke}/${d.id}/`;
|
||||
|
||||
frameLocation = `${d.dir}${s.gid(5)}.jpg`;
|
||||
|
||||
if(!fs.existsSync(d.dir)) {
|
||||
fs.mkdirSync(d.dir, dirCreationOptions);
|
||||
}
|
||||
|
||||
fs.writeFile(frameLocation, frameBuffer, function(err) {
|
||||
if(err) {
|
||||
return s.systemLog(err);
|
||||
}
|
||||
|
||||
try {
|
||||
const imageB64 = frameBuffer.toString('base64');
|
||||
|
||||
processImage(imageB64, d, tx, frameLocation, callback);
|
||||
|
||||
} catch(ex) {
|
||||
logError(`Detector failed to parse frame, Error: ${ex}`);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const getCurrentTimestamp = () => {
|
||||
const currentTime = new Date();
|
||||
const currentTimestamp = currentTime.getTime();
|
||||
|
||||
return currentTimestamp
|
||||
};
|
||||
|
||||
const getDuration = (requestTime) => {
|
||||
const currentTime = new Date();
|
||||
const currentTimestamp = currentTime.getTime();
|
||||
|
||||
const duration = currentTimestamp - requestTime;
|
||||
|
||||
return duration;
|
||||
};
|
||||
|
||||
const onFaceListResult = (err, res, body) => {
|
||||
const duration = !!res ? res.duration : 0;
|
||||
|
||||
try {
|
||||
const response = JSON.parse(body);
|
||||
|
||||
const success = response.success;
|
||||
const facesArr = response.faces;
|
||||
const faceStr = facesArr.join(",");
|
||||
|
||||
if(success) {
|
||||
logInfo(`DeepStack loaded with the following faces: ${faceStr}, Response time: ${duration} ms`);
|
||||
} else {
|
||||
logWarn(`Failed to connect to DeepStack server, Error: ${err}, Response time: ${duration} ms`);
|
||||
}
|
||||
} catch(ex) {
|
||||
logError(`Error while connecting to DeepStack server, Error: ${ex} | ${err}, Response time: ${duration} ms`);
|
||||
}
|
||||
};
|
||||
|
||||
const onImageProcessed = (d, tx, err, res, body, imageStream) => {
|
||||
const duration = !!res ? res.duration : 0;
|
||||
|
||||
let objects = [];
|
||||
|
||||
try {
|
||||
if(err) {
|
||||
throw err;
|
||||
}
|
||||
|
||||
const response = JSON.parse(body);
|
||||
|
||||
const success = response.success;
|
||||
|
||||
if(success) {
|
||||
const predictions = response.predictions;
|
||||
|
||||
if(predictions !== null && predictions.length > 0) {
|
||||
objects = predictions.map(p => getDeepStackObject(p)).filter(p => !!p);
|
||||
|
||||
if(objects.length > 0) {
|
||||
const identified = objects.filter(p => p.tag !== FACE_UNKNOWN);
|
||||
const unknownCount = objects.length - identified.length;
|
||||
|
||||
if(unknownCount > 0) {
|
||||
logInfo(`${d.id} detected ${unknownCount} unknown ${detectorSettings.type}s, Response time: ${duration} ms`);
|
||||
}
|
||||
|
||||
if(identified.length > 0) {
|
||||
const detectedObjectsStrArr = [];
|
||||
|
||||
if (detectorSettings.type === DETECTOR_TYPE_FACE) {
|
||||
identified.forEach(f => detectedObjectsStrArr.push(`${f.tag} (${f.person}) [${(f.confidence * 100).toFixed(2)}]`));
|
||||
} else {
|
||||
identified.forEach(f => detectedObjectsStrArr.push(`${f.tag} [${(f.confidence * 100).toFixed(2)}]`));
|
||||
}
|
||||
|
||||
const detectedObjectsStr = detectedObjectsStrArr.join(", ");
|
||||
|
||||
logInfo(`${d.id} detected ${detectorSettings.type}s: ${detectedObjectsStr}, Response time: ${duration} ms`);
|
||||
}
|
||||
|
||||
const isObjectDetectionSeparate = d.mon.detector_pam === '1' && d.mon.detector_use_detect_object === '1';
|
||||
const width = parseFloat(isObjectDetectionSeparate && d.mon.detector_scale_y_object ? d.mon.detector_scale_y_object : d.mon.detector_scale_y);
|
||||
const height = parseFloat(isObjectDetectionSeparate && d.mon.detector_scale_x_object ? d.mon.detector_scale_x_object : d.mon.detector_scale_x);
|
||||
|
||||
const eventData = {
|
||||
f: 'trigger',
|
||||
id: d.id,
|
||||
ke: d.ke,
|
||||
details: {
|
||||
plug: config.plug,
|
||||
name: d.id,
|
||||
reason: detectorSettings.type,
|
||||
matrices: objects,
|
||||
imgHeight: width,
|
||||
imgWidth: height,
|
||||
time: duration
|
||||
},
|
||||
frame: imageStream
|
||||
};
|
||||
|
||||
tx(eventData);
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch(ex) {
|
||||
logError(`Error while processing image, Error: ${ex} | ${err},, Response time: ${duration} ms, Body: ${body}`);
|
||||
}
|
||||
|
||||
return objects
|
||||
};
|
||||
|
||||
const getFormData = (endpoint, additionalParameters) => {
|
||||
const formData = {};
|
||||
|
||||
if(detectorSettings.apiKey) {
|
||||
formData[DEEPSTACK_API_KEY] = detectorSettings.apiKey;
|
||||
}
|
||||
|
||||
if(additionalParameters !== undefined && additionalParameters !== null) {
|
||||
const keys = Object.keys(additionalParameters);
|
||||
|
||||
keys.forEach(k => formData[k] = additionalParameters[k]);
|
||||
}
|
||||
|
||||
const requestData = {
|
||||
url: `${detectorSettings.baseUrl}${endpoint}`,
|
||||
time: true,
|
||||
formData: formData
|
||||
};
|
||||
|
||||
return requestData;
|
||||
};
|
||||
|
||||
const getDeepStackObject = (prediction) => {
|
||||
if(prediction === undefined) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const tag = prediction[detectorSettings.key];
|
||||
|
||||
const confidence = prediction.confidence ?? 0;
|
||||
const y_min = prediction.y_min ?? 0;
|
||||
const x_min = prediction.x_min ?? 0;
|
||||
const y_max = prediction.y_max ?? 0;
|
||||
const x_max = prediction.x_max ?? 0;
|
||||
const width = x_max - x_min;
|
||||
const height = y_max - y_min;
|
||||
|
||||
const obj = {
|
||||
x: x_min,
|
||||
y: y_min,
|
||||
width: width,
|
||||
height: height,
|
||||
tag: tag,
|
||||
confidence: confidence
|
||||
};
|
||||
|
||||
if (detectorSettings.type === DETECTOR_TYPE_FACE) {
|
||||
const matchingPersons = detectorSettings.registeredPersons.filter(p => tag.startsWith(p))
|
||||
const person = matchingPersons.length > 0 ? matchingPersons[0] : null;
|
||||
|
||||
obj["person"] = person;
|
||||
}
|
||||
|
||||
|
||||
return obj;
|
||||
};
|
||||
|
||||
initialize();
|
||||
|
|
|
|||
Loading…
Reference in New Issue