Door bell with pi camera and motion detection

Code below. So far working well and have captures several images of birds. Also triggered by cars in the background but I could experiment with the trigger_threshold. The images are only recorded when motion is detected. I haven’t characterised the latency, but should only be limited by the frame rate I guess. More detail at https://picamera.readthedocs.io/en/release-1.13/recipes2.html#recording-motion-vector-data

from future import division
import picamera
import numpy as np
import time
from time import gmtime,strftime

trigger_threshold = 10
recording_minutes = 500000

motion_dtype = np.dtype([
(‘x’, ‘i1’),
(‘y’, ‘i1’),
(‘sad’, ‘u2’),
])

class MyMotionDetector(object):
def init(self, camera):
width, height = camera.resolution
self.cols = (width + 15) // 16
self.cols += 1 # there’s always an extra column
self.rows = (height + 15) // 16
self.file_counter = 0

def write(self, s):
    # Load the motion data from the string to a numpy array
    data = np.fromstring(s, dtype=motion_dtype)
    # Re-shape it and calculate the magnitude of each vector
    data = data.reshape((self.rows, self.cols))
    data = np.sqrt(
        np.square(data['x'].astype(np.float)) +
        np.square(data['y'].astype(np.float))
        ).clip(0, 255).astype(np.uint8)
    # If there're more than 10 vectors with a magnitude greater
    # than 60, then say we've detected motion
    if (data > 60).sum() > trigger_threshold:
        time_string = strftime("%Y-%m-%d %H:%M:%S", gmtime())
        print('Motion detected at ' + time_string + '_file_' + str(self.file_counter) )
        file_string = 'File_r2_' + str(self.file_counter) + '.jpg'
        camera.capture(file_string)   # basic capture
        self.file_counter = self.file_counter + 1
        # camera.capture_continuous('img{counter:03d}.jpg')

    # Pretend we wrote all the bytes of s
    return len(s)

with picamera.PiCamera() as camera:
camera.resolution = (640, 480)
camera.framerate = 30
camera.start_recording(
# Throw away the video data, but make sure we’re using H.264
‘/dev/null’, format=‘h264’,
# Record motion data to our custom output object
motion_output=MyMotionDetector(camera)
)
camera.wait_recording(recording_minutes*60) # make continuous
camera.stop_recording()

Fantastic!
Glad to see you making progress. I have been a little busy decorating the past couple of weeks so not done anything other than searching for options, libraries, and examples.
I will be getting back to it soon. though so I will continue to post my updates on this thread.

played with your code, I added a bit at the end to allow it to restart the recording when it finished.

while true:    
    with picamera.PiCamera() as camera:
        camera.resolution = (640, 480)
        camera.framerate = 30
        camera.start_recording(
# Throw away the video data, but make sure we're using H.264
        '/dev/null', format='h264',
# Record motion data to our custom output object
        motion_output=MyMotionDetector(camera)
        )
        camera.wait_recording(30) # make continuous
        camera.stop_recording()

Ive been playing around and adapted it some more now.
It sends MQTT to home assistant when motion is detected and when it goes passive.
It works quite well.

import numpy as np
import picamera
import picamera.array
from time import gmtime,strftime
import time
import datetime
import subprocess
import paho.mqtt.client as mqtt
import paho.mqtt.publish as publish
# MQTT Variables
auth = {
  'username':"####",
  'password':"##########"
}
hostip="192.168.0.8"

MotionState="0"

#PreMotState="0"
# Send MQTT payload
def PublishMQTT(topic,payload):
    publish.single(topic,
                   payload=payload,
                   hostname=hostip,
                   client_id="doorbell",
                   auth=auth,
                   port=1883)
    
class DetectMotion(picamera.array.PiMotionAnalysis):
    def analyse(self, a):
        global MotionState
        a = np.sqrt(
            np.square(a['x'].astype(np.float)) +
            np.square(a['y'].astype(np.float))
            ).clip(0, 255).astype(np.uint8)
        # If there're more than 10 vectors with a magnitude greater
        # than 60, then say we've detected motion
        if (a > 60).sum() > 10:
            time_string = strftime("%Y-%m-%d %H:%M:%S", gmtime())
            print('Motion detected at ' + time_string)
            Tstamp = datetime.datetime.now().strftime('%b-%d-%G_%I%M%S%p-%G')+'.jpg'
            #file_string = 'File_r2_' + time_string + '.jpg'
            camera.capture(Tstamp)   # basic capture
            MotionState="1"
            PublishMQTT("doorbell/motion",MotionState)
        else:
           if MotionState == "1":
               print "Motion Passive."
               MotionState="0"
               PublishMQTT("doorbell/motion",MotionState)
           
