Contour Detection Plugin

merge-requests/25/head
Moe 2018-09-25 10:00:52 -07:00
parent b7a3317aac
commit 20733b3862
8 changed files with 577 additions and 0 deletions

3
plugins/python-contour/.gitignore vendored Normal file
View File

@ -0,0 +1,3 @@
conf.json
faces
data

View File

@ -0,0 +1,59 @@
#!/bin/bash
echo "-----------------------------------------------"
echo "-- Installing Python Dlib Plugin for Shinobi --"
echo "-----------------------------------------------"
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 "-----------------------------------"
sudo apt update -y
echo "Installing python3"
sudo apt install python3 python3-dev python3-pip -y
echo "-----------------------------------"
sudo add-apt-repository ppa:ubuntu-toolchain-r/test -y
sudo apt update
sudo apt-get install gcc-6 g++-6 -y && sudo update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-6 60 --slave /usr/bin/g++ g++ /usr/bin/g++-6
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 "-----------------------------------"
echo "Getting new pip..."
pip3 install --upgrade pip
pip install --user --upgrade pip
export PATH=/usr/local/cuda/bin:$PATH
echo "Smoking pips..."
pip3 install flask_socketio
pip3 install flask
pip3 install numpy
pip3 install gevent gevent-websocket
echo "Start the plugin with pm2 like so :"
echo "pm2 start shinobi-python-dlib.js"

View File

@ -0,0 +1,72 @@
# Python Contour Detection with OpenCV
> This plugin requires the use of port `7990` by default. You can specify a different port by adding `pythonPort` to your plugin's conf.json.
**Ubuntu and Debian only**
Go to the Shinobi directory. **/home/Shinobi** is the default directory.
```
cd /home/Shinobi/plugins/python-contour
```
Copy the config file.
```
sh INSTALL.sh
```
Start the plugin.
```
pm2 start shinobi-python-contour.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":"PythonContour",
"hostPort":8082,
"key":"SomeOpenALPRkeySoPeopleDontMessWithYourShinobi",
"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" : "PythonContour",
"https" : false,
"host" : "localhost",
"port" : 8082,
"key" : "SomeOpenALPRkeySoPeopleDontMessWithYourShinobi",
"mode" : "host",
"type" : "detector"
}
],
```

View File

@ -0,0 +1 @@
python3 -u $@

View File

@ -0,0 +1,10 @@
{
"plug":"PythonContour",
"host":"localhost",
"port":8080,
"pythonPort":7990,
"hostPort":8082,
"key":"YOUR_CONTOUR_PLUGIN_KEY",
"mode":"client",
"type":"detector"
}

View File

@ -0,0 +1,18 @@
{
"name": "shinobi-python-contour",
"version": "1.0.0",
"description": "Contour plugin for Shinobi that uses Python functions for detection.",
"main": "shinobi-python-contour.js",
"dependencies": {
"socket.io-client": "^1.7.4",
"express": "^4.16.2",
"moment": "^2.19.2",
"socket.io": "^2.0.4"
},
"devDependencies": {},
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "Moe Alam",
"license": "ISC"
}

View File

