1
0
Fork 0
mirror of https://gitlab.com/Shinobi-Systems/ShinobiCE.git synced 2025-03-09 15:40:15 +00:00

Solar Plexus

- New Plugin structure with pluginBase.js
- New plugins : Object Detection with YoloV3 and Face Detection with Dlib
- Fix 2-Factor Authentication
This commit is contained in:
Moe 2018-11-17 10:45:40 -08:00
parent e0f7c135af
commit 24de55e45a
22 changed files with 1268 additions and 899 deletions

50
plugins/dlib/INSTALL.sh Normal file
View file

@ -0,0 +1,50 @@
#!/bin/bash
THE_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null && pwd )"
sudo apt update -y
sudo apt-get install libx11-dev -y
sudo apt-get install libpng-dev -y
sudo apt-get install libopenblas-dev -y
echo "----------------------------------------"
echo "-- Installing Dlib Plugin for Shinobi --"
echo "----------------------------------------"
if ! [ -x "$(command -v nvidia-smi)" ]; then
echo "You need to install NVIDIA Drivers to use this."
echo "inside the Shinobi directory run the following :"
echo "sh INSTALL/cuda.sh"
exit 1
else
echo "NVIDIA Drivers found..."
echo "$(nvidia-smi |grep 'Driver Version')"
fi
echo "-----------------------------------"
if [ ! -d "/usr/local/cuda" ]; then
echo "You need to install CUDA Toolkit to use this."
echo "inside the Shinobi directory run the following :"
echo "sh INSTALL/cuda.sh"
exit 1
else
echo "CUDA Toolkit found..."
fi
echo "-----------------------------------"
if [ ! -e "./conf.json" ]; then
echo "Creating conf.json"
sudo cp conf.sample.json conf.json
else
echo "conf.json already exists..."
fi
npm i npm -g
echo "-----------------------------------"
echo "Getting node-gyp to build C++ modules"
npm install node-gyp -g --unsafe-perm
echo "-----------------------------------"
echo "Getting C++ module : face-recognition"
echo "https://gitlab.com/Shinobi-Systems/face-recognition-js-cuda"
npm install --unsafe-perm
npm audit fix --force
cd $THE_DIR
echo "-----------------------------------"
echo "Start the plugin with pm2 like so :"
echo "pm2 start shinobi-dlib.js"
echo "-----------------------------------"
echo "Start the plugin without pm2 :"
echo "node shinobi-dlib.js"

71
plugins/dlib/README.md Normal file
View file

@ -0,0 +1,71 @@
#Dlib Plugin for Shinobi
**Ubuntu and CentOS only**
Go to the Shinobi directory. **/home/Shinobi** is the default directory.
```
cd /home/Shinobi/plugins/dlib
```
Copy the config file.
```
sh INSTALL.sh
```
Start the plugin.
```
pm2 start shinobi-dlib.js
```
Doing this will reveal options in the monitor configuration. Shinobi does not need to be restarted when a plugin is initiated or stopped.
## Run the plugin as a Host
> The main app (Shinobi) will be the client and the plugin will be the host. The purpose of allowing this method is so that you can use one plugin for multiple Shinobi instances. Allowing you to easily manage connections without starting multiple processes.
Edit your plugins configuration file. Set the `hostPort` **to be different** than the `listening port for camera.js`.
```
nano conf.json
```
Here is a sample of a Host configuration for the plugin.
- `plug` is the name of the plugin corresponding in the main configuration file.
- `https` choose if you want to use SSL or not. Default is `false`.
- `hostPort` can be any available port number. **Don't make this the same port number as Shinobi.** Default is `8082`.
- `type` tells the main application (Shinobi) what kind of plugin it is. In this case it is a detector.
```
{
"plug":"Dlib",
"hostPort":8082,
"key":"Dlib123123",
"mode":"host",
"type":"detector",
"conectionType":"websocket"
}
```
Now modify the **main configuration file** located in the main directory of Shinobi.
```
nano conf.json
```
Add the `plugins` array if you don't already have it. Add the following *object inside the array*.
```
"plugins":[
{
"id" : "Dlib",
"https" : false,
"host" : "localhost",
"port" : 8082,
"key" : "Dlib123123",
"mode" : "host",
"type" : "detector"
}
],
```

View file

@ -0,0 +1,9 @@
{
"plug":"Dlib",
"host":"localhost",
"port":8080,
"key":"Dlib123123",
"mode":"client",
"type":"detector",
"connectionType":"websocket"
}

19
plugins/dlib/package.json Normal file
View file

@ -0,0 +1,19 @@
{
"name": "shinobi-dlib",
"version": "1.0.0",
"description": "Dlib plugin for Shinobi that uses C++ functions for detection.",
"main": "shinobi-dlib.js",
"dependencies": {
"socket.io-client": "^1.7.4",
"express": "^4.16.2",
"moment": "^2.19.2",
"socket.io": "^2.0.4",
"face-recognition-cuda": "0.9.3"
},
"devDependencies": {},
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "Moe Alam",
"license": "ISC"
}

View file

@ -0,0 +1,97 @@
//
// Shinobi - Dlib Plugin
// Copyright (C) 2016-2025 Moe Alam, moeiscool
//
// # Donate
//
// If you like what I am doing here and want me to continue please consider donating :)
// PayPal : paypal@m03.ca
//
// Base Init >>
var fs = require('fs');
var config = require('./conf.json')
var s
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.')
}
}
// Base Init />>
var fr = require('face-recognition-cuda');//modified "binding.gyp" file for "face-recognition" to build dlib with cuda
const detector = fr.FaceDetector()
s.detectObject=function(buffer,d,tx,frameLocation){
var detectStuff = function(frame){
try{
var buffer = fr.loadImage(frame)
var faceRectangles = detector.locateFaces(buffer)
var matrices = []
faceRectangles.forEach(function(v){
var coordinates = [
{"x" : v.rect.left, "y" : v.rect.top},
{"x" : v.rect.right, "y" : v.rect.top},
{"x" : v.rect.right, "y" : v.rect.bottom}
]
var width = Math.sqrt( Math.pow(coordinates[1].x - coordinates[0].x, 2) + Math.pow(coordinates[1].y - coordinates[0].y, 2));
var height = Math.sqrt( Math.pow(coordinates[2].x - coordinates[1].x, 2) + Math.pow(coordinates[2].y - coordinates[1].y, 2))
matrices.push({
x: coordinates[0].x,
y: coordinates[0].y,
width: width,
height: height,
tag: 'UNKNOWN FACE',
confidence: v.confidence,
})
})
if(matrices.length > 0){
tx({
f: 'trigger',
id: d.id,
ke: d.ke,
details:{
plug: config.plug,
name: 'dlib',
reason: 'object',
matrices: matrices,
imgHeight: parseFloat(d.mon.detector_scale_y),
imgWidth: parseFloat(d.mon.detector_scale_x)
}
})
}
fs.unlink(frame,function(){
})
}catch(err){
console.log(err)
}
}
if(frameLocation){
detectStuff(frameLocation)
}else{
d.tmpFile=s.gid(5)+'.jpg'
if(!fs.existsSync(s.dir.streams)){
fs.mkdirSync(s.dir.streams);
}
d.dir=s.dir.streams+d.ke+'/'
if(!fs.existsSync(d.dir)){
fs.mkdirSync(d.dir);
}
d.dir=s.dir.streams+d.ke+'/'+d.id+'/'
if(!fs.existsSync(d.dir)){
fs.mkdirSync(d.dir);
}
fs.writeFile(d.dir+d.tmpFile,buffer,function(err){
if(err) return s.systemLog(err);
try{
detectStuff(d.dir+d.tmpFile)
}catch(error){
console.error('Catch: ' + error);
}
})
}
}

View file