while True:
    with picamera.PiCamera() as camera:
        with DetectMotion(camera) as output:
            camera.resolution = (640, 480)
            camera.start_recording(
              '/dev/null', format='h264', motion_output=output)
            camera.wait_recording(30)
            camera.stop_recording()

That’s great :slight_smile: Do you get many false positives, and is it ready for use as a door bell?

I left it running over night in my dining room (curtains drawn , doors closed and no pets to trigger) on average Im getting 100 false positives per hour.
It seems it is detecting the brightness differences, there were more FP’s between 4:50am and 7am (sunrise).
So it does need tweaking a lot. I will start by increasing the vectors trigger and see how I get on .
I might add a data logger so it can analyse the data, saves me doing it manually.
I have ordered a noir camera for the door bell Ill give that a try when it arrives (in my theory should be less FP’s).

No havent added the button press to the code yet Im toying with the idea of using an off the shelf RF doorbell, hack in to the button press frequency,I already have a heap of 433mhz recievers .
I will still include the GPIO part,I still need to solder the header into the Zero W to use it though.

1 Like

Got it all setup and running on my pi zero now with doorbell button wired to GPIO 23 and GND
Running great so far.

import numpy as np
import picamera
import picamera.array
from time import gmtime,strftime
import time
import datetime
import subprocess
import paho.mqtt.client as mqtt
import paho.mqtt.publish as publish
import RPi.GPIO as GPIO
import os 
#import shutil
# Smart doorbell script adapted by Lee Goodman (Meganoodle from example online
# https://www.modmypi.com/blog/raspberry-pi-gpio-sensing-motion-detection
#
# COMPONENTS
#
# Raspberry Pi 2 (Will use Pi zero when it arrives)
# Raspberry Pi Camera V2
# Motion sensor...HC-SR501 
# Door bell button..Momentary (normally open)
# Wifi Dongle
#
# SOFTWARE
# Latest version of Raspian 
#
# Camera and GPIO must be enabled in raspi-config
#
# GPIO Pinouts
#=======================================
# Door Bell Button-----Raspberry Pi
# Feed-----------------GND
# Load-----------------GPIO 23 (PIN 16)
#=======================================
#
# Setup ================================
# MQTT Variables
auth = {
  'username':"pi",
  'password':"raspberry"
}
hostip="192.168.0.8"
# File Path
ImagePath='/home/pi/doorbell/camera/'
# Motion detection state
MotionState="0"
# Doorbell button GPIO SETUP
GPIO.setmode(GPIO.BCM)
DB_PIN = 23
GPIO.setup(DB_PIN, GPIO.IN, pull_up_down=GPIO.PUD_UP)
# Doorbell button state variable
DBState="0"




# Functions =============================
# Check dir structure and create if necessary
def CheckDir(dirchk):
    chkpath='./'+dirchk
    if os.path.isdir(chkpath)==False:
        print'Creating '+dirchk+' directory...'
        os.mkdir(chkpath)
        if os.path.isdir(chkpath)==False:
            print'Error creating directory please check the drive is full and your path permissions'
        else:
            print dirchk+' directory created successfully...'
    else:
        print'Found '+dirchk+' directory...'

# Check if 'doorapp.conf' exists and create a blank file if necessary
# To be included into programme
def ChkConf():
    if os.path.isfile('./conf/door.conf')==False:
        print'Creating blank configuration file...'
        fconf = open('./conf/door.conf','w')
        print >>fconf,'[SCHEDULED SNAPSHOT]=True'
        print >>fconf,'[MQTT HOST]=192.168.0.8'
        print >>fconf,'[MQTT PASSWORD]=Anon'
        print >>fconf,'[MQTT USERNAME]=Anon'
        fconf.close()
        print'Configuration file created, please edit "./conf/door.conf"'
    else:
        print 'Configuration file found.'

# Doorbell pressed function
def DOORBELLON(DB_PIN):
    #if motion is triggered
    if GPIO.input(DB_PIN)<>1:
        DBState="1"
        PublishMQTT("doorbell/button",DBState)
        print "Doorbell Pressed..."
        localtime = time.asctime( time.localtime(time.time()) )
        print "Local current time :", localtime
    #if doorbell is released  
    if GPIO.input(DB_PIN)<>0:
        DBState="0"
        PublishMQTT("doorbell/button",DBState)
        print "Doorbell Released..."

# Take a snapsots ( file name is the date and time .jpg )       
def SNAPSHOT(Pressed):
    print 'Snapping...'
    Tstamp = ImagePath+datetime.datetime.now().strftime('%b-%d-%I%M%S%p-%G')+'.jpg'
    camera.capture ('/home/pi/doorbell/camera/frequent.jpg')
    if Pressed<>1:
        shutil.copy('/home/pi/doorbell/camera/frequent.jpg',Tstamp)        
        print 'Snapshot Saved to "'+Tstamp+'"'
    print'Snapshot complete'

