diff --git a/plugins/yolo/INSTALL.sh b/plugins/yolo/INSTALL.sh new file mode 100644 index 00000000..5c57b63c --- /dev/null +++ b/plugins/yolo/INSTALL.sh @@ -0,0 +1,75 @@ +#!/bin/bash +echo "-----------------------------------------------" +echo "-- Installing Yolo Plugin for Shinobi --" +echo "-----------------------------------------------" +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/cuda9-part1.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/cuda9-part2-after-reboot.sh" + exit 1 +else + echo "CUDA Toolkit found..." +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 "-----------------------------------" +if [ ! -d "models" ]; then + echo "Downloading yolov3 weights..." + mkdir models + wget -O models/yolov3.weights https://pjreddie.com/media/files/yolov3-tiny.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-tiny.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 +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 "Start the plugin with pm2 like so :" +echo "pm2 start shinobi-yolo.js" diff --git a/plugins/yolo/README.md b/plugins/yolo/README.md new file mode 100644 index 00000000..8e4b1650 --- /dev/null +++ b/plugins/yolo/README.md @@ -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. *Where you currently should be.* + +``` +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" + } + ], +``` diff --git a/plugins/yolo/conf.sample.json b/plugins/yolo/conf.sample.json new file mode 100644 index 00000000..4cac6fcf --- /dev/null +++ b/plugins/yolo/conf.sample.json @@ -0,0 +1,8 @@ +{ + "plug":"Yolo", + "host":"localhost", + "port":8080, + "key":"Yolo123123", + "mode":"client", + "type":"detector" +} diff --git a/plugins/yolo/shinobi-yolo.js b/plugins/yolo/shinobi-yolo.js new file mode 100644 index 00000000..69f99ac9 --- /dev/null +++ b/plugins/yolo/shinobi-yolo.js @@ -0,0 +1,244 @@ +// +// 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 +// +process.on('uncaughtException', function (err) { + console.error('uncaughtException',err); +}); +var fs=require('fs'); +var yolo = require('@vapi/node-yolo'); +var exec = require('child_process').exec; +var moment = require('moment'); +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} +if(config.cascadesDir===undefined){config.cascadesDir=__dirname+'/cascades/'} +var detector = new yolo(__dirname + "/models", "cfg/coco.data", "cfg/yolov3.cfg", "yolov3.weights"); +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.detectObject=function(buffer,d,tx){ + d.tmpFile=s.gid(5)+'.jpg' + d.tmpFile2=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{ + detector.detect(d.dir+d.tmpFile) + .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){ + console.log(detections) + 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(d.dir+d.tmpFile,function(){ + + }) + }) + .catch(error => { + console.log(error) + + // here you can handle the errors. Ex: Out of memory + }) + }catch(error){ + console.error('Catch: ' + error); + } + }) +} +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.MainEventController=function(d,cn,tx){ + switch(d.f){ + case'refreshPlugins': + s.findCascades(function(cascades){ + s.cx({f:'s.tx',data:{f:'detector_cascade_list',cascades:cascades},to:'GRP_'+d.ke}) + }) + break; + 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 + } + 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){ + 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; + } +} +server.listen(config.hostPort); +//web pages and plugin api +app.get('/', function (req, res) { + res.end(''+config.plug+' 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) + }) +}