DIY Home Security Camera: Object/Person Detection & Alerts

DIY Home Security Camera: Object/Person Detection & Alerts

diy home security camera detects a squirrel

A DIY home security camera is only as good as its ability to detect threats. Our CCTV system can detect people, cars, visitors, and more.

Pictures are captured and presented for review.

Machine learning filters the data to find interesting objects — like a delivery truck, a person at the front door, or a chipmunk having a snack (above).

DIY Home Security Camera

Previously, we built a DIY home security camera with Raspberry Pis and MotionEye.

As we began to add more security cameras, the interface became somewhat overwhelming. It’s not practical to be monitoring the cameras all the time, as well. We needed a way to be alerted when something happened passively (instead of actively monitoring).

DIY home security cameras being monitored via motionEye
As the number of security cameras grew in MotionEye, they became difficult to monitor. Check out our garage workshop, pictured on the top-left.

The cameras above are run by Raspberry Pi & Arduino.

Different cameras are used in different locations…

If you're new to Raspberry Pi, the popular CanaKits are a great place to start. I prefer to buy the Raspberry Pi 4, power adapter, micro SD cards, and heatsinks separately. Not only is this cheaper, but it reduces e-waste.

The following are affiliate links to other parts I used in this project. I never link to a product that I have not personally used.

Thankfully, the world of computer vision has become accessible to hobbyists. There are plenty of pre-trained models that will allow someone without a background in machine learning to detect many common objects.

IP Security Camera System(s)

It’s also possible to use an off-the-shelf, name-brand IP security camera system.

Any camera that MotionEye can recognize will work.

Many security cameras conform to the IP camera protocol.

That said, it’s important to check before buying a camera. For example, I had a couple Arlo security cameras lying around. Unfortunately, like some other manufacturers, their WiFi cameras are proprietary. The data is shipped to the Arlo servers, and cannot be connected to MotionEye.

Personally, I don’t like using a cloud service for, well, anything home related. One of the main reasons I decided to build the IOT in the cabin from scratch was to avoid using cloud services that might otherwise invade our privacy. The unfortunate truth is: even if a company like Nest or Arlo never suffers a data breach, there are still many employees (and possibly 3rd parties) who could conceivably access data.

Object Detection with DOODS

DOODS uses machine learning to detect things. It’s already integrated into Home Assistant’s image_processing platform. The forum discussion and official docs explain how to use it. However, I’m going to suggest a different approach.

The main weakness of machine learning is that it’s slow. Especially without specialized hardware. Attempting to do real-time detection (less than a few seconds) on multiple high-resolution cameras simultaneously is not feasible.

Problem: high resolution, high framerate detection is hard.

Solution: only perform object detection on things captured by MotionEye.

MotionEye is already built to detect motion. It can capture still images whenever something moves:

I just needed to send these captured snapshots to DOODS. For this, I used Node Red. The following exported flow does a few things:

  1. Monitors the MotionEye directories for new files.
  2. Sends those files to the DOODS server in a buffered manner.
  3. Copies files which DOODS says contain some object to a destination folder.

In effect, this means that tagged images show up in the destination folder. Images without anything in them (triggered by some errant motion) are automatically deleted by MotionEye after one day.