@ -7,407 +7,117 @@
// If you like what I am doing here and want me to continue please consider donating :)
// PayPal : paypal@m03.ca
//
process.on('uncaughtException', function (err) {
console.error('uncaughtException',err);
});
//main vars
var fs=require('fs');
// Base Init >>
var fs = require('fs');
var config = require('./conf.json')
var exec = require('child_process').exec;
var moment = require('moment');
var Canvas = require('canvas');
var express = require('express');
var config=require('./conf.json');
var http = require('http'),
app = express(),
server = http.createServer(app);
s={
group:{},
dir:{
cascades:__dirname+'/cascades/'
},
isWin:(process.platform==='win32'),
s:function(json){return JSON.stringify(json,null,3)}
}
s.checkCorrectPathEnding=function(x){
var length=x.length
if(x.charAt(length-1)!=='/'){
x=x+'/'
var s
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. This may be because you started this plugin on another machine. Just copy the pluginBase.js file into this (plugin) directory.')
return console.log(config.plug,'pluginBase.js was not found.')
}
return x.replace('__DIR__',__dirname)
}
if(!config.port){config.port=8080}
if(!config.hostPort){config.hostPort=8082}
if(config.systemLog===undefined){config.systemLog=true}
// Base Init />>
// OpenALPR Init >>
if(config.alprConfig===undefined){config.alprConfig=__dirname+'/openalpr.conf'}
//default stream folder check
if(!config.streamDir){
if(s.isWin===false){
config.streamDir='/dev/shm'
}else{
config.streamDir=config.windowsTempDir
}
if(!fs.existsSync(config.streamDir)){
config.streamDir=__dirname+'/streams/'
}else{
config.streamDir+='/streams/'
}
}
s.dir.streams=config.streamDir;
//streams dir
if(!fs.existsSync(s.dir.streams)){
fs.mkdirSync(s.dir.streams);
}
s.gid=function(x){
if(!x){x=10};var t = "";var p = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
for( var i=0; i < x; i++ )
t += p.charAt(Math.floor(Math.random() * p.length));
return t;
};
s.detectObject=function(buffer,d,tx){
var keys = Object.keys(d.mon.detector_cascades);
if(d.mon.detector_lisence_plate==="1"){
if(!d.mon.detector_lisence_plate_country||d.mon.detector_lisence_plate_country===''){
d.mon.detector_lisence_plate_country='us'
}
d.tmpFile=s.gid(5)+'.jpg'
if(!fs.existsSync(s.dir.streams)){
fs.mkdirSync(s.dir.streams);
}
d.dir=s.dir.streams+d.ke+'/'
if(!fs.existsSync(d.dir)){
fs.mkdirSync(d.dir);
}
d.dir=s.dir.streams+d.ke+'/'+d.id+'/'
if(!fs.existsSync(d.dir)){
fs.mkdirSync(d.dir);
}
fs.writeFile(d.dir+d.tmpFile,buffer,function(err){
if(err) return s.systemLog(err);
exec('alpr -j --config '+config.alprConfig+' -c '+d.mon.detector_lisence_plate_country+' '+d.dir+d.tmpFile,{encoding:'utf8'},(err, scan, stderr) => {
if(err){
s.systemLog(err);
}else{
try{
try{
scan=JSON.parse(scan.replace('--(!)Loaded CUDA classifier','').trim())
}catch(err){
if(!scan||!scan.results){
return s.systemLog(scan,err);
}
}
// console.log('scan',scan)
if(scan.results.length>0){
if(s.isNumberOfTriggersMet(d,2)){
scan.plates=[]
scan.mats=[]
scan.results.forEach(function(v){
v.candidates.forEach(function(g,n){
if(v.candidates[n].matches_template)
delete(v.candidates[n].matches_template)
})
scan.plates.push({coordinates:v.coordinates,candidates:v.candidates,confidence:v.confidence,plate:v.plate})
var width = Math.sqrt( Math.pow(v.coordinates[1].x - v.coordinates[0].x, 2) + Math.pow(v.coordinates[1].y - v.coordinates[0].y, 2));
var height = Math.sqrt( Math.pow(v.coordinates[2].x - v.coordinates[1].x, 2) + Math.pow(v.coordinates[2].y - v.coordinates[1].y, 2))
scan.mats.push({
x:v.coordinates[0].x,
y:v.coordinates[0].y,
width:width,
height:height,
tag:v.plate
})
})
tx({f:'trigger',id:d.id,ke:d.ke,details:{split:true,plug:config.plug,name:'licensePlate',reason:'object',matrices:scan.mats,imgHeight:d.mon.detector_scale_y,imgWidth:d.mon.detector_scale_x,frame:d.base64}})
}
}
}catch(err){
s.systemLog(scan,err);
}
}
exec('rm -rf '+d.dir+d.tmpFile,{encoding:'utf8'})
})
})
}
}
s.systemLog=function(q,w,e){
if(w===undefined){return}
if(!w){w=''}
if(!e){e=''}
if(config.systemLog===true){
return console.log(moment().format(),q,w,e)
}
}
s.blenderRegion=function(d,cord,tx){
d.width = d.image.width;
d.height = d.image.height;
if(!s.group[d.ke][d.id].canvas[cord.name]){
if(!cord.sensitivity||isNaN(cord.sensitivity)){
cord.sensitivity=d.mon.detector_sensitivity;
}
s.group[d.ke][d.id].canvas[cord.name] = new Canvas(d.width,d.height);
s.group[d.ke][d.id].canvasContext[cord.name] = s.group[d.ke][d.id].canvas[cord.name].getContext('2d');
s.group[d.ke][d.id].canvasContext[cord.name].fillStyle = '#000';
s.group[d.ke][d.id].canvasContext[cord.name].fillRect( 0, 0,d.width,d.height);
if(cord.points&&cord.points.length>0){
s.group[d.ke][d.id].canvasContext[cord.name].beginPath();
for (var b = 0; b < cord.points.length; b++){
cord.points[b][0]=parseFloat(cord.points[b][0]);
cord.points[b][1]=parseFloat(cord.points[b][1]);
if(b===0){
s.group[d.ke][d.id].canvasContext[cord.name].moveTo(cord.points[b][0],cord.points[b][1]);
}else{
s.group[d.ke][d.id].canvasContext[cord.name].lineTo(cord.points[b][0],cord.points[b][1]);
}
}
s.group[d.ke][d.id].canvasContext[cord.name].clip();
}
}
if(!s.group[d.ke][d.id].canvasContext[cord.name]){
return
}
s.group[d.ke][d.id].canvasContext[cord.name].drawImage(d.image, 0, 0, d.width, d.height);
if(!s.group[d.ke][d.id].blendRegion[cord.name]){
s.group[d.ke][d.id].blendRegion[cord.name] = new Canvas(d.width, d.height);
s.group[d.ke][d.id].blendRegionContext[cord.name] = s.group[d.ke][d.id].blendRegion[cord.name].getContext('2d');
}
var sourceData = s.group[d.ke][d.id].canvasContext[cord.name].getImageData(0, 0, d.width, d.height);
// create an image if the previous image doesn<73>t exist
if (!s.group[d.ke][d.id].lastRegionImageData[cord.name]) s.group[d.ke][d.id].lastRegionImageData[cord.name] = s.group[d.ke][d.id].canvasContext[cord.name].getImageData(0, 0, d.width, d.height);
// create a ImageData instance to receive the blended result
var blendedData = s.group[d.ke][d.id].canvasContext[cord.name].createImageData(d.width, d.height);
// blend the 2 images
s.differenceAccuracy(blendedData.data,sourceData.data,s.group[d.ke][d.id].lastRegionImageData[cord.name].data);
// draw the result in a canvas
s.group[d.ke][d.id].blendRegionContext[cord.name].putImageData(blendedData, 0, 0);
// store the current webcam image
s.group[d.ke][d.id].lastRegionImageData[cord.name] = sourceData;
blendedData = s.group[d.ke][d.id].blendRegionContext[cord.name].getImageData(0, 0, d.width, d.height);
var i = 0;
d.average = 0;
while (i < (blendedData.data.length * 0.25)) {
d.average += (blendedData.data[i * 4] + blendedData.data[i * 4 + 1] + blendedData.data[i * 4 + 2]);
++i;
}
d.average = (d.average / (blendedData.data.length * 0.25))*10;
if (d.average > parseFloat(cord.sensitivity)){
if(s.isNumberOfTriggersMet(d,2)){
if(d.mon.detector_use_detect_object==="1"&&d.mon.detector_second!=='1'){
var buffer=s.group[d.ke][d.id].canvas[cord.name].toBuffer();
s.detectObject(buffer,d,tx)
}else{
tx({f:'trigger',id:d.id,ke:d.ke,details:{split:true,plug:config.plug,name:cord.name,reason:'motion',confidence:d.average,frame:d.base64}})
}
}
}
s.group[d.ke][d.id].canvasContext[cord.name].clearRect(0, 0, d.width, d.height);
s.group[d.ke][d.id].blendRegionContext[cord.name].clearRect(0, 0, d.width, d.height);
}
function blobToBuffer (blob, cb) {
if (typeof Blob === 'undefined' || !(blob instanceof Blob)) {
throw new Error('first argument must be a Blob')
}
if (typeof cb !== 'function') {
throw new Error('second argument must be a function')
}
var reader = new FileReader()
function onLoadEnd (e) {
reader.removeEventListener('loadend', onLoadEnd, false)
if (e.error) cb(e.error)
else cb(null, Buffer.from(reader.result))
}
reader.addEventListener('loadend', onLoadEnd, false)
reader.readAsArrayBuffer(blob)
}
function fastAbs(value) {
return (value ^ (value >> 31)) - (value >> 31);
}
function threshold(value) {
return (value > 0x15) ? 0xFF : 0;
}
s.differenceAccuracy=function(target, data1, data2) {
if (data1.length != data2.length) return null;
var i = 0;
while (i < (data1.length * 0.25)) {
var average1 = (data1[4 * i] + data1[4 * i + 1] + data1[4 * i + 2]) / 3;
var average2 = (data2[4 * i] + data2[4 * i + 1] + data2[4 * i + 2]) / 3;
var diff = threshold(fastAbs(average1 - average2));
target[4 * i] = diff;
target[4 * i + 1] = diff;
target[4 * i + 2] = diff;
target[4 * i + 3] = 0xFF;
++i;
}
}
s.checkAreas=function(d,tx){
if(!s.group[d.ke][d.id].cords){
if(!d.mon.cords){d.mon.cords={}}
s.group[d.ke][d.id].cords=Object.values(d.mon.cords);
}
if(d.mon.detector_frame==='1'){
d.mon.cords.frame={name:'FULL_FRAME',s:d.mon.detector_sensitivity,points:[[0,0],[0,d.image.height],[d.image.width,d.image.height],[d.image.width,0]]};
s.group[d.ke][d.id].cords.push(d.mon.cords.frame);
}
for (var b = 0; b < s.group[d.ke][d.id].cords.length; b++){
if(!s.group[d.ke][d.id].cords[b]){return}
s.blenderRegion(d,s.group[d.ke][d.id].cords[b],tx)
}
delete(d.image)
}
s.isNumberOfTriggersMet = function(d,max){
// ++s.group[d.ke][d.id].numberOfTriggers
// clearTimeout(s.group[d.ke][d.id].numberOfTriggersTimeout)
// s.group[d.ke][d.id].numberOfTriggersTimeout = setTimeout(function(){
// s.group[d.ke][d.id].numberOfTriggers=0
// },10000)
// if(s.group[d.ke][d.id].numberOfTriggers>max){
return true;
// }
// return false;
}
s.MainEventController=function(d,cn,tx){
switch(d.f){
case'init_plugin_as_host':
if(!cn){
console.log('No CN',d)
return
}
if(d.key!==config.key){
console.log(new Date(),'Plugin Key Mismatch',cn.request.connection.remoteAddress,d)
cn.emit('init',{ok:false})
cn.disconnect()
}else{
console.log(new Date(),'Plugin Connected to Client',cn.request.connection.remoteAddress)
cn.emit('init',{ok:true,plug:config.plug,notice:config.notice,type:config.type})
}
break;
case'init_monitor':
if(s.group[d.ke]&&s.group[d.ke][d.id]){
s.group[d.ke][d.id].canvas={}
s.group[d.ke][d.id].canvasContext={}
s.group[d.ke][d.id].blendRegion={}
s.group[d.ke][d.id].blendRegionContext={}
s.group[d.ke][d.id].lastRegionImageData={}
s.group[d.ke][d.id].numberOfTriggers=0
delete(s.group[d.ke][d.id].cords)
delete(s.group[d.ke][d.id].buffer)
}
break;
case'init_aws_push':
// console.log('init_aws')
s.group[d.ke][d.id].aws={links:[],complete:0,total:d.total,videos:[],tx:tx}
break;
case'frame':
try{
if(!s.group[d.ke]){
s.group[d.ke]={}
}
if(!s.group[d.ke][d.id]){
s.group[d.ke][d.id]={
canvas:{},
canvasContext:{},
lastRegionImageData:{},
blendRegion:{},
blendRegionContext:{},
}
}
if(!s.group[d.ke][d.id].buffer){
s.group[d.ke][d.id].buffer=[d.frame];
}else{
s.group[d.ke][d.id].buffer.push(d.frame)
}
if(d.frame[d.frame.length-2] === 0xFF && d.frame[d.frame.length-1] === 0xD9){
s.group[d.ke][d.id].buffer=Buffer.concat(s.group[d.ke][d.id].buffer);
try{
d.mon.detector_cascades=JSON.parse(d.mon.detector_cascades)
}catch(err){
}
if(d.mon.detector_frame_save==="1"){
d.base64=s.group[d.ke][d.id].buffer.toString('base64')
}
if(d.mon.detector_second==='1'&&d.objectOnly===true){
s.detectObject(s.group[d.ke][d.id].buffer,d,tx)
}else{
if(d.mon.detector_use_motion==="1"||d.mon.detector_use_detect_object!=="1"){
if((typeof d.mon.cords ==='string')&&d.mon.cords.trim()===''){
d.mon.cords=[]
}else{
try{
d.mon.cords=JSON.parse(d.mon.cords)
}catch(err){
// console.log('d.mon.cords',err,d)
}
}
s.group[d.ke][d.id].cords=Object.values(d.mon.cords);
d.mon.cords=d.mon.cords;
d.image = new Canvas.Image;
if(d.mon.detector_scale_x===''||d.mon.detector_scale_y===''){
s.systemLog('Must set detector image size')
return
}else{
d.image.width=d.mon.detector_scale_x;
d.image.height=d.mon.detector_scale_y;
}
d.width=d.image.width;
d.height=d.image.height;
d.image.onload = function() {
s.checkAreas(d,tx);
}
d.image.src = s.group[d.ke][d.id].buffer;
}else{
s.detectObject(s.group[d.ke][d.id].buffer,d,tx)
}
}
s.group[d.ke][d.id].buffer=null;
}
}catch(err){
// OpenALPR Init />>
s.detectObject = function(buffer,d,tx,frameLocation){
var detectStuff = function(frame){
try{
exec('alpr -j --config '+config.alprConfig+' -c '+d.mon.detector_lisence_plate_country+' '+frame,{encoding:'utf8'},(err, scan, stderr) => {
if(err){
s.systemLog(err)
delete(s.group[d.ke][d.id].buffer)
s.systemLog(err);
}else{
try{
try{
scan=JSON.parse(scan.replace('--(!)Loaded CUDA classifier','').trim())
}catch(err){
if(!scan||!scan.results){
return s.systemLog(scan,err);
}
}
// console.log('scan',scan)
if(scan.results.length > 0){
scan.plates=[]
scan.mats=[]
scan.results.forEach(function(v){
v.candidates.forEach(function(g,n){
if(v.candidates[n].matches_template){
delete(v.candidates[n].matches_template)
}
})
scan.plates.push({
coordinates: v.coordinates,
candidates: v.candidates,
confidence: v.confidence,
plate: v.plate
})
var width = Math.sqrt( Math.pow(v.coordinates[1].x - v.coordinates[0].x, 2) + Math.pow(v.coordinates[1].y - v.coordinates[0].y, 2));
var height = Math.sqrt( Math.pow(v.coordinates[2].x - v.coordinates[1].x, 2) + Math.pow(v.coordinates[2].y - v.coordinates[1].y, 2))
scan.mats.push({
x: v.coordinates[0].x,
y: v.coordinates[0].y,
width: width,
height: height,
tag: v.plate
})
})
tx({
f: 'trigger',
id: d.id,
ke: d.ke,
details: {
plug: config.plug,
name: 'licensePlate',
reason: 'object',
matrices: scan.mats,
imgHeight: d.mon.detector_scale_y,
imgWidth: d.mon.detector_scale_x,
frame: d.base64
}
})
}
}catch(err){
s.systemLog(scan,err);
}
}
fs.unlink(frame,function(){
})
})
}catch(err){
console.log(err)
}
}
if(frameLocation){
detectStuff(frameLocation)
}else{
d.tmpFile=s.gid(5)+'.jpg'
if(!fs.existsSync(s.dir.streams)){
fs.mkdirSync(s.dir.streams);
}
d.dir=s.dir.streams+d.ke+'/'
if(!fs.existsSync(d.dir)){
fs.mkdirSync(d.dir);
}
d.dir=s.dir.streams+d.ke+'/'+d.id+'/'
if(!fs.existsSync(d.dir)){
fs.mkdirSync(d.dir);
}
fs.writeFile(d.dir+d.tmpFile,buffer,function(err){
if(err) return s.systemLog(err);
try{
detectStuff(d.dir+d.tmpFile)
}catch(error){
console.error('Catch: ' + error);
}
break;
})
}
}
server.listen(config.hostPort);
//web pages and plugin api
app.get('/', function (req, res) {
res.end('<b>'+config.plug+'</b> for Shinobi is running')
});
//Conector to Shinobi
if(config.mode==='host'){
//start plugin as host
var io = require('socket.io')(server);
io.attach(server);
s.connectedClients={};
io.on('connection', function (cn) {
s.connectedClients[cn.id]={id:cn.id}
s.connectedClients[cn.id].tx = function(data){
data.pluginKey=config.key;data.plug=config.plug;
return io.to(cn.id).emit('ocv',data);
}
cn.on('f',function(d){
s.MainEventController(d,cn,s.connectedClients[cn.id].tx)
});
cn.on('disconnect',function(d){
delete(s.connectedClients[cn.id])
})
});
}else{
//start plugin as client
if(!config.host){config.host='localhost'}
var io = require('socket.io-client')('ws://'+config.host+':'+config.port);//connect to master
s.cx=function(x){x.pluginKey=config.key;x.plug=config.plug;return io.emit('ocv',x)}
io.on('connect',function(d){
s.cx({f:'init',plug:config.plug,notice:config.notice,type:config.type});
})
io.on('disconnect',function(d){
io.connect();
})
io.on('f',function(d){
s.MainEventController(d,null,s.cx)
})
}

View file

@ -7,311 +7,42 @@
// If you like what I am doing here and want me to continue please consider donating :)
// PayPal : paypal@m03.ca
//
process.on('uncaughtException', function (err) {
console.error('uncaughtException',err);
});
var fs=require('fs');
var cv=require('opencv4nodejs');
// Base Init >>
var fs = require('fs');
var config = require('./conf.json')
var s
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.')
}
}
// Base Init />>
// OpenCV Init >>
var exec = require('child_process').exec;
var moment = require('moment');
var Canvas = require('canvas');
var express = require('express');
var http = require('http'),
app = express(),
server = http.createServer(app);
var config=require('./conf.json');
if(!config.port){config.port=8080}
if(!config.hostPort){config.hostPort=8082}
if(config.systemLog===undefined){config.systemLog=true}
var cv = require('opencv4nodejs');
if(config.cascadesDir===undefined){config.cascadesDir=__dirname+'/cascades/'}
if(config.alprConfig===undefined){config.alprConfig=__dirname+'/openalpr.conf'}
s={
group:{},
dir:{
cascades : config.cascadesDir
},
isWin:(process.platform==='win32'),
foundCascades : {
}
}
//default stream folder check
if(!config.streamDir){
if(s.isWin===false){
config.streamDir='/dev/shm'
}else{
config.streamDir=config.windowsTempDir
}
if(!fs.existsSync(config.streamDir)){
config.streamDir=__dirname+'/streams/'
}else{
config.streamDir+='/streams/'
}
}
s.dir.streams=config.streamDir;
//streams dir
if(!fs.existsSync(s.dir.streams)){
fs.mkdirSync(s.dir.streams);
}
//streams dir
if(!fs.existsSync(s.dir.cascades)){
fs.mkdirSync(s.dir.cascades);
}
s.gid=function(x){
if(!x){x=10};var t = "";var p = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
for( var i=0; i < x; i++ )
t += p.charAt(Math.floor(Math.random() * p.length));
return t;
};
s.findCascades=function(callback){
var tmp={};
tmp.foundCascades=[];
fs.readdir(s.dir.cascades,function(err,files){
files.forEach(function(cascade,n){
if(cascade.indexOf('.xml')>-1){
tmp.foundCascades.push(cascade.replace('.xml',''))
}
})
s.cascadesInDir=tmp.foundCascades;
callback(tmp.foundCascades)
s.findCascades = function(callback){
var foundCascades = []
Object.keys(cv).forEach(function(cascade,n){
if(cascade.indexOf('HAAR_') >- 1){
foundCascades.push(cascade)
}
})
s.cascadesInDir = foundCascades
s.systemLog('Found '+foundCascades.length+' Cascades')
callback(foundCascades)
}
s.findCascades(function(){
s.findCascades(function(cascades){
//get cascades
})
s.detectLicensePlate=function(buffer,d,tx){
if(!d.mon.detector_lisence_plate_country||d.mon.detector_lisence_plate_country===''){
d.mon.detector_lisence_plate_country='us'
}
d.tmpFile=s.gid(5)+'.jpg'
if(!fs.existsSync(s.dir.streams)){
fs.mkdirSync(s.dir.streams);
}
d.dir=s.dir.streams+d.ke+'/'
if(!fs.existsSync(d.dir)){
fs.mkdirSync(d.dir);
}
d.dir=s.dir.streams+d.ke+'/'+d.id+'/'
if(!fs.existsSync(d.dir)){
fs.mkdirSync(d.dir);
}
fs.writeFile(d.dir+d.tmpFile,buffer,function(err){
if(err) return s.systemLog(err);
exec('alpr -j --config '+config.alprConfig+' -c '+d.mon.detector_lisence_plate_country+' '+d.dir+d.tmpFile,{encoding:'utf8'},(err, scan, stderr) => {
if(err){
s.systemLog(err);
}else{
try{
scan=JSON.parse(scan.replace('--(!)Loaded CUDA classifier','').trim())
}catch(err){
if(!scan||!scan.results){
return s.systemLog(scan,err);
}
}
if(scan.results.length>0){
scan.plates=[]
scan.mats=[]
scan.results.forEach(function(v){
v.candidates.forEach(function(g,n){
if(v.candidates[n].matches_template)
delete(v.candidates[n].matches_template)
})
scan.plates.push({coordinates:v.coordinates,candidates:v.candidates,confidence:v.confidence,plate:v.plate})
var width = Math.sqrt( Math.pow(v.coordinates[1].x - v.coordinates[0].x, 2) + Math.pow(v.coordinates[1].y - v.coordinates[0].y, 2));
var height = Math.sqrt( Math.pow(v.coordinates[2].x - v.coordinates[1].x, 2) + Math.pow(v.coordinates[2].y - v.coordinates[1].y, 2))
scan.mats.push({
x:v.coordinates[0].x,
y:v.coordinates[0].y,
width:width,
height:height,
tag:v.plate
})
})
tx({f:'trigger',id:d.id,ke:d.ke,details:{split:true,plug:config.plug,name:'licensePlate',reason:'object',matrices:scan.mats,imgHeight:d.mon.detector_scale_y,imgWidth:d.mon.detector_scale_x,frame:d.base64}})
}
}
exec('rm -rf '+d.dir+d.tmpFile,{encoding:'utf8'})
})
})
}
s.detectObject=function(buffer,d,tx){
//detect license plate?
if(d.mon.detector_lisence_plate==="1"){
s.detectLicensePlate(buffer,d,tx)
}
//check selected opencv cascades
if(!d.mon.detector_cascades || d.mon.detector_cascades === '')return;
var selectedCascades = Object.keys(d.mon.detector_cascades);
if(selectedCascades.length > 0){
cv.imdecodeAsync(buffer,(err,im) => {
if(err){
console.log(err)
return
}
selectedCascades.forEach(function(cascade){
var cascadePath = s.dir.cascades+cascade+'.xml'
if(s.foundCascades[cascadePath] === undefined){
s.foundCascades[cascadePath] = fs.existsSync(cascadePath)
}else if(s.foundCascades[cascadePath] === false){
return s.systemLog('Attempted to use non existant cascade. : '+cascadePath)
}
var classifier = new cv.CascadeClassifier(cascadePath)
var matrices = classifier.detectMultiScaleGpu(im).objects
if(matrices.length > 0){
matrices.forEach(function(v,n){
v.centerX=v.width/2
v.centerY=v.height/2
v.centerXnoParent=v.x+(v.width/2)
v.centerYnoParent=v.y+(v.height/2)
})
s.cx({
f:'trigger',
id:d.id,
ke:d.ke,
name:cascade,
details:{
plug:'built-in-opencv',
name:cascade,
reason:'object',
matrices : matrices,
confidence:d.average
},
imgHeight:d.mon.detector_scale_y,
imgWidth:d.mon.detector_scale_x
})
}
})
});
}
}
s.systemLog=function(q,w,e){
if(!w){w=''}
if(!e){e=''}
if(config.systemLog===true){
return console.log(moment().format(),q,w,e)
}
}
s.blenderRegion=function(d,cord,tx){
d.width = d.image.width;
d.height = d.image.height;
if(!s.group[d.ke][d.id].canvas[cord.name]){
if(!cord.sensitivity||isNaN(cord.sensitivity)){
cord.sensitivity=d.mon.detector_sensitivity;
}
s.group[d.ke][d.id].canvas[cord.name] = new Canvas(d.width,d.height);
s.group[d.ke][d.id].canvasContext[cord.name] = s.group[d.ke][d.id].canvas[cord.name].getContext('2d');
s.group[d.ke][d.id].canvasContext[cord.name].fillStyle = '#000';
s.group[d.ke][d.id].canvasContext[cord.name].fillRect( 0, 0,d.width,d.height);
if(cord.points&&cord.points.length>0){
s.group[d.ke][d.id].canvasContext[cord.name].beginPath();
for (var b = 0; b < cord.points.length; b++){
cord.points[b][0]=parseFloat(cord.points[b][0]);
cord.points[b][1]=parseFloat(cord.points[b][1]);
if(b===0){
s.group[d.ke][d.id].canvasContext[cord.name].moveTo(cord.points[b][0],cord.points[b][1]);
}else{
s.group[d.ke][d.id].canvasContext[cord.name].lineTo(cord.points[b][0],cord.points[b][1]);
}
}
s.group[d.ke][d.id].canvasContext[cord.name].clip();
}
}
if(!s.group[d.ke][d.id].canvasContext[cord.name]){
return
}
s.group[d.ke][d.id].canvasContext[cord.name].drawImage(d.image, 0, 0, d.width, d.height);
if(!s.group[d.ke][d.id].blendRegion[cord.name]){
s.group[d.ke][d.id].blendRegion[cord.name] = new Canvas(d.width, d.height);
s.group[d.ke][d.id].blendRegionContext[cord.name] = s.group[d.ke][d.id].blendRegion[cord.name].getContext('2d');
}
var sourceData = s.group[d.ke][d.id].canvasContext[cord.name].getImageData(0, 0, d.width, d.height);
// create an image if the previous image doesn<73>t exist
if (!s.group[d.ke][d.id].lastRegionImageData[cord.name]) s.group[d.ke][d.id].lastRegionImageData[cord.name] = s.group[d.ke][d.id].canvasContext[cord.name].getImageData(0, 0, d.width, d.height);
// create a ImageData instance to receive the blended result
var blendedData = s.group[d.ke][d.id].canvasContext[cord.name].createImageData(d.width, d.height);
// blend the 2 images
s.differenceAccuracy(blendedData.data,sourceData.data,s.group[d.ke][d.id].lastRegionImageData[cord.name].data);
// draw the result in a canvas
s.group[d.ke][d.id].blendRegionContext[cord.name].putImageData(blendedData, 0, 0);
// store the current webcam image
s.group[d.ke][d.id].lastRegionImageData[cord.name] = sourceData;
blendedData = s.group[d.ke][d.id].blendRegionContext[cord.name].getImageData(0, 0, d.width, d.height);
var i = 0;
d.average = 0;
while (i < (blendedData.data.length * 0.25)) {
d.average += (blendedData.data[i * 4] + blendedData.data[i * 4 + 1] + blendedData.data[i * 4 + 2]);
++i;
}
d.average = (d.average / (blendedData.data.length * 0.25))*10;
if (d.average > parseFloat(cord.sensitivity)){
if(d.mon.detector_use_detect_object==="1"&&d.mon.detector_second!=='1'){
var buffer=s.group[d.ke][d.id].canvas[cord.name].toBuffer();
s.detectObject(buffer,d,tx)
}else{
tx({f:'trigger',id:d.id,ke:d.ke,details:{split:true,plug:config.plug,name:cord.name,reason:'motion',confidence:d.average,frame:d.base64}})
}
}
s.group[d.ke][d.id].canvasContext[cord.name].clearRect(0, 0, d.width, d.height);
s.group[d.ke][d.id].blendRegionContext[cord.name].clearRect(0, 0, d.width, d.height);
}
function blobToBuffer (blob, cb) {
if (typeof Blob === 'undefined' || !(blob instanceof Blob)) {
throw new Error('first argument must be a Blob')
}
if (typeof cb !== 'function') {
throw new Error('second argument must be a function')
}
var reader = new FileReader()
function onLoadEnd (e) {
reader.removeEventListener('loadend', onLoadEnd, false)
if (e.error) cb(e.error)
else cb(null, Buffer.from(reader.result))
}
reader.addEventListener('loadend', onLoadEnd, false)
reader.readAsArrayBuffer(blob)
}
function fastAbs(value) {
return (value ^ (value >> 31)) - (value >> 31);
}
function threshold(value) {
return (value > 0x15) ? 0xFF : 0;
}
s.differenceAccuracy=function(target, data1, data2) {
if (data1.length != data2.length) return null;
var i = 0;
while (i < (data1.length * 0.25)) {
var average1 = (data1[4 * i] + data1[4 * i + 1] + data1[4 * i + 2]) / 3;
var average2 = (data2[4 * i] + data2[4 * i + 1] + data2[4 * i + 2]) / 3;
var diff = threshold(fastAbs(average1 - average2));
target[4 * i] = diff;
target[4 * i + 1] = diff;
target[4 * i + 2] = diff;
target[4 * i + 3] = 0xFF;
++i;
}
}
s.checkAreas=function(d,tx){
if(!s.group[d.ke][d.id].cords){
if(!d.mon.cords){d.mon.cords={}}
s.group[d.ke][d.id].cords=Object.values(d.mon.cords);
}
if(d.mon.detector_frame==='1'){
d.mon.cords.frame={name:'FULL_FRAME',s:d.mon.detector_sensitivity,points:[[0,0],[0,d.image.height],[d.image.width,d.image.height],[d.image.width,0]]};
s.group[d.ke][d.id].cords.push(d.mon.cords.frame);
}
for (var b = 0; b < s.group[d.ke][d.id].cords.length; b++){
if(!s.group[d.ke][d.id].cords[b]){return}
s.blenderRegion(d,s.group[d.ke][d.id].cords[b],tx)
}
delete(d.image)
}
s.MainEventController=function(d,cn,tx){
s.onPluginEventExtender(function(d,cn,tx){
switch(d.f){
case'refreshPlugins':
s.findCascades(function(cascades){
@ -321,145 +52,160 @@ s.MainEventController=function(d,cn,tx){
case'readPlugins':
s.cx({f:'s.tx',data:{f:'detector_cascade_list',cascades:s.cascadesInDir},to:'GRP_'+d.ke})
break;
case'init_plugin_as_host':
if(!cn){
console.log('No CN',d)
return
}
})
// OpenCV Init />>
s.detectObject = function(buffer,d,tx,frameLocation){
var detectStuff = function(frameBuffer,callback){
if(d.mon.detector_lisence_plate==="1"){
s.detectLicensePlate(buffer,d,tx,frameLocation)
}
if(!d.mon.detector_cascades || d.mon.detector_cascades === '')return;
var selectedCascades = Object.keys(d.mon.detector_cascades);
if(selectedCascades.length > 0){
cv.imdecodeAsync(frameBuffer,(err,im) => {
if(err){
console.log(err)
return
}
selectedCascades.forEach(function(cascade){
if(!cv[cascade]){
return s.systemLog('Attempted to use non existant cascade. : '+cascade)
}
var classifier = new cv.CascadeClassifier(cv[cascade])
var matrices = classifier.detectMultiScaleGpu(im).objects
if(matrices.length > 0){
matrices.forEach(function(v,n){
v.centerX=v.width/2
v.centerY=v.height/2
v.centerXnoParent=v.x+(v.width/2)
v.centerYnoParent=v.y+(v.height/2)
})
s.cx({
f:'trigger',
id:d.id,
ke:d.ke,
name:cascade,
details:{
plug:'built-in-opencv',
name:cascade,
reason:'object',
matrices : matrices,
confidence:d.average
},
imgHeight:d.mon.detector_scale_y,
imgWidth:d.mon.detector_scale_x
})
}
})
});
}
}
if(frameLocation){
fs.readFile(frameLocation,function(err,buffer){
if(!err){
detectStuff(buffer)
}
if(d.key!==config.key){
console.log(new Date(),'Plugin Key Mismatch',cn.request.connection.remoteAddress,d)
cn.emit('init',{ok:false})
cn.disconnect()
}else{
console.log(new Date(),'Plugin Connected to Client',cn.request.connection.remoteAddress)
cn.emit('init',{ok:true,plug:config.plug,notice:config.notice,type:config.type})
}
break;
case'init_monitor':
if(s.group[d.ke]&&s.group[d.ke][d.id]){
s.group[d.ke][d.id].canvas={}
s.group[d.ke][d.id].canvasContext={}
s.group[d.ke][d.id].blendRegion={}
s.group[d.ke][d.id].blendRegionContext={}
s.group[d.ke][d.id].lastRegionImageData={}
s.group[d.ke][d.id].numberOfTriggers=0
delete(s.group[d.ke][d.id].cords)
delete(s.group[d.ke][d.id].buffer)
}
break;
case'init_aws_push':
// console.log('init_aws')
s.group[d.ke][d.id].aws={links:[],complete:0,total:d.total,videos:[],tx:tx}
break;
case'frame':
try{
if(!s.group[d.ke]){
s.group[d.ke]={}
}
if(!s.group[d.ke][d.id]){
s.group[d.ke][d.id]={
canvas:{},
canvasContext:{},
lastRegionImageData:{},
blendRegion:{},
blendRegionContext:{},
}
}
if(!s.group[d.ke][d.id].buffer){
s.group[d.ke][d.id].buffer=[d.frame];
}else{
s.group[d.ke][d.id].buffer.push(d.frame)
}
if(d.frame[d.frame.length-2] === 0xFF && d.frame[d.frame.length-1] === 0xD9){
s.group[d.ke][d.id].buffer=Buffer.concat(s.group[d.ke][d.id].buffer);
try{
d.mon.detector_cascades=JSON.parse(d.mon.detector_cascades)
}catch(err){
}
if(d.mon.detector_frame_save==="1"){
d.base64=s.group[d.ke][d.id].buffer.toString('base64')
}
if(d.mon.detector_second==='1'&&d.objectOnly===true){
s.detectObject(s.group[d.ke][d.id].buffer,d,tx)
}else{
if((d.mon.detector_pam !== '1' && d.mon.detector_use_motion === "1") || d.mon.detector_use_detect_object !== "1"){
if((typeof d.mon.cords ==='string')&&d.mon.cords.trim()===''){
d.mon.cords=[]
}else{
try{
d.mon.cords=JSON.parse(d.mon.cords)
}catch(err){
// console.log('d.mon.cords',err,d)
}
}
s.group[d.ke][d.id].cords=Object.values(d.mon.cords);
d.mon.cords=d.mon.cords;
d.image = new Canvas.Image;
if(d.mon.detector_scale_x===''||d.mon.detector_scale_y===''){
s.systemLog('Must set detector image size')
return
}else{
d.image.width=d.mon.detector_scale_x;
d.image.height=d.mon.detector_scale_y;
}
d.width=d.image.width;
d.height=d.image.height;
d.image.onload = function() {
s.checkAreas(d,tx);
}
d.image.src = s.group[d.ke][d.id].buffer;
}else{
s.detectObject(s.group[d.ke][d.id].buffer,d,tx)
}
}
s.group[d.ke][d.id].buffer=null;
}
}catch(err){
if(err){
s.systemLog(err)
delete(s.group[d.ke][d.id].buffer)
}
}
break;
fs.unlink(frameLocation,function(){
})
})
}else{
detectStuff(buffer)
}
}
server.listen(config.hostPort);
//web pages and plugin api
app.get('/', function (req, res) {
res.end('<b>'+config.plug+'</b> for Shinobi is running')
});
//Conector to Shinobi
if(config.mode==='host'){
//start plugin as host
var io = require('socket.io')(server);
io.attach(server);
s.connectedClients={};
io.on('connection', function (cn) {
s.connectedClients[cn.id]={id:cn.id}
s.connectedClients[cn.id].tx = function(data){
data.pluginKey=config.key;data.plug=config.plug;
return io.to(cn.id).emit('ocv',data);
// OpenALPR Detector >>
s.detectLicensePlate = function(buffer,d,tx,frameLocation){
var detectStuff = function(frame){
try{
exec('alpr -j --config '+config.alprConfig+' -c '+d.mon.detector_lisence_plate_country+' '+frame,{encoding:'utf8'},(err, scan, stderr) => {
if(err){
s.systemLog(err);
}else{
try{
try{
scan=JSON.parse(scan.replace('--(!)Loaded CUDA classifier','').trim())
}catch(err){
if(!scan||!scan.results){
return s.systemLog(scan,err);
}
}
// console.log('scan',scan)
if(scan.results.length > 0){
scan.plates=[]
scan.mats=[]
scan.results.forEach(function(v){
v.candidates.forEach(function(g,n){
if(v.candidates[n].matches_template){
delete(v.candidates[n].matches_template)
}
})
scan.plates.push({
coordinates: v.coordinates,
candidates: v.candidates,
confidence: v.confidence,
plate: v.plate
})
var width = Math.sqrt( Math.pow(v.coordinates[1].x - v.coordinates[0].x, 2) + Math.pow(v.coordinates[1].y - v.coordinates[0].y, 2));
var height = Math.sqrt( Math.pow(v.coordinates[2].x - v.coordinates[1].x, 2) + Math.pow(v.coordinates[2].y - v.coordinates[1].y, 2))
scan.mats.push({
x: v.coordinates[0].x,
y: v.coordinates[0].y,
width: width,
height: height,
tag: v.plate
})
})
tx({
f: 'trigger',
id: d.id,
ke: d.ke,
details: {
plug: config.plug,
name: 'licensePlate',
reason: 'object',
matrices: scan.mats,
imgHeight: d.mon.detector_scale_y,
imgWidth: d.mon.detector_scale_x,
frame: d.base64
}
})
}
}catch(err){
s.systemLog(scan,err);
}
}
fs.unlink(frame,function(){
})
})
}catch(err){
console.log(err)
}
cn.on('f',function(d){
s.MainEventController(d,cn,s.connectedClients[cn.id].tx)
});
cn.on('disconnect',function(d){
delete(s.connectedClients[cn.id])
}
if(frameLocation){
detectStuff(frameLocation)
}else{
d.tmpFile=s.gid(5)+'.jpg'
if(!fs.existsSync(s.dir.streams)){
fs.mkdirSync(s.dir.streams);
}
d.dir=s.dir.streams+d.ke+'/'
if(!fs.existsSync(d.dir)){
fs.mkdirSync(d.dir);
}
d.dir=s.dir.streams+d.ke+'/'+d.id+'/'
if(!fs.existsSync(d.dir)){
fs.mkdirSync(d.dir);
}
fs.writeFile(d.dir+d.tmpFile,buffer,function(err){
if(err) return s.systemLog(err);
try{
detectStuff(d.dir+d.tmpFile)
}catch(error){
console.error('Catch: ' + error);
}
})
});
}else{
//start plugin as client
if(!config.host){config.host='localhost'}
var io = require('socket.io-client')('ws://'+config.host+':'+config.port);//connect to master
s.cx=function(x){x.pluginKey=config.key;x.plug=config.plug;return io.emit('ocv',x)}
io.on('connect',function(d){
s.cx({f:'init',plug:config.plug,notice:config.notice,type:config.type});
})
io.on('disconnect',function(d){
io.connect();
})
io.on('f',function(d){
s.MainEventController(d,null,s.cx)
})
}
}
}
// OpenALPR Detector />>

256
plugins/pluginBase.js Normal file
View file

@ -0,0 +1,256 @@
//
// Shinobi - Plugin Base
// Copyright (C) 2016-2025 Moe Alam, moeiscool
//
// # Donate
//
// If you like what I am doing here and want me to continue please consider donating :)
// PayPal : paypal@m03.ca
//
var fs=require('fs');
var exec = require('child_process').exec;
var moment = require('moment');
var express = require('express');
var http = require('http'),
app = express();
module.exports = function(__dirname,config){
var plugLog = function(d1){
console.log(new Date(),config.plug,d1)
}
process.on('uncaughtException', function (err) {
console.error('uncaughtException',err);
});
try{
if(!config.skipMainConfigCheck){
mainConfig = require('../conf.json')
plugLog('Main Shinobi Config Found... Checking for Plugin Key...')
var foundKeyAdded = false
if(mainConfig.pluginKeys && mainConfig.pluginKeys[config.plug]){
foundKeyAdded = true
}
if(mainConfig.plugins){
mainConfig.plugins.forEach(function(plug){
if(plug.id === config.plug){
foundKeyAdded = true
}
})
}
plugLog('Plugin Key matches Main Configuration : ' + foundKeyAdded)
if(foundKeyAdded === false){
console.error(new Date(),'Plugin Cannot Be Initiated, Check Plugin Key in Main Configuration!')
}
}
}catch(err){
}
if(!config.port){config.port=8080}
if(!config.hostPort){config.hostPort=8082}
if(config.systemLog===undefined){config.systemLog=true}
if(config.connectionType === undefined)config.connectionType = 'websocket'
s = {
group:{},
dir:{},
isWin:(process.platform==='win32'),
s:function(json){return JSON.stringify(json,null,3)}
}
//default stream folder check
if(!config.streamDir){
if(s.isWin===false){
config.streamDir='/dev/shm'
}else{
config.streamDir=config.windowsTempDir
}
if(!fs.existsSync(config.streamDir)){
config.streamDir=__dirname+'/streams/'
}else{
config.streamDir+='/streams/'
}
}
s.dir.streams=config.streamDir;
//streams dir
if(!fs.existsSync(s.dir.streams)){
fs.mkdirSync(s.dir.streams);
}
s.checkCorrectPathEnding=function(x){
var length=x.length
if(x.charAt(length-1)!=='/'){
x=x+'/'
}
return x.replace('__DIR__',__dirname)
}
s.gid = function(x){
if(!x){x=10};var t = "";var p = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
for( var i=0; i < x; i++ )
t += p.charAt(Math.floor(Math.random() * p.length));
return t;
};
s.systemLog = function(q,w,e){
if(!w){w=''}
if(!e){e=''}
if(config.systemLog===true){
return console.log(moment().format(),q,w,e)
}
}
s.detectObject=function(buffer,d,tx,frameLocation){
console.log('detectObject handler not set')
}
s.onPluginEvent = []
s.onPluginEventExtender = function(extender){
s.onPluginEvent.push(extender)
}
s.MainEventController = function(d,cn,tx){
switch(d.f){
case'init_plugin_as_host':
if(!cn){
console.log('No CN',d)
return
}
if(d.key!==config.key){
console.log(new Date(),'Plugin Key Mismatch',cn.request.connection.remoteAddress,d)
cn.emit('init',{ok:false})
cn.disconnect()
}else{
console.log(new Date(),'Plugin Connected to Client',cn.request.connection.remoteAddress)
cn.emit('init',{ok:true,plug:config.plug,notice:config.notice,type:config.type})
}
break;
case'init_monitor':
retryConnection = 0
if(s.group[d.ke]&&s.group[d.ke][d.id]){
s.group[d.ke][d.id].numberOfTriggers = 0
delete(s.group[d.ke][d.id].cords)
delete(s.group[d.ke][d.id].buffer)
}
break;
case'frameFromRam':
if(!s.group[d.ke]){
s.group[d.ke]={}
}
if(!s.group[d.ke][d.id]){
s.group[d.ke][d.id]={}
}
s.detectObject(buffer,d,tx,d.frameLocation)
break;
case'frame':
try{
if(!s.group[d.ke]){
s.group[d.ke]={}
}
if(!s.group[d.ke][d.id]){
s.group[d.ke][d.id]={}
}
if(!s.group[d.ke][d.id].buffer){
s.group[d.ke][d.id].buffer=[d.frame];
}else{
s.group[d.ke][d.id].buffer.push(d.frame)
}
if(d.frame[d.frame.length-2] === 0xFF && d.frame[d.frame.length-1] === 0xD9){
var buffer = Buffer.concat(s.group[d.ke][d.id].buffer);
s.detectObject(buffer,d,tx)
s.group[d.ke][d.id].buffer=null;
}
}catch(err){
if(err){
s.systemLog(err)
delete(s.group[d.ke][d.id].buffer)
}
}
break;
}
s.onPluginEvent.forEach(function(extender){
extender(d,cn,tx)
})
}
server = http.createServer(app).on('error', function(err){
if(err.code === 'EADDRINUSE'){
//try next port
if(webServerTryCount === 5){
return plugLog('Failed to Start Web Server. No Longer Trying.')
}
++webServerTryCount
var port = parseInt(config.hostPort)
config.hostPort = parseInt(config.hostPort) + 1
plugLog('Failed to Start Web Server on '+port+'. Trying next Port '+config.hostPort)
startWebServer()
}else{
console.log(err)
}
})
var webServerTryCount = 0
var startWebServer = function(){
var port = parseInt(config.hostPort)
server.listen(config.hostPort,function(err){
if(port === config.hostPort){
plugLog('Plugin started on Port ' + port)
}
})
}
startWebServer()
//web pages and plugin api
var webPageMssage = '<b>'+config.plug+'</b> for Shinobi is running'
app.get('/', function (req, res) {
res.end()
});
//Conector to Shinobi
if(config.mode === 'host'){
plugLog('Plugin started as Host')
//start plugin as host
var io = require('socket.io')(server);
io.attach(server);
s.connectedClients={};
io.on('connection', function (cn) {
s.connectedClients[cn.id]={id:cn.id}
s.connectedClients[cn.id].tx = function(data){
data.pluginKey=config.key;data.plug=config.plug;
return io.to(cn.id).emit('ocv',data);
}
cn.on('f',function(d){
s.MainEventController(d,cn,s.connectedClients[cn.id].tx)
});
cn.on('disconnect',function(d){
plugLog('Plugin Disconnected.',cn.id)
delete(s.connectedClients[cn.id])
})
});
}else{
var retryConnection = 0
maxRetryConnection = config.maxRetryConnection || 5
plugLog('Plugin starting as Client, Host Address : '+'ws://'+config.host+':'+config.port)
//start plugin as client
if(!config.host){config.host='localhost'}
var io = require('socket.io-client')('ws://'+config.host+':'+config.port,{
transports: ['websocket']
});
//connect to master
s.cx = function(x){
var sendData = Object.assign(x,{
pluginKey : config.key,
plug : config.plug
})
return io.emit('ocv',sendData)
}
io.on('connect_error', function(err){
plugLog('ws://'+config.host+':'+config.port)
plugLog('Connection Failed')
plugLog(err)
})
io.on('connect',function(d){
s.cx({f:'init',plug:config.plug,notice:config.notice,type:config.type,connectionType:config.connectionType});
})
io.on('disconnect',function(d){
if(retryConnection > maxRetryConnection){
webPageMssage = 'Max Failed Retries Reached'
return plugLog('Max Failed Retries Reached!',maxRetryConnection)
}
++retryConnection
plugLog('Plugin Disconnected. Attempting Reconnect..')
io.connect();
})
io.on('f',function(d){
s.MainEventController(d,null,s.cx)
})
}
return s
}

106
plugins/yolo/INSTALL.sh Normal file
View file

@ -0,0 +1,106 @@
#!/bin/bash
echo "----------------------------------------"
echo "-- Installing Yolo Plugin for Shinobi --"
echo "----------------------------------------"
if ! [ -x "$(command -v nvidia-smi)" ]; then
echo "You need to install NVIDIA Drivers to use this."
echo "inside the Shinobi directory run the following :"
echo "sh INSTALL/cuda.sh"
exit 1
else
echo "NVIDIA Drivers found..."
echo "$(nvidia-smi |grep 'Driver Version')"
fi
echo "-----------------------------------"
if [ ! -d "/usr/local/cuda" ]; then
echo "You need to install CUDA Toolkit to use this."
echo "inside the Shinobi directory run the following :"
echo "sh INSTALL/cuda.sh"
exit 1
else
echo "CUDA Toolkit found..."
echo "============="
echo "Shinobi - Do you want to install the plugin with CUDA support?"
echo "Do this if you installed NVIDIA Drivers, CUDA Toolkit, and CuDNN"
echo "(y)es or (N)o"
read usecuda
if [ "$usecuda" = "y" ] || [ "$usecuda" = "Y" ]; then
export PATH=/usr/local/cuda/bin:$PATH
export LD_LIBRARY_PATH=/usr/local/cuda/lib64:$LD_LIBRARY_PATH
fi
fi
echo "-----------------------------------"
if ! [ -x "$(command -v opencv_version)" ]; then
echo "You need to install OpenCV with CUDA first."
echo "inside the Shinobi directory run the following :"
echo "sh INSTALL/opencv-cuda.sh"
exit 1
else
echo "OpenCV found... : $(opencv_version)"
fi
echo "============="
echo "Shinobi - Do you want to Install Tiny Weights?"
echo "This is better for Graphics Cards with less than 4GB RAM"
echo "(y)es or (N)o"
weightNameExtension=""
read tinyweights
if [ "$tinyweights" = "y" ] || [ "$tinyweights" = "Y" ]; then
weightNameExtension="-tiny"
fi
echo "-----------------------------------"
if [ ! -d "models" ]; then
echo "Downloading yolov3 weights..."
mkdir models
wget -O models/yolov3.weights https://pjreddie.com/media/files/yolov3$weightNameExtension.weights
else
echo "yolov3 weights found..."
fi
echo "-----------------------------------"
if [ ! -d "models/cfg" ]; then
echo "Downloading yolov3 cfg"
mkdir models/cfg
wget -O models/cfg/coco.data https://raw.githubusercontent.com/pjreddie/darknet/master/cfg/coco.data
wget -O models/cfg/yolov3.cfg https://raw.githubusercontent.com/pjreddie/darknet/master/cfg/yolov3$weightNameExtension.cfg
else
echo "yolov3 cfg found..."
fi
echo "-----------------------------------"
if [ ! -d "models/data" ]; then
echo "Downloading yolov3 data"
mkdir models/data
wget -O models/data/coco.names https://raw.githubusercontent.com/pjreddie/darknet/master/data/coco.names
else
echo "yolov3 data found..."
fi
echo "-----------------------------------"
if [ ! -e "./conf.json" ]; then
echo "Creating conf.json"
sudo cp conf.sample.json conf.json
else
echo "conf.json already exists..."
fi
echo "-----------------------------------"
if [ -f /etc/redhat-release ]; then
yum update
yum install imagemagick -y
fi
if [ -f /etc/lsb-release ]; then
apt update -y
apt install imagemagick -y
fi
echo "-----------------------------------"
echo "Getting node-gyp to build C++ modules"
npm install node-gyp -g --unsafe-perm
echo "-----------------------------------"
echo "Getting C++ module : node-yolo-shinobi"
echo "https://www.npmjs.com/package/node-yolo-shinobi is a fork of https://github.com/rcaceiro/node-yolo"
npm install --unsafe-perm
npm install node-yolo-shinobi --unsafe-perm
npm audit fix --force
echo "-----------------------------------"
echo "Start the plugin with pm2 like so :"
echo "pm2 start shinobi-yolo.js"
echo "-----------------------------------"
echo "Start the plugin without pm2 :"
echo "node shinobi-yolo.js"

70
plugins/yolo/README.md Normal file
View file

@ -0,0 +1,70 @@
# Yolo
**Ubuntu and CentOS only**
Go to the Shinobi directory. **/home/Shinobi** is the default directory.
```
cd /home/Shinobi/plugins/yolo
```
Copy the config file.
```
sh INSTALL.sh
```
Start the plugin.
```
pm2 start shinobi-yolo.js
```
Doing this will reveal options in the monitor configuration. Shinobi does not need to be restarted when a plugin is initiated or stopped.
## Run the plugin as a Host
> The main app (Shinobi) will be the client and the plugin will be the host. The purpose of allowing this method is so that you can use one plugin for multiple Shinobi instances. Allowing you to easily manage connections without starting multiple processes.
Edit your plugins configuration file. Set the `hostPort` **to be different** than the `listening port for camera.js`.
```
nano conf.json
```
Here is a sample of a Host configuration for the plugin.
- `plug` is the name of the plugin corresponding in the main configuration file.
- `https` choose if you want to use SSL or not. Default is `false`.
- `hostPort` can be any available port number. **Don't make this the same port number as Shinobi.** Default is `8082`.
- `type` tells the main application (Shinobi) what kind of plugin it is. In this case it is a detector.
```
{
"plug":"Yolo",
"hostPort":8082,
"key":"Yolo123123",
"mode":"host",
"type":"detector"
}
```
Now modify the **main configuration file** located in the main directory of Shinobi.
```
nano conf.json
```
Add the `plugins` array if you don't already have it. Add the following *object inside the array*.
```
"plugins":[
{
"id" : "Yolo",
"https" : false,
"host" : "localhost",
"port" : 8082,
"key" : "Yolo123123",
"mode" : "host",
"type" : "detector"
}
],
```

View file

@ -0,0 +1,8 @@
{
"plug":"Yolo",
"host":"localhost",
"port":8080,
"key":"Yolo123123",
"mode":"client",
"type":"detector"
}

20
plugins/yolo/package.json Normal file
View file

@ -0,0 +1,20 @@
{
"name": "shinobi-yolo",
"version": "2.0.0",
"description": "YoloV3 plugin for Shinobi that uses C++ functions for detection.",
"main": "shinobi-yolo.js",
"dependencies": {
"socket.io-client": "^1.7.4",
"express": "^4.16.2",
"moment": "^2.19.2",
"socket.io": "^2.0.4",
"imagickal": "^4.0.0",
"node-yolo-shinobi": "^2.0.3"
},
"devDependencies": {},
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "Moe Alam",
"license": "ISC"
}

View file

@ -0,0 +1,93 @@
//
// Shinobi - Yolo Plugin
// Copyright (C) 2016-2025 Moe Alam, moeiscool
//
// # Donate
//
// If you like what I am doing here and want me to continue please consider donating :)
// PayPal : paypal@m03.ca
//
// Base Init >>
var fs = require('fs');
var config = require('./conf.json')
var s
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.')
}
}
// Base Init />>
var yolo = require('node-yolo-shinobi');//this is @vapi/node-yolo@1.2.4 without the console output for detection speed
// var yolo = require('@vapi/node-yolo');
var detector = new yolo(__dirname + "/models", "cfg/coco.data", "cfg/yolov3.cfg", "yolov3.weights");
s.detectObject=function(buffer,d,tx,frameLocation){
var detectStuff = function(frame,callback){
detector.detect(frame)
.then(detections => {
matrices = []
detections.forEach(function(v){
matrices.push({
x:v.box.x,
y:v.box.y,
width:v.box.w,
height:v.box.h,
tag:v.className,
confidence:v.probability,
})
})
if(matrices.length > 0){
tx({
f:'trigger',
id:d.id,
ke:d.ke,
details:{
plug:config.plug,
name:'yolo',
reason:'object',
matrices:matrices,
imgHeight:parseFloat(d.mon.detector_scale_y),
imgWidth:parseFloat(d.mon.detector_scale_x)
}
})
}
fs.unlink(frame,function(){
})
})
.catch(error => {
console.log(error)
// here you can handle the errors. Ex: Out of memory
})
}
if(frameLocation){
detectStuff(frameLocation)
}else{
d.tmpFile=s.gid(5)+'.jpg'
if(!fs.existsSync(s.dir.streams)){
fs.mkdirSync(s.dir.streams);
}
d.dir=s.dir.streams+d.ke+'/'
if(!fs.existsSync(d.dir)){
fs.mkdirSync(d.dir);
}
d.dir=s.dir.streams+d.ke+'/'+d.id+'/'
if(!fs.existsSync(d.dir)){
fs.mkdirSync(d.dir);
}
fs.writeFile(d.dir+d.tmpFile,buffer,function(err){
if(err) return s.systemLog(err);
try{
detectStuff(d.dir+d.tmpFile)
}catch(error){
console.error('Catch: ' + error);
}
})
}
}