mirror of
https://gitlab.com/Shinobi-Systems/ShinobiCE.git
synced 2025-03-09 15:40:15 +00:00
Shinobi CE officially lands on Gitlab
This commit is contained in:
commit
f1406d4eec
431 changed files with 118157 additions and 0 deletions
1
plugins/motion/.gitignore
vendored
Normal file
1
plugins/motion/.gitignore
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
conf.json
|
5
plugins/motion/INSTALL.sh
Normal file
5
plugins/motion/INSTALL.sh
Normal file
|
@ -0,0 +1,5 @@
|
|||
apt-get install libcairo2-dev libjpeg-dev libpango1.0-dev libgif-dev build-essential g++
|
||||
npm install canvas
|
||||
cd plugins/motion
|
||||
cp conf.sample.json conf.json
|
||||
pm2 start shinobi-motion.js
|
56
plugins/motion/README.md
Normal file
56
plugins/motion/README.md
Normal file
|
@ -0,0 +1,56 @@
|
|||
# Shinobi Motion Detector
|
||||
|
||||
Install required libraries.
|
||||
|
||||
**Ubuntu and Debian only**
|
||||
|
||||
```
|
||||
sudo apt-get install libcairo2-dev libjpeg-dev libpango1.0-dev libgif-dev build-essential g++
|
||||
```
|
||||
|
||||
**CentOS only**
|
||||
|
||||
```
|
||||
su -c 'yum install cairo cairo-devel cairomm-devel libjpeg-turbo-devel pango pango-devel pangomm pangomm-devel giflib-devel'
|
||||
yum search arial
|
||||
yum install liberation-sans-fonts.noarch
|
||||
```
|
||||
|
||||
**Install the Node.js Canvas engine**
|
||||
|
||||
```
|
||||
sudo npm install canvas
|
||||
```
|
||||
|
||||
Go to the Shinobi directory. **Below is an example.**
|
||||
|
||||
```
|
||||
cd /home/Shinobi
|
||||
```
|
||||
|
||||
Copy the config file.
|
||||
|
||||
```
|
||||
cp plugins/motion/conf.sample.json plugins/motion/conf.json
|
||||
```
|
||||
|
||||
Edit it the new file. Host should be `localhost` and port should match the `listening port for camera.js`.
|
||||
|
||||
```
|
||||
nano plugins/motion/conf.json
|
||||
```
|
||||
|
||||
Start the plugin.
|
||||
|
||||
```
|
||||
node plugins/motion/shinobi-motion.js
|
||||
```
|
||||
|
||||
Or to daemonize with PM2.
|
||||
|
||||
```
|
||||
pm2 start plugins/motion/shinobi-motion.js
|
||||
```
|
||||
|
||||
Doing this will reveal options in the monitor configuration. Shinobi does not need to be restarted when a plugin is initiated or stopped.
|
||||
|
7
plugins/motion/conf.sample.json
Normal file
7
plugins/motion/conf.sample.json
Normal file
|
@ -0,0 +1,7 @@
|
|||
{
|
||||
"plug":"Motion",
|
||||
"host":"localhost",
|
||||
"port":8080,
|
||||
"key":"change_this_to_something_very_random____make_sure_to_match__/plugins/motion/conf.json",
|
||||
"notice":"Looks like you have the Motion plugin running. Don't forget to enable <b>Send Frames</b> to start pushing frames to be read."
|
||||
}
|
115
plugins/motion/libs/clusterPoints.js
Normal file
115
plugins/motion/libs/clusterPoints.js
Normal file
|
@ -0,0 +1,115 @@
|
|||
'use strict'
|
||||
|
||||
module.exports = {
|
||||
|
||||
data: getterSetter([], function(arrayOfArrays) {
|
||||
var n = arrayOfArrays[0].length;
|
||||
return (arrayOfArrays.map(function(array) {
|
||||
return array.length == n;
|
||||
}).reduce(function(boolA, boolB) { return (boolA & boolB) }, true));
|
||||
}),
|
||||
|
||||
clusters: function() {
|
||||
var pointsAndCentroids = kmeans(this.data(), {k: this.k(), iterations: this.iterations() });
|
||||
var points = pointsAndCentroids.points;
|
||||
var centroids = pointsAndCentroids.centroids;
|
||||
|
||||
return centroids.map(function(centroid) {
|
||||
return {
|
||||
centroid: centroid.location(),
|
||||
points: points.filter(function(point) { return point.label() == centroid.label() }).map(function(point) { return point.location() }),
|
||||
};
|
||||
});
|
||||
},
|
||||
|
||||
k: getterSetter(undefined, function(value) { return ((value % 1 == 0) & (value > 0)) }),
|
||||
|
||||
iterations: getterSetter(Math.pow(10, 3), function(value) { return ((value % 1 == 0) & (value > 0)) }),
|
||||
|
||||
};
|
||||
|
||||
function kmeans(data, config) {
|
||||
// default k
|
||||
var k = config.k || Math.round(Math.sqrt(data.length / 2));
|
||||
var iterations = config.iterations;
|
||||
|
||||
// initialize point objects with data
|
||||
var points = data.map(function(vector) { return new Point(vector) });
|
||||
|
||||
// intialize centroids randomly
|
||||
var centroids = [];
|
||||
for (var i = 0; i < k; i++) {
|
||||
centroids.push(new Centroid(points[i % points.length].location(), i));
|
||||
};
|
||||
|
||||
// update labels and centroid locations until convergence
|
||||
for (var iter = 0; iter < iterations; iter++) {
|
||||
points.forEach(function(point) { point.updateLabel(centroids) });
|
||||
centroids.forEach(function(centroid) { centroid.updateLocation(points) });
|
||||
};
|
||||
|
||||
// return points and centroids
|
||||
return {
|
||||
points: points,
|
||||
centroids: centroids
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
// objects
|
||||
function Point(location) {
|
||||
var self = this;
|
||||
this.location = getterSetter(location);
|
||||
this.label = getterSetter();
|
||||
this.updateLabel = function(centroids) {
|
||||
var distancesSquared = centroids.map(function(centroid) {
|
||||
return sumOfSquareDiffs(self.location(), centroid.location());
|
||||
});
|
||||
self.label(mindex(distancesSquared));
|
||||
};
|
||||
};
|
||||
|
||||
function Centroid(initialLocation, label) {
|
||||
var self = this;
|
||||
this.location = getterSetter(initialLocation);
|
||||
this.label = getterSetter(label);
|
||||
this.updateLocation = function(points) {
|
||||
var pointsWithThisCentroid = points.filter(function(point) { return point.label() == self.label() });
|
||||
if (pointsWithThisCentroid.length > 0) self.location(averageLocation(pointsWithThisCentroid));
|
||||
};
|
||||
};
|
||||
|
||||
// convenience functions
|
||||
function getterSetter(initialValue, validator) {
|
||||
var thingToGetSet = initialValue;
|
||||
var isValid = validator || function(val) { return true };
|
||||
return function(newValue) {
|
||||
if (typeof newValue === 'undefined') return thingToGetSet;
|
||||
if (isValid(newValue)) thingToGetSet = newValue;
|
||||
};
|
||||
};
|
||||
|
||||
function sumOfSquareDiffs(oneVector, anotherVector) {
|
||||
var squareDiffs = oneVector.map(function(component, i) {
|
||||
return Math.pow(component - anotherVector[i], 2);
|
||||
});
|
||||
return squareDiffs.reduce(function(a, b) { return a + b }, 0);
|
||||
};
|
||||
|
||||
function mindex(array) {
|
||||
var min = array.reduce(function(a, b) {
|
||||
return Math.min(a, b);
|
||||
});
|
||||
return array.indexOf(min);
|
||||
};
|
||||
|
||||
function sumVectors(a, b) {
|
||||
return a.map(function(val, i) { return val + b[i] });
|
||||
};
|
||||
|
||||
function averageLocation(points) {
|
||||
var zeroVector = points[0].location().map(function() { return 0 });
|
||||
var locations = points.map(function(point) { return point.location() });
|
||||
var vectorSum = locations.reduce(function(a, b) { return sumVectors(a, b) }, zeroVector);
|
||||
return vectorSum.map(function(val) { return val / points.length });
|
||||
};
|
245
plugins/motion/shinobi-motion-pixel.js
Normal file
245
plugins/motion/shinobi-motion-pixel.js
Normal file
|
@ -0,0 +1,245 @@
|
|||
//
|
||||
// Shinobi - Motion 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 moment = require('moment');
|
||||
var Canvas = require('canvas');
|
||||
var Cluster = require('./libs/clusterPoints.js');
|
||||
var config=require('./conf.json');
|
||||
if(process.argv[2]&&process.argv[3]){
|
||||
config.host=process.argv[2]
|
||||
config.port=process.argv[3]
|
||||
config.key=process.argv[4]
|
||||
}
|
||||
if(config.systemLog===undefined){config.systemLog=true}
|
||||
s={
|
||||
group:{},
|
||||
}
|
||||
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.checkRegion=function(d,cord){
|
||||
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 = '#005337';
|
||||
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();
|
||||
}
|
||||
}
|
||||
s.group[d.ke][d.id].canvasContext[cord.name].drawImage(d.image, 0, 0, d.width, d.height);
|
||||
var blenderCanvas = s.group[d.ke][d.id].canvas[cord.name];
|
||||
var blenderCanvasContext = s.group[d.ke][d.id].canvasContext[cord.name];
|
||||
s.group[d.ke][d.id].frameSelected[s.group[d.ke][d.id].frameNumber] = blenderCanvasContext.getImageData(0, 0, blenderCanvas.width, blenderCanvas.height);
|
||||
s.group[d.ke][d.id].frameNumber = 0 == s.group[d.ke][d.id].frameNumber ? 1 : 0;
|
||||
s.group[d.ke][d.id].lastRegionImageData = blenderCanvasContext.getImageData(0, 0, blenderCanvas.width, blenderCanvas.height);
|
||||
if(!s.group[d.ke][d.id].lastRegionImageData){return}
|
||||
var foundPixels = [];
|
||||
var average = 0;
|
||||
var currentImageLength = s.group[d.ke][d.id].lastRegionImageData.data.length * 0.25;
|
||||
for (b = 0; b < currentImageLength;){
|
||||
var pos = b * 4
|
||||
s.group[d.ke][d.id].lastRegionImageData.data[pos] = .5 * (255 - s.group[d.ke][d.id].lastRegionImageData.data[pos]) + .5 * s.group[d.ke][d.id].frameSelected[s.group[d.ke][d.id].frameNumber].data[pos];
|
||||
s.group[d.ke][d.id].lastRegionImageData.data[pos + 1] = .5 * (255 - s.group[d.ke][d.id].lastRegionImageData.data[pos + 1]) + .5 * s.group[d.ke][d.id].frameSelected[s.group[d.ke][d.id].frameNumber].data[pos + 1];
|
||||
s.group[d.ke][d.id].lastRegionImageData.data[pos + 2] = .5 * (255 - s.group[d.ke][d.id].lastRegionImageData.data[pos + 2]) + .5 * s.group[d.ke][d.id].frameSelected[s.group[d.ke][d.id].frameNumber].data[pos + 2];
|
||||
s.group[d.ke][d.id].lastRegionImageData.data[pos + 3] = 255;
|
||||
var score = (s.group[d.ke][d.id].lastRegionImageData.data[pos] + s.group[d.ke][d.id].lastRegionImageData.data[pos + 1] + s.group[d.ke][d.id].lastRegionImageData.data[pos + 2]) / 3;
|
||||
if(score>170){
|
||||
var x = (pos / 4) % d.width;
|
||||
var y = Math.floor((pos / 4) / d.width);
|
||||
foundPixels.push([x,y])
|
||||
}
|
||||
|
||||
average += (s.group[d.ke][d.id].lastRegionImageData.data[b * 4] + s.group[d.ke][d.id].lastRegionImageData.data[b * 4 + 1] + s.group[d.ke][d.id].lastRegionImageData.data[b * 4 + 2]);
|
||||
|
||||
b += 4;
|
||||
}
|
||||
// console.log(foundPixels)
|
||||
var matrices
|
||||
if(d.mon.detector_region_of_interest==='1'&&foundPixels.length>0){
|
||||
var groupedPoints = Object.assign({},Cluster);
|
||||
groupedPoints.iterations(25);
|
||||
groupedPoints.data(foundPixels);
|
||||
var groupedPoints = groupedPoints.clusters()
|
||||
var matrices=[]
|
||||
var mostHeight = 0;
|
||||
var mostWidth = 0;
|
||||
var mostWithMotion = null;
|
||||
groupedPoints.forEach(function(v,n){
|
||||
var matrix = {
|
||||
topLeft:[d.width,d.height],
|
||||
topRight:[0,d.height],
|
||||
bottomRight:[0,0],
|
||||
bottomLeft:[d.width,0],
|
||||
}
|
||||
v.points.forEach(function(b){
|
||||
var x = b[0]
|
||||
var y = b[1]
|
||||
if(x<matrix.topLeft[0])matrix.topLeft[0]=x;
|
||||
if(y<matrix.topLeft[1])matrix.topLeft[1]=y;
|
||||
//Top Right point
|
||||
if(x>matrix.topRight[0])matrix.topRight[0]=x;
|
||||
if(y<matrix.topRight[1])matrix.topRight[1]=y;
|
||||
//Bottom Right point
|
||||
if(x>matrix.bottomRight[0])matrix.bottomRight[0]=x;
|
||||
if(y>matrix.bottomRight[1])matrix.bottomRight[1]=y;
|
||||
//Bottom Left point
|
||||
if(x<matrix.bottomLeft[0])matrix.bottomLeft[0]=x;
|
||||
if(y>matrix.bottomLeft[1])matrix.bottomLeft[1]=y;
|
||||
})
|
||||
matrix.x = matrix.topLeft[0];
|
||||
matrix.y = matrix.topLeft[1];
|
||||
matrix.width = matrix.topRight[0] - matrix.topLeft[0]
|
||||
matrix.height = matrix.bottomLeft[1] - matrix.topLeft[1]
|
||||
|
||||
if(matrix.width>mostWidth&&matrix.height>mostHeight){
|
||||
mostWidth = matrix.width;
|
||||
mostHeight = matrix.height;
|
||||
mostWithMotion = matrix;
|
||||
}
|
||||
|
||||
matrices.push(matrix)
|
||||
})
|
||||
}
|
||||
average = (average / (currentImageLength));
|
||||
if (average > parseFloat(cord.sensitivity)){
|
||||
s.cx({f:'trigger',id:d.id,ke:d.ke,details:{plug:config.plug,name:cord.name,reason:'motion',confidence:average,matrices:matrices}})
|
||||
}
|
||||
s.group[d.ke][d.id].canvasContext[cord.name].clearRect(0, 0, d.width, d.height);
|
||||
}
|
||||
s.checkAreas=function(d){
|
||||
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:'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.checkRegion(d,s.group[d.ke][d.id].cords[b])
|
||||
}
|
||||
delete(d.image)
|
||||
}
|
||||
|
||||
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});
|
||||
})
|
||||
io.on('disconnect',function(d){
|
||||
io.connect();
|
||||
})
|
||||
io.on('f',function(d){
|
||||
switch(d.f){
|
||||
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].lastRegionImageData=undefined
|
||||
s.group[d.ke][d.id].frameNumber=0
|
||||
s.group[d.ke][d.id].frameSelected=[]
|
||||
delete(s.group[d.ke][d.id].cords)
|
||||
delete(s.group[d.ke][d.id].buffer)
|
||||
}
|
||||
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:undefined,
|
||||
frameNumber:0,
|
||||
frameSelected:[],
|
||||
}
|
||||
}
|
||||
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){
|
||||
if(s.group[d.ke][d.id].motion_lock){
|
||||
return
|
||||
}else{
|
||||
if(!d.mon.detector_lock_timeout||d.mon.detector_lock_timeout===''||d.mon.detector_lock_timeout==0){
|
||||
d.mon.detector_lock_timeout=2000
|
||||
}else{
|
||||
d.mon.detector_lock_timeout=parseFloat(d.mon.detector_lock_timeout)
|
||||
}
|
||||
s.group[d.ke][d.id].motion_lock=setTimeout(function(){
|
||||
clearTimeout(s.group[d.ke][d.id].motion_lock);
|
||||
delete(s.group[d.ke][d.id].motion_lock);
|
||||
},d.mon.detector_lock_timeout)
|
||||
}
|
||||
s.group[d.ke][d.id].buffer=Buffer.concat(s.group[d.ke][d.id].buffer);
|
||||
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){
|
||||
}
|
||||
}
|
||||
if(d.mon.detector_frame_save==="1"){
|
||||
d.base64=s.group[d.ke][d.id].buffer.toString('base64')
|
||||
}
|
||||
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.image.onload = function() {
|
||||
s.checkAreas(d);
|
||||
}
|
||||
d.image.src = s.group[d.ke][d.id].buffer;
|
||||
s.group[d.ke][d.id].buffer=null;
|
||||
}
|
||||
}catch(err){
|
||||
if(err){
|
||||
s.systemLog(err)
|
||||
delete(s.group[d.ke][d.id].buffer)
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
})
|
233
plugins/motion/shinobi-motion.js
Normal file
233
plugins/motion/shinobi-motion.js
Normal file
|
@ -0,0 +1,233 @@
|
|||
//
|
||||
// Shinobi - Motion 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 moment = require('moment');
|
||||
var Canvas = require('canvas');
|
||||
var config=require('./conf.json');
|
||||
if(process.argv[2]&&process.argv[3]){
|
||||
config.host=process.argv[2]
|
||||
config.port=process.argv[3]
|
||||
config.key=process.argv[4]
|
||||
}
|
||||
if(config.systemLog===undefined){config.systemLog=true}
|
||||
s={
|
||||
group:{},
|
||||
}
|
||||
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){
|
||||
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 = '#005337';
|
||||
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;
|
||||
var average = 0;
|
||||
while (i < (blendedData.data.length * 0.25)) {
|
||||
average += (blendedData.data[i * 4] + blendedData.data[i * 4 + 1] + blendedData.data[i * 4 + 2]);
|
||||
++i;
|
||||
}
|
||||
average = (average / (blendedData.data.length * 0.25))*10;
|
||||
if (average > parseFloat(cord.sensitivity)){
|
||||
s.cx({f:'trigger',id:d.id,ke:d.ke,details:{plug:config.plug,name:cord.name,reason:'motion',confidence:average}})
|
||||
|
||||
}
|
||||
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 fastAbs(value) {
|
||||
return (value ^ (value >> 31)) - (value >> 31);
|
||||
}
|
||||
|
||||
function threshold(value) {
|
||||
return (value > 0x15) ? 0xFF : 0;
|
||||
}
|
||||
|
||||
function difference(target, data1, data2) {
|
||||
// blend mode difference
|
||||
if (data1.length != data2.length) return null;
|
||||
var i = 0;
|
||||
while (i < (data1.length * 0.25)) {
|
||||
target[4 * i] = data1[4 * i] == 0 ? 0 : fastAbs(data1[4 * i] - data2[4 * i]);
|
||||
target[4 * i + 1] = data1[4 * i + 1] == 0 ? 0 : fastAbs(data1[4 * i + 1] - data2[4 * i + 1]);
|
||||
target[4 * i + 2] = data1[4 * i + 2] == 0 ? 0 : fastAbs(data1[4 * i + 2] - data2[4 * i + 2]);
|
||||
target[4 * i + 3] = 0xFF;
|
||||
++i;
|
||||
}
|
||||
}
|
||||
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){
|
||||
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:'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])
|
||||
}
|
||||
delete(d.image)
|
||||
}
|
||||
|
||||
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});
|
||||
})
|
||||
io.on('disconnect',function(d){
|
||||
io.connect();
|
||||
})
|
||||
io.on('f',function(d){
|
||||
switch(d.f){
|
||||
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={}
|
||||
delete(s.group[d.ke][d.id].cords)
|
||||
delete(s.group[d.ke][d.id].buffer)
|
||||
}
|
||||
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){
|
||||
if(s.group[d.ke][d.id].motion_lock){
|
||||
return
|
||||
}else{
|
||||
if(!d.mon.detector_lock_timeout||d.mon.detector_lock_timeout===''||d.mon.detector_lock_timeout==0){
|
||||
d.mon.detector_lock_timeout=2000
|
||||
}else{
|
||||
d.mon.detector_lock_timeout=parseFloat(d.mon.detector_lock_timeout)
|
||||
}
|
||||
s.group[d.ke][d.id].motion_lock=setTimeout(function(){
|
||||
clearTimeout(s.group[d.ke][d.id].motion_lock);
|
||||
delete(s.group[d.ke][d.id].motion_lock);
|
||||
},d.mon.detector_lock_timeout)
|
||||
}
|
||||
s.group[d.ke][d.id].buffer=Buffer.concat(s.group[d.ke][d.id].buffer);
|
||||
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){
|
||||
}
|
||||
}
|
||||
if(d.mon.detector_frame_save==="1"){
|
||||
d.base64=s.group[d.ke][d.id].buffer.toString('base64')
|
||||
}
|
||||
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.image.onload = function() {
|
||||
s.checkAreas(d);
|
||||
}
|
||||
d.image.src = s.group[d.ke][d.id].buffer;
|
||||
s.group[d.ke][d.id].buffer=null;
|
||||
}
|
||||
}catch(err){
|
||||
if(err){
|
||||
s.systemLog(err)
|
||||
delete(s.group[d.ke][d.id].buffer)
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
})
|
Loading…
Add table
Add a link
Reference in a new issue