A Dashboard setup for video cameras

Yes, I sea, in your previous reply. But where will it store the picture file.

It sure can,
In addition, if one wants to limit the coverage to only get detection from a smaller area, you can create a mask, very effective.

You define where in Motion configuration

In target_dir you can set to a path like /home/pi/tmp or so

And then you can use the watch node to check when a new image is saved, grab it and send it via mail

You will most likely also have to limit the number of images to send per event. I would use a rate limiting delay node and drop intermediate messages

Could you please elaborate it. you mean to say I need to change in "motion.conf" file?

Yes, the change is in the motion.conf. Depending on version, you can do the change from the web gui or directly by editing the file. Lets assume we edit the file directly in a RPi

The file motion.conf should be found in /etc/motion
So we stop Motion and then we do "sudo nano /etc/motion/motion.conf"

Halfway down in the file you should find the section for target directory. You could change it like this or any other path you like (remember that both Motion and NR needs to have access rights to the specified folder)


# Target base directory for pictures and films
# Recommended to use absolute path. (Default: current working directory)
target_dir /home/pi/Pictures

Save and restart Motion

In NR, add the watch node, a rate limiting delay node and a debug node, watching the same folder. Try now to take snapshots with Motion and you should see images saved in the folder. The watch node will report that new files have been found and you could use the reported path to add the image to the email

To take snapshots in Motion, you find this action in the web gui

image

I did this and found the following pagepicture

But after clicking on snapshot my camera turned off and motion library also turned off. Only with cam-id "0" I got settings as mentioned in above picture.

What if you would select the camera first?

Like http://192.1168.178.50:8080/1/action/snapshot

EDIT: To make snapshot in Motion is really Motion specific. Once you manage to see that an image is saved in the target directory, next step is to use that and send it via email. This is a good link to Motion documentation https://motion-project.github.io/motion_guide.html

picture
If I write in cam-id "1" then I got " Not Valid" page.

But without changing the "target_dir" path directory. I get picture but in "/var/lib/motion/
" directory folder.
But its ok. I will select ("/var/lib/motion/") current directory path for send to email.

The below picture shows the nodes, for sending a picture (by using a motion library) to an email address in the result of PIR sensor detection.