@ -0,0 +1,116 @@
from flask import Flask, request, jsonify, render_template
from flask_socketio import SocketIO, emit
import cv2
import os
import json
import numpy as np
import sys
dirname = sys.argv[1]
try:
with open("{}/conf.json".format(dirname)) as json_file:
config = json.load(json_file)
httpPort = config['pythonPort']
try:
httpPort
except NameError:
httpPort = 7990
except Exception as e:
print("conf.json not found.")
httpPort = 7990
# Load Flask
app = Flask("Contour Detection for Shinobi (Pumpkin Pie)")
socketio = SocketIO(app)
# Silence Flask
# import logging
# log = logging.getLogger('werkzeug')
# log.setLevel(logging.ERROR)
#load car detector
oldFrames = {}
fgbg = cv2.createBackgroundSubtractorMOG2()
# detection function
def spark(filepath,trackerId):
try:
filepath
except NameError:
return "File path not found."
frame = cv2.imread(filepath)
returnData = []
# resize the frame, convert it to grayscale, and blur it
# frame = imutils.resize(frame, width=500)
gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
gray = cv2.GaussianBlur(gray, (21, 21), 0)
# if the first frame is None, initialize it
global oldFrames
try:
oldFrames[trackerId]
except KeyError:
oldFrames[trackerId] = None
if oldFrames[trackerId] is None:
oldFrames[trackerId] = gray
# compute the absolute difference between the current frame and
# first frame
frameDelta = cv2.absdiff(oldFrames[trackerId], gray)
thresh = cv2.threshold(frameDelta, 55, 255, cv2.THRESH_BINARY)[1]
# dilate the thresholded image to fill in holes, then find contours
# on thresholded image
thresh = cv2.dilate(thresh, None, iterations=2)
image = thresh.copy()
image,cnts,hierarchy = cv2.findContours(image, cv2.RETR_EXTERNAL,cv2.CHAIN_APPROX_SIMPLE)
# loop over the contours
for c in cnts:
# if the contour is too small, ignore it
#if cv2.contourArea(c) > args["max_area"] or cv2.contourArea < args["min_area"]:
# continue
d = max(cnts, key = cv2.contourArea)
# compute the bounding box for the contour, draw it on the frame,
# and update the text
(x, y, w, h) = cv2.boundingRect(d)
matrix = {}
matrix["tag"] = "Contour"
matrix["x"] = int(x)
matrix["y"] = int(y)
matrix["w"] = int(w)
matrix["h"] = int(h)
returnData.append(matrix)
return returnData
# bake the image data by a file path
# POST body contains the "img" variable. The value should be to a local image path.
# Example : /dev/shm/streams/[GROUP_KEY]/[MONITOR_ID]/s.jpg
@app.route('/', methods=['GET'])
def index():
return "Pumpkin.py is running. This web interface should NEVER be accessible remotely. The Node.js plugin that runs this script should only be allowed accessible remotely."
# bake the image data by a file path
# POST body contains the "img" variable. The value should be to a local image path.
# Example : /dev/shm/streams/[GROUP_KEY]/[MONITOR_ID]/s.jpg
@app.route('/post', methods=['POST'])
def post():
filepath = request.form['img']
return jsonify(spark(filepath))
# bake the image data by a file path
# GET string contains the "img" variable. The value should be to a local image path.
# Example : /dev/shm/streams/[GROUP_KEY]/[MONITOR_ID]/s.jpg
@app.route('/get', methods=['GET'])
def get():
filepath = request.args.get('img')
return jsonify(spark(filepath))
@socketio.on('f')
def receiveMessage(message):
emit('f',{'id':message.get("id"),'data':spark(message.get("path"),message.get("trackerId"))})
# quick-and-dirty start
if __name__ == '__main__':
socketio.run(app, port=httpPort)

View File

