From 68af947d8296582f281daf0fcd563d56f3ec5d6f Mon Sep 17 00:00:00 2001 From: Moe Date: Tue, 19 Jun 2018 23:00:11 -0700 Subject: [PATCH] coco dnn plugin (OpenCL only) --- plugins/dnnCoco/.gitignore | 2 + plugins/dnnCoco/INSTALL.sh | 19 + plugins/dnnCoco/conf.sample.json | 9 + plugins/dnnCoco/dnnCocoClassNames.js | 83 +++++ plugins/dnnCoco/openalpr.conf | 94 +++++ plugins/dnnCoco/shinobi-coco.js | 525 +++++++++++++++++++++++++++ 6 files changed, 732 insertions(+) create mode 100644 plugins/dnnCoco/.gitignore create mode 100644 plugins/dnnCoco/INSTALL.sh create mode 100644 plugins/dnnCoco/conf.sample.json create mode 100644 plugins/dnnCoco/dnnCocoClassNames.js create mode 100644 plugins/dnnCoco/openalpr.conf create mode 100644 plugins/dnnCoco/shinobi-coco.js diff --git a/plugins/dnnCoco/.gitignore b/plugins/dnnCoco/.gitignore new file mode 100644 index 00000000..342a4b13 --- /dev/null +++ b/plugins/dnnCoco/.gitignore @@ -0,0 +1,2 @@ +conf.json +data \ No newline at end of file diff --git a/plugins/dnnCoco/INSTALL.sh b/plugins/dnnCoco/INSTALL.sh new file mode 100644 index 00000000..476b40b1 --- /dev/null +++ b/plugins/dnnCoco/INSTALL.sh @@ -0,0 +1,19 @@ +#!/bin/bash +mkdir data +chmod -R 777 data +wget https://cdn.shinobi.video/weights/dnnCocoData.zip -O dnnCocoData.zip +unzip dnnCocoData.zip -d data +if [ $(dpkg-query -W -f='${Status}' opencv_version 2>/dev/null | grep -c "ok installed") -eq 0 ]; then + echo "Shinobi - Do ypu want to let the `opencv4nodejs` npm package install OpenCV? " + echo "Only do this if you do not have OpenCV already or will not use a GPU (Hardware Acceleration)." + echo "(y)es or (N)o" + read nodejsinstall + if [ "$nodejsinstall" = "y" ] || [ "$nodejsinstall" = "Y" ]; then + export OPENCV4NODEJS_DISABLE_AUTOBUILD=0 + else + export OPENCV4NODEJS_DISABLE_AUTOBUILD=1 + fi +else + export OPENCV4NODEJS_DISABLE_AUTOBUILD=1 +fi +npm install opencv4nodejs moment express canvas@1.6 --unsafe-perm \ No newline at end of file diff --git a/plugins/dnnCoco/conf.sample.json b/plugins/dnnCoco/conf.sample.json new file mode 100644 index 00000000..831ea794 --- /dev/null +++ b/plugins/dnnCoco/conf.sample.json @@ -0,0 +1,9 @@ +{ + "plug":"Coco", + "host":"localhost", + "port":8080, + "hostPort":8082, + "key":"change_this_to_something_very_random____make_sure_to_match__/plugins/opencv/conf.json", + "mode":"client", + "type":"detector" +} \ No newline at end of file diff --git a/plugins/dnnCoco/dnnCocoClassNames.js b/plugins/dnnCoco/dnnCocoClassNames.js new file mode 100644 index 00000000..6c0e980c --- /dev/null +++ b/plugins/dnnCoco/dnnCocoClassNames.js @@ -0,0 +1,83 @@ +module.exports = [ + 'background', + 'person', + 'bicycle', + 'car', + 'motorcycle', + 'airplane', + 'bus', + 'train', + 'truck', + 'boat', + 'traffic light', + 'fire hydrant', + 'stop sign', + 'parking meter', + 'bench', + 'bird', + 'cat', + 'dog', + 'horse', + 'sheep', + 'cow', + 'elephant', + 'bear', + 'zebra', + 'giraffe', + 'backpack', + 'umbrella', + 'handbag', + 'tie', + 'suitcase', + 'frisbee', + 'skis', + 'snowboard', + 'sports ball', + 'kite', + 'baseball bat', + 'baseball glove', + 'skateboard', + 'surfboard', + 'tennis racket', + 'bottle', + 'wine glass', + 'cup', + 'fork', + 'knife', + 'spoon', + 'bowl', + 'banana', + 'apple', + 'sandwich', + 'orange', + 'broccoli', + 'carrot', + 'hot dog', + 'pizza', + 'donut', + 'cake', + 'chair', + 'couch', + 'potted plant', + 'bed', + 'dining table', + 'toilet', + 'tv', + 'laptop', + 'mouse', + 'remote', + 'keyboard', + 'cell phone', + 'microwave', + 'oven', + 'toaster', + 'sink', + 'refrigerator', + 'book', + 'clock', + 'vase', + 'scissors', + 'teddy bear', + 'hair drier', + 'toothbrush' +]; diff --git a/plugins/dnnCoco/openalpr.conf b/plugins/dnnCoco/openalpr.conf new file mode 100644 index 00000000..070752b1 --- /dev/null +++ b/plugins/dnnCoco/openalpr.conf @@ -0,0 +1,94 @@ + +; Specify the path to the runtime data directory +runtime_dir = ${CMAKE_INSTALL_PREFIX}/share/openalpr/runtime_data + + +ocr_img_size_percent = 1.33333333 +state_id_img_size_percent = 2.0 + +; Calibrating your camera improves detection accuracy in cases where vehicle plates are captured at a steep angle +; Use the openalpr-utils-calibrate utility to calibrate your fixed camera to adjust for an angle +; Once done, update the prewarp config with the values obtained from the tool +prewarp = + +; detection will ignore plates that are too large. This is a good efficiency technique to use if the +; plates are going to be a fixed distance away from the camera (e.g., you will never see plates that fill +; up the entire image +max_plate_width_percent = 100 +max_plate_height_percent = 100 + +; detection_iteration_increase is the percentage that the LBP frame increases each iteration. +; It must be greater than 1.0. A value of 1.01 means increase by 1%, 1.10 increases it by 10% each time. +; So a 1% increase would be ~10x slower than 10% to process, but it has a higher chance of landing +; directly on the plate and getting a strong detection +detection_iteration_increase = 1.1 + +; The minimum detection strength determines how sure the detection algorithm must be before signaling that +; a plate region exists. Technically this corresponds to LBP nearest neighbors (e.g., how many detections +; are clustered around the same area). For example, 2 = very lenient, 9 = very strict. +detection_strictness = 3 + +; The detection doesn't necessarily need an extremely high resolution image in order to detect plates +; Using a smaller input image should still find the plates and will do it faster +; Tweaking the max_detection_input values will resize the input image if it is larger than these sizes +; max_detection_input_width/height are specified in pixels +max_detection_input_width = 1280 +max_detection_input_height = 720 + +; detector is the technique used to find license plate regions in an image. Value can be set to +; lbpcpu - default LBP-based detector uses the system CPU +; lbpgpu - LBP-based detector that uses Nvidia GPU to increase recognition speed. +; lbpopencl - LBP-based detector that uses OpenCL GPU to increase recognition speed. Requires OpenCV 3.0 +; morphcpu - Experimental detector that detects white rectangles in an image. Does not require training. +detector = lbpgpu + +; If set to true, all results must match a postprocess text pattern if a pattern is available. +; If not, the result is disqualified. +must_match_pattern = 0 + +; Bypasses plate detection. If this is set to 1, the library assumes that each region provided is a likely plate area. +skip_detection = 0 + +; Specifies the full path to an image file that constrains the detection area. Only the plate regions allowed through the mask +; will be analyzed. The mask image must match the resolution of your image to be analyzed. The mask is black and white. +; Black areas will be ignored, white areas will be searched. An empty value means no mask (scan the entire image) +detection_mask_image = + +; OpenALPR can scan the same image multiple times with different randomization. Setting this to a value larger than +; 1 may increase accuracy, but will increase processing time linearly (e.g., analysis_count = 3 is 3x slower) +analysis_count = 1 + +; OpenALPR detects high-contrast plate crops and uses an alternative edge detection technique. Setting this to 0.0 +; would classify ALL images as high-contrast, setting it to 1.0 would classify no images as high-contrast. +contrast_detection_threshold = 0.3 + +max_plate_angle_degrees = 15 + +ocr_min_font_point = 6 + +; Minimum OCR confidence percent to consider. +postprocess_min_confidence = 65 + +; Any OCR character lower than this will also add an equally likely +; chance that the character is incorrect and will be skipped. Value is a confidence percent +postprocess_confidence_skip_level = 80 + + +debug_general = 0 +debug_timing = 0 +debug_detector = 0 +debug_prewarp = 0 +debug_state_id = 0 +debug_plate_lines = 0 +debug_plate_corners = 0 +debug_char_segment = 0 +debug_char_analysis = 0 +debug_color_filter = 0 +debug_ocr = 0 +debug_postprocess = 0 +debug_show_images = 0 +debug_pause_on_frame = 0 + + + + diff --git a/plugins/dnnCoco/shinobi-coco.js b/plugins/dnnCoco/shinobi-coco.js new file mode 100644 index 00000000..6a0a88d5 --- /dev/null +++ b/plugins/dnnCoco/shinobi-coco.js @@ -0,0 +1,525 @@ +// +// Shinobi - OpenCV 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 cv=require('opencv4nodejs'); +var exec = require('child_process').exec; +var moment = require('moment'); +var Canvas = require('canvas'); +var express = require('express'); +const path = require('path'); +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/'} +if(config.alprConfig===undefined){config.alprConfig=__dirname+'/openalpr.conf'} + + +const classNames = require(__dirname+'/dnnCocoClassNames.js'); +const extractResults = function (outputBlob, imgDimensions) { + return Array(outputBlob.rows).fill(0) + .map((res, i) => { + const classLabel = outputBlob.at(i, 1); + const confidence = outputBlob.at(i, 2); + const bottomLeft = new cv.Point( + outputBlob.at(i, 3) * imgDimensions.cols, + outputBlob.at(i, 6) * imgDimensions.rows + ); + const topRight = new cv.Point( + outputBlob.at(i, 5) * imgDimensions.cols, + outputBlob.at(i, 4) * imgDimensions.rows + ); + const rect = new cv.Rect( + bottomLeft.x, + topRight.y, + topRight.x - bottomLeft.x, + bottomLeft.y - topRight.y + ); + + return ({ + classLabel, + confidence, + rect + }); + }); +}; +// replace with path where you unzipped inception model +const ssdcocoModelPath = __dirname+'/data'; + +const prototxt = path.resolve(ssdcocoModelPath, 'deploy.prototxt'); +const modelFile = path.resolve(ssdcocoModelPath, 'VGG_coco_SSD_300x300_iter_400000.caffemodel'); + +if (!fs.existsSync(prototxt) || !fs.existsSync(modelFile)) { + console.log('could not find ssdcoco model'); + console.log('download the model from: https://cdn.shinobi.video/weights/dnnCocoData.zip'); + throw new Error('exiting: could not find ssdcoco model'); +} + +// initialize ssdcoco model from prototxt and modelFile +const net = cv.readNetFromCaffe(prototxt, modelFile); + +function classifyImg(img) { + // ssdcoco model works with 300 x 300 images + const imgResized = img.resize(300, 300); + + // network accepts blobs as input + const inputBlob = cv.blobFromImage(imgResized); + net.setInput(inputBlob); + + // forward pass input through entire network, will return + // classification result as 1x1xNxM Mat + let outputBlob = net.forward(); + // extract NxM Mat + outputBlob = outputBlob.flattenFloat(outputBlob.sizes[2], outputBlob.sizes[3]); + + return extractResults(outputBlob, img) + .map(r => Object.assign({}, r.rect, { confidence : r.confidence, className: classNames[r.classLabel] })); +} + +const makeDrawClassDetections = predictions => (drawImg, className, getColor, thickness = 2) => { + predictions + .filter(p => classNames[p.classLabel] === className) + .forEach(p => console.log(p)); +}; + + +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(){ + //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) + } + cv.imdecodeAsync(buffer,(err,im) => { + if(err){ + console.log(err) + return + } + + if (!cv.xmodules.dnn) { + throw new Error('exiting: opencv4nodejs compiled without dnn module'); + } + + + const minConfidence = 0.5; + + const predictions = classifyImg(im).filter(res => res.confidence > minConfidence); +// console.log(predictions) + if(predictions.length > 0) { + s.cx({ + f:'trigger', + id:d.id, + ke:d.ke, + name:'coco', + details:{ + plug:'coco', + name:'coco', + reason:'object', + matrices : predictions + // 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�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){ + 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){ + 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; + } +} +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) + }) +}