# Send MQTT payload
def PublishMQTT(topic,payload):
    publish.single(topic,
                   payload=payload,
                   hostname=hostip,
                   client_id="doorbell",
                   auth=auth,
                   port=1883)

# PICAM Motion detection class    
class DetectMotion(picamera.array.PiMotionAnalysis):
    def analyse(self, a):
        global MotionState
        a = np.sqrt(
            np.square(a['x'].astype(np.float)) +
            np.square(a['y'].astype(np.float))
            ).clip(0, 255).astype(np.uint8)
        # If there're more than 10 vectors with a magnitude greater
        # than 60, then say we've detected motion
        # and send MQTT motion state to HA
        if (a > 70).sum() > 15:
            time_string = strftime("%Y-%m-%d %H:%M:%S", gmtime())
            print('Motion detected at ' + time_string)
            Tstamp = datetime.datetime.now().strftime('%b-%d-%G_%I%M%S%p-%G')+'.jpg'
            CamDir='/home/pi/doorbell/camera/'+Tstamp
            camera.capture(CamDir)   # basic capture
            MotionState="1"
            PublishMQTT("doorbell/motion",MotionState)
        else:
            # If Motion has only just turned passive send MQTT passive state to HA
           if MotionState == "1":
               print "Motion Passive."
               MotionState="0"
               PublishMQTT("doorbell/motion",MotionState)

# Main ========================
# Initial Setup
print "Smart Doorbell ... (CTRL+C to exit)"
print "Checking directory stucture..."
CheckDir('camera')
time.sleep(2)
CheckDir('conf')
print " Directory structure check complete."
print "Checking for configuration file..."
ChkConf()
print " Configuration file check complete."

# GPIO Interrupt setup
try:               
    GPIO.add_event_detect(DB_PIN,GPIO.BOTH, callback=DOORBELLON, bouncetime=200 )
# Main Loop                      
    while 1:
        with picamera.PiCamera() as camera:
            with DetectMotion(camera) as output:
                camera.resolution = (640, 480)
                camera.vflip = True
                camera.framerate = 20
                camera.start_recording('/dev/null', format='h264', motion_output=output)
                camera.wait_recording(3600)
                camera.stop_recording()
except KeyboardInterrupt:
    print " Quit"
    GPIO.cleanup()
1 Like

No face detection yet and the config file doesnt do anything yet either.
It sends MQTT message when motion is detected and when it turns passive
And when the doobell is pressed and released.

1 Like

Have you got it in a 3D printed case or something similar?

Not yet, its in an ABS of the shelf box at the moment.
I havent got a 3d printer , but there are a few online services which print for you for a price.
only thing is it is going to extremley lucky to get the box right on the first attempt.

Modifying something like this could be a start http://www.thingiverse.com/thing:1649799 Do you have access to solidworks to do the design? I can help out with the design if your’e interested.

I have used https://www.3dhubs.com/ Generally you pay for the material used

Also interesting, and in my original proposal :slight_smile: http://www.thingiverse.com/thing:2097751

Ive orderd another dash button to have a go at using this within the script.
it can be a programme with many options.

I have not used any 3d design or printing software, I was going to have a hunt tonight to see whats available.

Solidworks is the industry standard, but I am sure there are free ones around too.
In my case I downloaded the .stl files from thingiverse and then just uploaded them to 3D hubs

Interesting project - I am sure there will be much interest in this once completed. So currently you are using the cam motion detection to send a mqtt message as well as by push button?
Whats left to work on:

  • CV integration?
  • sending on video / audio feeds to smartphone to allow interaction with visitor?

Both interesting ideas :slight_smile:
My plan was to train a classifier to recognise when certain features are present in the images (e.g a person) to reduce the false positive rate of motion detection.I don’t have much experience in this area but a colleague recommended CNN https://www.tensorflow.org/tutorials/deep_cnn

Both of these are a little way off at the moment, still getting used to playing with Python and raspberry Pi.
Not even sure how it would perform yet.
Just trying to get the basics up and running then move on to the next stage.

It now saves a log when a doorbell press has been made and when motion is detected.

Ive tidied the code up a bit also shifted code blocks into functions added remarks etc.

I am now working at getting HA to notify my phone and my wifes phone when either event occurs.

Im also looking at BTLE tag/beacon detection. Maybe as an alternative/addition to face recognition.

For face recognition, this article covers the AWS service https://www.hackster.io/gr1m/raspberry-pi-facial-recognition-16e34e?utm_source=hackster&utm_medium=email&utm_campaign=new_projects

This actually looks really cool https://aws.amazon.com/rekognition/