@ -0,0 +1,298 @@
//
// Shinobi - Python 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
//
process.on('uncaughtException', function (err) {
console.error('uncaughtException',err);
});
//main vars
var fs=require('fs');
var exec = require('child_process').exec;
var spawn = require('child_process').spawn;
var moment = require('moment');
var http = require('http');
var express = require('express');
var socketIoClient = require('socket.io-client');
var config = require('./conf.json');
var http = require('http'),
app = express(),
server = http.createServer(app);
exec("kill $(ps aux | grep '[p]ython3 pumpkin.py' | awk '{print $2}')")
s={
group:{},
dir:{},
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+'/'
}
return x.replace('__DIR__',__dirname)
}
s.debugLog = function(){
if(config.debugLog === true){
console.log(new Date(),arguments)
if(config.debugLogVerbose === true){
console.log(new Error())
}
}
}
if(!config.port){config.port=8080}
if(!config.pythonScript){config.pythonScript=__dirname+'/pumpkin.py'}
if(!config.pythonPort){config.pythonPort=7990}
if(!config.hostPort){config.hostPort=8082}
if(config.systemLog===undefined){config.systemLog=true}
//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.getRequest = function(url,callback){
return http.get(url, function(res){
var body = '';
res.on('data', function(chunk){
body += chunk;
});
res.on('end',function(){
try{body = JSON.parse(body)}catch(err){}
callback(body)
});
}).on('error', function(e){
// s.systemLog("Get Snapshot Error", e);
});
}
s.multiplerHeight = 1
s.multiplerWidth = 1
s.detectObject=function(buffer,d,tx){
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);
if(s.isPythonRunning === false){
return console.log('Python Script is not Running.')
}
var callbackId = s.gid(10)
s.group[d.ke][d.id].sendToPython({path:d.dir+d.tmpFile,id:callbackId,trackerId:d.ke+d.id},function(data){
if(data.length > 0){
var mats=[]
data.forEach(function(v){
mats.push({
x:v.x,
y:v.y,
width: v.w,
height: v.h,
confidence:v.confidence,
tag:v.tag
})
})
tx({
f:'trigger',
id:d.id,
ke:d.ke,
details:{
plug:config.plug,
name:'dlib',
reason:'object',
matrices:mats,
imgHeight:parseFloat(d.mon.detector_scale_y),
imgWidth:parseFloat(d.mon.detector_scale_x)
}
})
}
delete(s.callbacks[callbackId])
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.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]){
delete(s.group[d.ke][d.id].buffer)
s.group[d.ke][d.id].refreshTracker(d.ke+d.id)
}
break;
case'frame':
try{
if(!s.group[d.ke]){
s.group[d.ke]={}
}
if(!s.group[d.ke][d.id]){
var engine = s.createCameraBridgeToPython(d.ke+d.id)
s.group[d.ke][d.id]={
sendToPython : engine.sendToPython,
refreshTracker : engine.refreshTracker
}
}
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.detectObject(Buffer.concat(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;
}
}
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 = socketIoClient('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)
})
}
//Start Python Controller
s.callbacks = {}
s.createCameraBridgeToPython = function(uniqueId){
var pythonIo = socketIoClient('ws://localhost:'+config.pythonPort,{transports : ['websocket']});
var sendToPython = function(data,callback){
s.callbacks[data.id] = callback
pythonIo.emit('f',data)
}
var refreshTracker = function(data){
pythonIo.emit('refreshTracker',{trackerId : data})
}
pythonIo.on('connect',function(d){
s.debugLog(uniqueId+' is Connected from Python')
})
pythonIo.on('disconnect',function(d){
s.debugLog(uniqueId+' is Disconnected from Python')
setTimeout(function(){
pythonIo.connect();
s.debugLog(uniqueId+' is Attempting to Reconect to Python')
},3000)
})
pythonIo.on('f',function(d){
if(s.callbacks[d.id]){
s.callbacks[d.id](d.data)
delete(s.callbacks[d.id])
}
})
return {refreshTracker : refreshTracker, sendToPython : sendToPython}
}
//Start Python Daemon
process.env.PYTHONUNBUFFERED = 1;
s.createPythonProcess = function(){
s.isPythonRunning = false
s.pythonScript = spawn('sh',[__dirname+'/bootPy.sh',config.pythonScript,__dirname]);
var onStdErr = function(data){
s.debugLog(data.toString())
}
var onStdOut = function(data){
s.debugLog(data.toString())
}
setTimeout(function(){
s.isPythonRunning = true
},5000)
s.pythonScript.stderr.on('data',onStdErr);
s.pythonScript.stdout.on('data',onStdOut);
s.pythonScript.on('close', function () {
s.debugLog('Python CLOSED')
});
}
s.createPythonProcess()