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

41
INSTALL/freenas.csh Normal file
View file

@ -0,0 +1,41 @@
#!/bin/tcsh
echo "Installing updates..."
pkg update -f
pkg upgrade -y
echo "Installing packages..."
pkg install -y nano ffmpeg libav x264 x265 mysql56-server node npm
echo "Enabling mysql..."
sysrc mysql_enable=yes
service mysql-server start
echo "Cloning the official Shinobi Community Edition gitlab repo..."
git clone "https://gitlab.com/Shinobi-Systems/ShinobiCE"
cd ./ShinobiCE
echo "Adding Shinobi user to database..."
mysql -h localhost -u root -e "source sql/user.sql"
ehco "Shinobi database framework setup..."
mysql -h localhost -u root -e "source sql/framework.sql"
echo "Securing mysql..."
#/usr/local/bin/mysql_secure_installation
#mysql -h localhost -u root -e "source sql/secure_mysql.sq"
npm i npm -g
#There are some errors in here that I don't want you to see. Redirecting to dev null :D
npm install --unsafe-perm > & /dev/null
npm audit fix --force > & /dev/null
npm install pm2 -g
cp conf.sample.json conf.json
cp super.sample.json super.json
pm2 start camera.js
pm2 start cron.js
pm2 save
pm2 list
pm2 startup rcd
echo "====================================="
echo "||===== Install Completed =====||"
echo "====================================="
echo "|| Login with the Superuser and ||"
echo "|| create a new user at ||"
echo "|| http://THIS_JAIL_IP:8080/super ||"
echo "||==================================="
echo "|| Superuser : admin@shinobi.video ||"
echo "|| Default Password : admin ||"
echo "====================================="

View file

@ -25,55 +25,40 @@ fi
#create super.json
if [ ! -e "./super.json" ]; then
echo "============="
echo "Shinobi - Do you want to enable superuser access?"
echo "This may be useful if passwords are forgotten or"
echo "if you would like to limit accessibility of an"
echo "account for business scenarios."
echo "(y)es or (N)o"
read createSuperJson
if [ "$createSuperJson" = "y" ] || [ "$createSuperJson" = "Y" ]; then
echo "Default Superuser : admin@shinobi.video"
echo "Default Password : admin"
echo "* You can edit these settings in \"super.json\" located in the Shinobi directory."
sudo cp super.sample.json super.json
fi
echo "Default Superuser : admin@shinobi.video"
echo "Default Password : admin"
echo "* You can edit these settings in \"super.json\" located in the Shinobi directory."
sudo cp super.sample.json super.json
fi
echo "============="
echo "Shinobi - Do you want to Install Node.js?"
echo "(y)es or (N)o"
read nodejsinstall
if [ "$nodejsinstall" = "y" ] || [ "$nodejsinstall" = "Y" ]; then
wget https://deb.nodesource.com/setup_8.x
chmod +x setup_8.x
./setup_8.x
if ! [ -x "$(command -v node)" ]; then
echo "============="
echo "Shinobi - Installing Node.js"
wget https://deb.nodesource.com/setup_9.x
chmod +x setup_9.x
./setup_9.x
sudo apt install nodejs -y
else
echo "Node.js Found..."
echo "Version : $(node -v)"
fi
if ! [ -x "$(command -v npm)" ]; then
sudo apt install npm -y
fi
sudo apt install make -y
echo "============="
echo "Shinobi - Do you want to Install FFMPEG?"
echo "(y)es or (N)o"
read ffmpeginstall
if [ "$ffmpeginstall" = "y" ] || [ "$ffmpeginstall" = "Y" ]; then
echo "Shinobi - Do you want to Install FFMPEG with apt or download a static version provided with npm?"
echo "(a)pt or (N)pm"
echo "Press [ENTER] for default (npm)"
read ffmpegstaticinstall
if [ "$ffmpegstaticinstall" = "a" ] || [ "$ffmpegstaticinstall" = "A" ]; then
if [ "$getubuntuversion" = "16" ] || [ "$getubuntuversion" < "16" ]; then
echo "============="
echo "Shinobi - Get FFMPEG 3.x from ppa:jonathonf/ffmpeg-3"
sudo add-apt-repository ppa:jonathonf/ffmpeg-3 -y
sudo apt update -y && sudo apt install ffmpeg libav-tools x264 x265 -y
echo "============="
else
echo "============="
echo "Shinobi - Installing FFMPEG"
sudo apt install ffmpeg -y
echo "============="
fi
if ! [ -x "$(command -v ffmpeg)" ]; then
if [ "$getubuntuversion" = "16" ] || [ "$getubuntuversion" < "16" ]; then
echo "============="
echo "Shinobi - Get FFMPEG 3.x from ppa:jonathonf/ffmpeg-3"
sudo add-apt-repository ppa:jonathonf/ffmpeg-3 -y
sudo apt update -y && sudo apt install ffmpeg libav-tools x264 x265 -y
else
sudo npm install ffbinaries
echo "============="
echo "Shinobi - Installing FFMPEG"
sudo apt install ffmpeg -y
fi
else
echo "FFmpeg Found..."
echo "Version : $(ffmpeg -version)"
fi
echo "============="
echo "Shinobi - Do you want to use MariaDB or SQLite3?"
@ -137,15 +122,6 @@ sudo chmod -R 755 .
touch INSTALL/installed.txt
dos2unix /home/Shinobi/INSTALL/shinobi
ln -s /home/Shinobi/INSTALL/shinobi /usr/bin/shinobi
if [ "$mysqlDefaultData" = "y" ] || [ "$mysqlDefaultData" = "Y" ]; then
echo "=====================================" > INSTALL/installed.txt
echo "======= Login Credentials =======" >> INSTALL/installed.txt
echo "|| Username : $userEmail" >> INSTALL/installed.txt
echo "|| Password : $userPasswordPlain" >> INSTALL/installed.txt
echo "|| API Key : $apiKey" >> INSTALL/installed.txt
echo "=====================================" >> INSTALL/installed.txt
echo "=====================================" >> INSTALL/installed.txt
fi
echo "Shinobi - Start Shinobi and set to start on boot?"
echo "(y)es or (N)o"
read startShinobi
@ -156,16 +132,6 @@ if [ "$startShinobi" = "y" ] || [ "$startShinobi" = "y" ]; then
sudo pm2 save
sudo pm2 list
fi
if [ "$mysqlDefaultData" = "y" ] || [ "$mysqlDefaultData" = "Y" ]; then
echo "details written to INSTALL/installed.txt"
echo "====================================="
echo "======= Login Credentials ======="
echo "|| Username : $userEmail"
echo "|| Password : $userPasswordPlain"
echo "|| API Key : $apiKey"
echo "====================================="
echo "====================================="
fi
if [ ! "$sqliteormariadb" = "M" ] && [ ! "$sqliteormariadb" = "m" ]; then
echo "====================================="
echo "||===== Install Completed =====||"