[{"id":"eba176be.56e168","type":"tab","label":"Motion -> DOODS Security Feed","disabled":false,"info":""},{"id":"e4d34c45.1c416","type":"debug","z":"eba176be.56e168","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","statusVal":"","statusType":"auto","x":1150,"y":120,"wires":[]},{"id":"413695d9.5362c4","type":"inject","z":"eba176be.56e168","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"1","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"","payloadType":"date","x":110,"y":40,"wires":[["55b302e4.e632d4"]]},{"id":"55b302e4.e632d4","type":"fs-file-lister","z":"eba176be.56e168","name":"jpgs","start":"/etc/motioneye/","pattern":"*.jpg","folders":"*","hidden":true,"lstype":"files","path":true,"single":true,"depth":"4","stat":false,"showWarnings":false,"x":250,"y":40,"wires":[["2d7e780a.f66728"]]},{"id":"5d577f68.a92138","type":"delay","z":"eba176be.56e168","name":"","pauseType":"rate","timeout":"5","timeoutUnits":"seconds","rate":"3","nbRateUnits":"1","rateUnits":"second","randomFirst":"1","randomLast":"5","randomUnits":"seconds","drop":false,"x":730,"y":40,"wires":[["f58cb1b3.4daf4"]]},{"id":"ef0228a2.06ced8","type":"function","z":"eba176be.56e168","name":"","func":"msg.payload = {\n    \"detector_name\": \"default\",\n    \"data\": msg.payload.toString('base64'),\n    \"detect\": { \"*\": 57 }\n}\nmsg.headers = {\n    \"Content-Type\": \"application/json\"\n}\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","x":220,"y":120,"wires":[["7069e735.ff1f"]]},{"id":"7069e735.ff1f","type":"http request","z":"eba176be.56e168","name":"detect","method":"POST","ret":"obj","paytoqs":"ignore","url":"http://doods.home.svc.cluster.local:8080/detect","tls":"","persist":false,"proxy":"","authType":"","x":350,"y":120,"wires":[["50c3d48c.436a94"]]},{"id":"f58cb1b3.4daf4","type":"file in","z":"eba176be.56e168","name":"","filename":"","format":"","chunk":false,"sendError":false,"encoding":"none","x":90,"y":120,"wires":[["ef0228a2.06ced8"]]},{"id":"9d478ff.e172d7","type":"split","z":"eba176be.56e168","name":"","splt":"\\n","spltType":"str","arraySplt":1,"arraySpltType":"len","stream":false,"addname":"","x":750,"y":120,"wires":[["cbb83f9e.3ff7d"]]},{"id":"50c3d48c.436a94","type":"switch","z":"eba176be.56e168","name":"","property":"payload.detections","propertyType":"msg","rules":[{"t":"nempty"}],"checkall":"true","repair":false,"outputs":1,"x":470,"y":120,"wires":[["81991971.c6a928"]]},{"id":"81991971.c6a928","type":"change","z":"eba176be.56e168","name":"detections","rules":[{"t":"set","p":"payload","pt":"msg","to":"payload.detections","tot":"msg"}],"action":"","property":"","from":"","to":"","reg":false,"x":610,"y":120,"wires":[["9d478ff.e172d7"]]},{"id":"cbb83f9e.3ff7d","type":"function","z":"eba176be.56e168","name":"","func":"msg.dstpath = \"/etc/motion-detections\"\nmsg.dstfn = msg.ymd + \"_\" + msg.hms + \"_\" + + \"_\" + msg.payload.label + \"_\" + Math.round(msg.payload.confidence) + \".jpg\";\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","x":880,"y":120,"wires":[["427f0dac.da86a4"]]},{"id":"427f0dac.da86a4","type":"fs-ops-copy","z":"eba176be.56e168","name":"","sourcePath":"srcpath","sourcePathType":"msg","sourceFilename":"srcfn","sourceFilenameType":"msg","destPath":"dstpath","destPathType":"msg","destFilename":"dstfn","destFilenameType":"msg","link":false,"overwrite":false,"x":1020,"y":120,"wires":[["e4d34c45.1c416"]]},{"id":"bf304a4b.a2b8c8","type":"function","z":"eba176be.56e168","name":"dedupe, filter to recent","func":"msg.filename = msg.payload;\nmsg.srcfile = msg.payload;\n\nvar parts = msg.payload.split('/')\ = parts[3]\nif ( == 'feed') {\n    return null;\n}\nmsg.ymd = parts[parts.length-2]\nmsg.hms = parts[parts.length-1].split('.')[0]\nmsg.srcfn = parts.pop()\nmsg.srcpath = parts.join('/')\nif (msg.ymd.indexOf(\"-\") <= 0) {\n    return null;\n}\nmsg.datetime = msg.ymd+\"T\"+msg.hms.split(\"-\").join(\":\")\nmsg.time = Date.parse(msg.datetime)\nmsg.age = - msg.time\n\nif (msg.age >= (1000 * 60 * 60)) {\n    return null;\n}\n\nmsg.files = flow.get('processed-files') || []\nif (msg.files.includes(msg.filename)) {\n    return null;\n}\nmsg.files.push(msg.filename);\nflow.set('processed-files', msg.files)\n\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","x":540,"y":40,"wires":[["5d577f68.a92138"]]},{"id":"2d7e780a.f66728","type":"split","z":"eba176be.56e168","name":"","splt":"\\n","spltType":"str","arraySplt":1,"arraySpltType":"len","stream":false,"addname":"","x":370,"y":40,"wires":[["bf304a4b.a2b8c8"]]}]