[{"id":"2f8957c5.e65e78","type":"tab","label":"Flow 1","disabled":false,"info":""},{"id":"97215231.a4661","type":"debug","z":"2f8957c5.e65e78","name":"","active":false,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","x":490,"y":260,"wires":[]},{"id":"9d61d83b.2c3c58","type":"function","z":"2f8957c5.e65e78","name":"","func":"\n //var msg1;\nif (msg.payload === 0){\n    msg.payload =1;\n    msg.payload= Number(msg.payload);\n    return msg;\n}\nif( msg.payload === 1){\n    msg.payload = 0;\n     msg.payload= Number(msg.payload);\nreturn msg;\n}\n  ","outputs":1,"noerr":0,"x":350,"y":320,"wires":[["97215231.a4661","2bb8153b.d53b3a"]]},{"id":"ba0f6d0f.87067","type":"e-mail","z":"2f8957c5.e65e78","server":"smtp.gmail.com","port":"465","secure":true,"tls":true,"name":"receiver@gmail.com","dname":"Email","x":1010,"y":380,"wires":[]},{"id":"8ab852b8.1f5b4","type":"function","z":"2f8957c5.e65e78","name":"","func":"msg.payload={};\nmsg.payload.type=\"photo\";\nmsg.payload.caption=\"Next photo in sequence\";\nmsg.payload.content=\"/var/lib/motion/lastsnap.jpg\";\nreturn msg;","outputs":1,"noerr":0,"x":630,"y":340,"wires":[["72baafb1.f7297"]]},{"id":"f6d69641.758b78","type":"debug","z":"2f8957c5.e65e78","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","x":890,"y":320,"wires":[]},{"id":"72baafb1.f7297","type":"file in","z":"2f8957c5.e65e78","name":"file path","filename":"/var/lib/motion/lastsnap.jpg","format":"","chunk":false,"sendError":false,"x":760,"y":380,"wires":[["f6d69641.758b78","4d771b92.e94974"]]},{"id":"4d771b92.e94974","type":"change","z":"2f8957c5.e65e78","name":"attach","rules":[{"t":"set","p":"attachments","pt":"msg","to":"payload","tot":"msg"}],"action":"","property":"","from":"","to":"","reg":false,"x":890,"y":420,"wires":[["ba0f6d0f.87067"]]},{"id":"4c1fee01.e447","type":"delay","z":"2f8957c5.e65e78","name":"delay","pauseType":"delay","timeout":"10","timeoutUnits":"seconds","rate":"1","nbRateUnits":"1","rateUnits":"second","randomFirst":"1","randomLast":"5","randomUnits":"seconds","drop":false,"x":250,"y":400,"wires":[["9d61d83b.2c3c58"]]},{"id":"c2a18d59.7741b","type":"comment","z":"2f8957c5.e65e78","name":"If motion detection, then send Picture to email","info":"","x":210,"y":240,"wires":[]},{"id":"2bb8153b.d53b3a","type":"http request","z":"2f8957c5.e65e78","name":"Snapshot","method":"GET","ret":"txt","paytoqs":false,"url":"http://192.168.178.10:8080/0/action/snapshot","tls":"","persist":false,"proxy":"","authType":"","x":480,"y":400,"wires":[["8ab852b8.1f5b4"]]},{"id":"ffe0bc17.39f07","type":"rpi-gpio in","z":"2f8957c5.e65e78","name":"PIR Motion Sensor","pin":"40","intype":"tri","debounce":"25","read":false,"x":130,"y":320,"wires":[["4c1fee01.e447"]]}]

But is it possible to send a picture to the email, in the result of motion detection with a camera instead of PIR sensor?

2 Likes

Fine, you got it working!

Possible yes, requires some additional configuration in Motion, specifically you would need to set Motion in detection mode (detection/start) and create a mask to avoid false alarms from areas of no interest. Could however be very tricky to achieve a good solution to avoid false alarms. To use only camera I think the solution required should use an object detection model, looking for "persons". Luckily there is such available. It is discussed here: https://discourse.nodered.org/t/object-detection-using-node-red-contrib-tfjs-coco-ssd/22931and I have it running successfully in a RPi3+

So the basic principle for such a solution would be:

  • Motion is running in "detection start" mode and looking for motion in the video
  • When motion is detected, images is stored in a folder
  • New file is then analyzed by the "tf coco ssd" node, looking for a "person" in the image
  • If person is found, the analyzed picture will be sent by email

It is an interesting setup, I shall see if I find the time to setup such a flow, I have anyway already a camera and Motion monitoring my entrance door so all hardware and software is already installed

I think you should now run your PIR based solution a while and see how it works. One concern is where you save the images (/var/lib/motion), if you would set Motion in detection start mode, it would start to write many times, it could destroy your sd card. You should therefore, in that case, have a ssd disk or similar attached

Are you running Motion in a RPi3?

This is the first trial and it already works as expected!! Thanks to many contributors providing great nodes, I have just put them together in a simple flow

I will share the flow, required nodes etc very soon, asap we finished easter lunch etc etc, so later today or tomorrow

Functionality:

  • when an image is dropped in the watched folder, manually or created by Motion, it will be recognized, sent for coco ssd analyze, if a person is detected, boxes will be drawn and finally it will be sent via email
  • as additional feature, there is a mqtt in node in the flow. This allows image data to be sent via mqtt. This is how I communicate images over the network from my Motion installations on several RPi's to one common analyzing computer (to make this happen, on motion detected, a python script is started by Motion where the image is taken and forwarded over mqtt)

2 Likes

Yes, I am using RPi 3. Its ok, I will continue with PIR sensor.

If you are interested, you can try this flow, it might look complex but is rather straight forward. You could add it to a separate tab and let it watch the same Monitor image directory (/var/lib/motion), keeping your PIR solution unchanged

Required nodes in addition to standard nodes (I use the latest current versions):
Install via "Manage palette"

  • node-red-contrib-image-tools
  • node-red-contrib-tfjs-coco-ssd
  • node-red-node-email
[{"id":"345bf70a.8ff7c8","type":"comment","z":"62e04cc7.69cf04","name":"If motion detection, then send Picture to email","info":"","x":250,"y":40,"wires":[]},{"id":"411fa684.d527d8","type":"tensorflowCoco","z":"62e04cc7.69cf04","name":"","scoreThreshold":"","passthru":true,"x":580,"y":160,"wires":[["d7499d10.9b50b"]]},{"id":"24206a8a.65a716","type":"mqtt in","z":"62e04cc7.69cf04","name":"","topic":"epic","qos":"2","datatype":"auto","broker":"2a019090.5ba4d","x":170,"y":240,"wires":[["a357a64f.869c78"]]},{"id":"a357a64f.869c78","type":"switch","z":"62e04cc7.69cf04","name":"","property":"payload","propertyType":"msg","rules":[{"t":"istype","v":"buffer","vt":"buffer"}],"checkall":"true","repair":false,"outputs":1,"x":380,"y":240,"wires":[["411fa684.d527d8"]]},{"id":"23db01de.10b36e","type":"switch","z":"62e04cc7.69cf04","name":"detect persons","property":"classes","propertyType":"msg","rules":[{"t":"hask","v":"person","vt":"str"}],"checkall":"true","repair":false,"outputs":1,"x":580,"y":310,"wires":[["cf65cd55.3b0ef"]]},{"id":"d7499d10.9b50b","type":"change","z":"62e04cc7.69cf04","name":"pay to detect","rules":[{"t":"move","p":"payload","pt":"msg","to":"detect","tot":"msg"}],"action":"","property":"","from":"","to":"","reg":false,"x":580,"y":240,"wires":[["23db01de.10b36e"]]},{"id":"cf65cd55.3b0ef","type":"jimp-image","z":"62e04cc7.69cf04","name":"","data":"image","dataType":"msg","ret":"img","parameter1":"","parameter1Type":"msg","parameter2":"","parameter2Type":"msg","parameter3":"","parameter3Type":"msg","parameter4":"","parameter4Type":"msg","parameter5":"","parameter5Type":"msg","parameter6":"","parameter6Type":"msg","parameter7":"","parameter7Type":"msg","parameter8":"","parameter8Type":"msg","parameterCount":0,"jimpFunction":"none","selectedJimpFunction":{"name":"none","fn":"none","description":"Just loads the image.","parameters":[]},"x":580,"y":380,"wires":[["ef5baf2e.aaf7a"]]},{"id":"ef5baf2e.aaf7a","type":"function","z":"62e04cc7.69cf04","name":"draw Rects","func":"const LINE_WIDTH = 2;\nconst LINE_COLOR = 0xED143DFF;\n\nvar jimpImage = msg.payload;\nlet imgW = jimpImage.bitmap.width;\nlet imgH = jimpImage.bitmap.height;\n\n//create clamping functions to avoid printing outside of image\nlet clampX = (val) => clamp(val,0,imgW);\nlet clampY = (val) => clamp(val,0,imgH);\n\n//add imgBatchOps to msg for drawing text in next node \nmsg.imgBatchOps = [];\n\n\nif (msg.detect.length>0) {\n    drawBoxes(jimpImage, msg.detect);\n}\n\nfunction drawBox(img, box) {\n    //get box ccords\n    let x = box.bbox[0];\n    let y = box.bbox[1];\n    let w = box.bbox[2];\n    let h = box.bbox[3];\n    \n    scanHLine(img, x, y+h-14, w, 14, 0xfa758e22); //draw box for text\n    scanRectangle(img, x, y, w, h, LINE_WIDTH, LINE_COLOR);//draw outer rect\n    \n    //built batch operations for next image node to draw text\n    msg.imgBatchOps.push(  \n        {\n            \"name\": \"print\",\n            \"parameters\": [\n                \"FONT_SANS_10_BLACK\",\n                clampX(x+2),\n                clampY(y+h-16),\n                box.class\n            ]\n        }\n        \n    );  \n}\n\nfunction drawBoxes(img, detections) {\n    detections.forEach(element => drawBox(img, element))\n}\n\n\nfunction makeColorIterator(color) {\n  return function (x, y, offset) {\n    this.bitmap.data.writeUInt32BE(color, offset, true);\n  }\n}\n\nfunction scanRectangle(image,x,y,w,h,linePX,color){\n    let iterator = makeColorIterator(color);\n   \n    let xw, yh;\n    x = clampX(x-(linePX/2));\n    y = clampY(y-(linePX/2));\n    w = clampX(w);\n    h = clampY(h);\n    xw = clampX(x+w);\n    yh = clampY(y+h);\n\n    \n    if(y+linePX <= imgH){\n        image.scan(x, y, w, linePX,  iterator);// scan linePX height line - TOP\n    }\n    if(yh+linePX <= imgH){\n        image.scan(x, yh, w, linePX, iterator);// scan linePX height line - BOTTOM\n    }\n    if(x+linePX <= imgW){\n        image.scan(x, y, linePX, h,  iterator);// scan linePX width line - LEFT\n    }\n    if(xw+linePX <= imgW){\n        image.scan(xw, y, linePX, h, iterator);// scan linePX width line - RIGHT\n    }\n    \n}\n\n\nfunction scanHLine(image,x,y,length,linePX,color){\n    let iterator = makeColorIterator(color);\n    x = clampX(x);\n    y = clampY(y);\n    length = clampX(length);\n    if(y+linePX <= imgH){\n        image.scan(x, y, length, linePX,  iterator);// \n    }\n}\n\nfunction scanVLine(image,x,y,height,linePX,color){\n    let iterator = makeColorIterator(color);\n    x = clampX(x);\n    y = clampY(y);\n    height = clampY(y+height);\n    if(x+linePX <= imgW){\n        image.scan(x, y, linePX, height,  iterator);// \n    }\n}\n\n\n//scanCircle(jimpImage, 92, 170, 43, 0x000000FF);\n//scanCircle(jimpImage, 92, 170, 42, 0xFFCC00FF);\n\nfunction clamp(value, min, max) {\n    return Math.min(Math.max(value, min), max);\n}\n\nfunction scanCircle(image, x, y, radius, color) {\n    let iterator = makeColorIterator(color);\n    return image.scan(x - radius, y - radius, radius*2, radius*2, function (pixX, pixY, idx) {\n        if (Math.pow(pixX - x, 2) + Math.pow(pixY - y, 2) < radius*radius) {\n            iterator.call(this, pixX, pixY, idx);\n        }\n    });\n}\n\n \n\nreturn msg;","outputs":1,"noerr":0,"x":580,"y":450,"wires":[["c9964923.b3e558"]]},{"id":"c9964923.b3e558","type":"jimp-image","z":"62e04cc7.69cf04","name":"add text","data":"payload","dataType":"msg","ret":"img","parameter1":"imgBatchOps","parameter1Type":"msg","parameter2":"","parameter2Type":"msg","parameter3":"","parameter3Type":"msg","parameter4":"","parameter4Type":"msg","parameter5":"","parameter5Type":"msg","parameter6":"","parameter6Type":"msg","parameter7":"","parameter7Type":"msg","parameter8":"","parameter8Type":"msg","parameterCount":1,"jimpFunction":"batch","selectedJimpFunction":{"name":"batch","fn":"batch","description":"apply one or more functions","parameters":[{"name":"options","type":"json","required":true,"hint":"an object or an array of objects containing {\"name\" : \"function_name\", \"parameters\" : [x,y,z]}.  Refer to info on side panel}"}]},"x":580,"y":520,"wires":[["8e6fe5d.4d2f018","171ad863.c30528"]]},{"id":"8e6fe5d.4d2f018","type":"jimp-image","z":"62e04cc7.69cf04","name":"","data":"payload","dataType":"msg","ret":"buf","parameter1":"","parameter1Type":"msg","parameter2":"","parameter2Type":"msg","parameter3":"","parameter3Type":"msg","parameter4":"","parameter4Type":"msg","parameter5":"","parameter5Type":"msg","parameter6":"","parameter6Type":"msg","parameter7":"","parameter7Type":"msg","parameter8":"","parameter8Type":"msg","parameterCount":0,"jimpFunction":"none","selectedJimpFunction":{"name":"none","fn":"none","description":"Just loads the image.","parameters":[]},"x":580,"y":590,"wires":[["883190ad.6dc53"]]},{"id":"fa145cb8.aa157","type":"file in","z":"62e04cc7.69cf04","name":"","filename":"","format":"","chunk":false,"sendError":false,"x":170,"y":160,"wires":[["4360aa96.6a4b34"]]},{"id":"efefc67f.4b58d8","type":"watch","z":"62e04cc7.69cf04","name":"","files":"/home/pi/Pictures","recursive":"","x":170,"y":80,"wires":[["4f3cf324.67896c"]]},{"id":"4f3cf324.67896c","type":"delay","z":"62e04cc7.69cf04","name":"","pauseType":"delay","timeout":"100","timeoutUnits":"milliseconds","rate":"1","nbRateUnits":"1","rateUnits":"second","randomFirst":"1","randomLast":"5","randomUnits":"seconds","drop":false,"x":380,"y":80,"wires":[["fa145cb8.aa157"]]},{"id":"4360aa96.6a4b34","type":"delay","z":"62e04cc7.69cf04","name":"","pauseType":"rate","timeout":"5","timeoutUnits":"seconds","rate":"1","nbRateUnits":"10","rateUnits":"second","randomFirst":"1","randomLast":"5","randomUnits":"seconds","drop":true,"x":380,"y":160,"wires":[["411fa684.d527d8"]]},{"id":"171ad863.c30528","type":"image viewer","z":"62e04cc7.69cf04","name":"","width":160,"data":"payload","dataType":"msg","x":840,"y":380,"wires":[[]]},{"id":"883190ad.6dc53","type":"e-mail","z":"62e04cc7.69cf04","server":"smtp.gmail.com","port":"465","secure":true,"tls":true,"name":"","dname":"","x":840,"y":590,"wires":[]},{"id":"2a019090.5ba4d","type":"mqtt-broker","z":"","name":"","broker":"192.168.0.240","port":"1883","clientid":"","usetls":false,"compatmode":true,"keepalive":"60","cleansession":true,"birthTopic":"","birthQos":"0","birthPayload":"","closeTopic":"","closePayload":"","willTopic":"","willQos":"0","willPayload":""}]

1 Like

Could you tell me which software you are using for image processing. I am looking for a lightweight one, tks a lot. In your diagram, some how 2 node "pay to detect" and "detect persons" doing the image processing, that is so interesting.

However, if I understand you are using PIR for triggering capture, not realtime processing camera stream, right?

@linhtranvu
If you are asking me, I do not use PIR, I use only the cameras.

  1. The cameras are handled by a software named Motion. Motion is detecting movements in the video and saving images to a folder/directory when this happens. In Motion you can setup a lot of params and filters, you can enable/disable detection etc etc. To make a long story short, please read about Motion and it's features here

  2. NR example above is configured to watch the folder for new images and to do object analyze on those and send an email when a known object is recognized. Everything is from this point handled by nodes available for NR

@krambriw tks for your quick reply. I will try motion

Take a look at my project on GitHub using Coral TPU AI accelerator and handle net cameras, up to 4K, directly:

Its Python with some straight forward node-red to handle notifications and spatial filtering to not alert on persons in the frame but not of interest -- people walking along the sidewalk in front of the house etc.

1 Like