View file

@ -649,6 +649,8 @@
"Thumbnail": "Thumbnail",
"Host Type": "Host Type",
"Edit": "Edit",
"Show Matrices": "Show Matrices",
"Show Matrix": "Show Matrix",
"No Monitor ID Present in Form": "No Monitor ID Present in Form",
"State Configuration has no monitors associated": "State Configuration has no monitors associated",
"State Configuration Not Found": "State Configuration Not Found",

View file

@ -53,7 +53,11 @@ module.exports = function(s,config){
var regions = s.createPamDiffRegionArray(regionJson,globalColorThreshold,globalSensitivity,fullFrame)
s.group[e.ke].mon[e.id].pamDiff = new PamDiff({grayscale: 'luminosity', regions : regions.forPam});
s.group[e.ke].mon[e.id].pamDiff = new PamDiff({
grayscale: 'luminosity',
regions : regions.forPam,
drawMatrix : e.details.detector_show_matrix
});
s.group[e.ke].mon[e.id].p2p = new P2P();
var sendTrigger = function(trigger){
var detectorObject = {

View file

@ -44,6 +44,7 @@ class PamDiff extends Transform {
this.difference = PamDiff._parseOptions('difference', options);//global option, can be overridden per region
this.percent = PamDiff._parseOptions('percent', options);//global option, can be overridden per region
this.regions = PamDiff._parseOptions('regions', options);//can be no regions or a single region or multiple regions. if no regions, all pixels will be compared.
this.drawMatrix = PamDiff._parseOptions('drawMatrix', options);//can be no regions or a single region or multiple regions. if no regions, all pixels will be compared.
this.callback = callback;//callback function to be called when pixel difference is detected
this._parseChunk = this._parseFirstChunk;//first parsing will be reading settings and configuring internal pixel reading
}
@ -331,7 +332,7 @@ class PamDiff extends Transform {
* @param chunk
* @private
*/
_grayScalePixelDiff(chunk) {
_grayScalePixelDiffWithMatrices(chunk) {
this._newPix = chunk.pixels;
for (let j = 0; j < this._regionsLength; j++) {
this._regions[j].topLeft = {
@ -430,6 +431,71 @@ class PamDiff extends Transform {
this._oldPix = this._newPix;
}
/**
*
* @param chunk
* @private
*/
_grayScalePixelDiff(chunk) {
this._newPix = chunk.pixels;
for (let y = 0, i = 0; y < this._height; y++) {
for (let x = 0; x < this._width; x++, i++) {
if (this._oldPix[i] !== this._newPix[i]) {
const diff = Math.abs(this._oldPix[i] - this._newPix[i]);
if (this._regions && diff >= this._minDiff) {
for (let j = 0; j < this._regionsLength; j++) {
if (this._pointsInPolygons[j][i] && diff >= this._regions[j].difference) {
this._regions[j].diffs++;
}
}
} else {
if (diff >= this._difference) {
this._diffs++;
}
}
}
}
}
if (this._regions) {
const regionDiffArray = [];
for (let i = 0; i < this._regionsLength; i++) {
const percent = Math.floor(100 * this._regions[i].diffs / this._regions[i].pointsLength);
if (percent >= this._regions[i].percent) {
regionDiffArray.push({name: this._regions[i].name, percent: percent});
}
this._regions[i].diffs = 0;
}
if (regionDiffArray.length > 0) {
const data = {trigger: regionDiffArray, pam: chunk.pam};
if (this._callback) {
this._callback(data);
}
if (this._readableState.pipesCount > 0) {
this.push(data);
}
if (this.listenerCount('diff') > 0) {
this.emit('diff', data);
}
}
} else {
const percent = Math.floor(100 * this._diffs / this._length);
if (percent >= this._percent) {
const data = {trigger: [{name: 'percent', percent: percent}], pam: chunk.pam};
if (this._callback) {
this._callback(data);
}
if (this._readableState.pipesCount > 0) {
this.push(data);
}
if (this.listenerCount('diff') > 0) {
this.emit('diff', data);
}
}
this._diffs = 0;
}
this._oldPix = this._newPix;
}
/**
*
* @param chunk
@ -576,7 +642,11 @@ class PamDiff extends Transform {
this._parseChunk = this._blackAndWhitePixelDiff;
break;
case 'grayscale' :
this._parseChunk = this._grayScalePixelDiff;
if(this.drawMatrix === "1"){
this._parseChunk = this._grayScalePixelDiffWithMatrices;
}else{
this._parseChunk = this._grayScalePixelDiff;
}
break;
case 'rgb' :
this._parseChunk = this._rgbPixelDiff;

View file

@ -59,14 +59,14 @@ module.exports = function(s,config,lang,io){
var tx;
//set "client" detector plugin event function
cn.on('ocv',function(d){
if(!cn.pluginEngine&&d.f==='init'){
if(config.pluginKeys[d.plug]===d.pluginKey){
if(!cn.pluginEngine && d.f === 'init'){
if(config.pluginKeys[d.plug] === d.pluginKey){
s.pluginInitiatorSuccess("client",d,cn)
}else{
s.pluginInitiatorFail("client",d,cn)
}
}else{
if(config.pluginKeys[d.plug]===d.pluginKey){
if(config.pluginKeys[d.plug] === d.pluginKey){
s.pluginEventController(d)
}else{
cn.disconnect()

View file

@ -364,6 +364,36 @@ module.exports = function(s,config,lang,app){
},res,req)
})
/**
* API : Administrator : Get Monitor State Presets List
*/
app.get([
config.webPaths.apiPrefix+':auth/monitorStates/:ke',
config.webPaths.adminApiPrefix+':auth/monitorStates/:ke'
],function (req,res){
s.auth(req.params,function(user){
var endData = {
ok : false
}
if(user.details.sub){
endData.msg = user.lang['Not Permitted']
s.closeJsonResponse(res,endData)
return
}
s.sqlQuery("SELECT * FROM Presets WHERE ke=? AND type=?",[req.params.ke,'monitorStates'],function(err,presets){
if(presets && presets[0]){
endData.ok = true
presets.forEach(function(preset){
preset.details = JSON.parse(preset.details)
})
endData.presets = presets
}else{
endData.msg = user.lang['State Configuration Not Found']
}
s.closeJsonResponse(res,endData)
})
})
})
/**
* API : Administrator : Change Group Preset. Currently affects Monitors only.
*/
app.all([

View file

@ -18,6 +18,7 @@ module.exports = function(s,config,lang,app,io){
s.renderPage = function(req,res,paths,passables,callback){
passables.window = {}
passables.originalURL = s.getOriginalUrl(req)
passables.config = config
res.render(paths,passables,callback)
}
//child node proxy check

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);
}
})
}
}

View file

@ -4710,7 +4710,7 @@ $.timelapse.f.submit(function(e){
$.timelapse.drawTimeline=function(getData){
var e={};
if(getData===undefined){getData=true}
var mid=$.timelapse.monitors.val();
var mid = $.timelapse.monitors.val()
e.dateRange=$.timelapse.dr.data('daterangepicker');
e.dateRange={startDate:e.dateRange.startDate,endDate:e.dateRange.endDate}
e.videoURL=$.ccio.init('location',$user)+$user.auth_token+'/videos/'+$user.ke+'/'+mid;