Security Feed in Home Assistant

The tagged images from the DIY home security camera are saved in the /config/www/security_feed directory of Home Assistant. Before beginning this step, you need to ensure that the folder exists and is being populated with the images you want to review (from the last step). This directory was chosen because it allows the images to be viewed from the web browser. But first, I needed a good way to view them.

Find the Security Feed component on Github.

This Home Assistant component lets you review the images captured by the object detection.

As explained in the README from the Github, the security feed presents the images which have been recorded. We can choose to delete or save each image. Saved images are copied to a permanent directory:

DIY home security camera UI
When there are no images remaining, the Security Feed shows it is recording.

The sensor includes data about how many images are available for review and what was most recently detected. For example, the sensor data will look like this when our DIY smart doorbell detects a person:

This allows us to automate notifications for the security feed:

  • If a person is detection at the doorbell.
  • If a car or truck is detected in the driveway.
  • If a dog, cat, etc. is detected in the yard.

Depending on the importance of each event, we might send an alert over the loudspeaker system.

Home Automation Ideas

Thanks for reading this post. You might be interested in this list of 100+ home automation ideas.

Or, drop your email in the form below and you'll receive links to the individual build-guides and projects on this site, as well as updates with the newest projects.

... but this site has no paywalls. If you do choose to sign up for this mailing list I promise I'll keep the content worth your time.

Written by
(zane) / Technically Wizardry
Join the discussion

  • Hello,

    Thanks for the tutorial.

    Could you please explain this bit in “dedupe, filter to recent” function?

    if (msg.files.includes(msg.filename)) {
    return null;

    With this bit it always return null

    • Hey Alex,

      That prevents the flow from re-processing an image which has already been processed. msg.files was previously set to a flow-variable that contains any filename that has already been through the flow. The fact that it is returning null indicates that the flow has already been run on all the images in the directory.

  • Hey I tried following your steps and setting up the Feed through HACS, but I hit a snag. Not sure if I’m doing everything correctly.. or if I was supposed to have downloaded some extra dependencies. Here’s what the log says
    020-08-02 12:40:31 ERROR (MainThread) [homeassistant.config] Invalid config for [sensor.security_feed]: not a directory for dictionary value @ data[‘folder’]. Got ‘/config/www/security_feed’. (See /config/configuration.yaml, line 42). Please check the docs at [link to]
    2020-08-02 12:40:32 ERROR (MainThread) [homeassistant.setup] Unable to set up dependencies of default_config. Setup failed for dependencies: script
    2020-08-02 12:40:32 ERROR (MainThread) [homeassistant.setup] Setup failed for default_config: Could not set up all dependencies.

    Any help would be appreciated

    • Hi Lee! The error you’re seeing is telling you that you’ve configured the Security Feed to pull from the /config/www/security_feed folder. So it’s trying to look in that folder for the images, but the folder does not exist. It sounds like you’re not generating images for review, which the security feed depends upon. This folder should have been created in the “Object Detection with DOODS” step, as it is the folder where the images you wish to review should be placed after they have been processed. Sorry if I did not mention that exactly. The DOODS step, right now, is the most confusing bit for others to implement since it’s just a Node RED implementation. I’m actually working on changing that step to a custom HA component, which should be much easier for everybody, and will be updating this post appropriately.

      – Z

  • I’d love if you could explain in more detail the Node Red implementation. I’m new to all things home assistant and have successfully gotten camera streaming through motioneye and DOODS working but am not sure how to adjust the node red portion to take the motion detections and then send an alert if chosen labels are detected.

    • Why do it in Node RED at all? The purpose of Node RED is to get the data into a Home Assistant entity. Once that is the case, as it should now be, you can just write your automations with Home Assistant. That way, you don’t need to figure out how to send notifications thru Node RED (since Home Assistant already has a rather sophisticated alert system).