Door bell with pi camera and motion detection

From the latest magpi, perhaps we can dispense with the power supply too… https://www.raspberrypi.org/magpi-issues/MagPi55.pdf

1 Like

Got this working using the Motion package, but I don’t really like the way that software works and want to do everything in python. Has anyone tried other python packages for motion detection from the pi camera? I found this http://www.ostafichuk.com/raspberry-pi-projects/python-picamera-motion-detection/

There is also simpleCV

I havent looked into this yet my setup is still on my test pi (P2B) still waiting for my zeroW to turn up. Its been a week now.
As I mentioned I am still keen on getting Open CV to install. but wont do that until I get my Pi zeroW.

I might have a look at simple CV

This seems quite an insightful site for Simple CV.

http://homepage.cem.itesm.mx/carbajal/EmbeddedSystems/SLIDES/Computer%20Vision/Computer%20Vision%20using%20SimpleCV%20and%20the%20Raspberry%20Pi.pdf

Covers motion detection, not sure how responsive it is but certainly worth a look.

I have been checking for native python motion detection on the pi and discovered that the picamera module can do motion detection, check out https://picamera.readthedocs.io/en/release-1.13/recipes2.html#recording-motion-vector-data and http://picamera.readthedocs.io/en/release-1.12/recipes2.html#custom-outputs It also dose capture for openCV btw https://picamera.readthedocs.io/en/release-1.13/recipes2.html#capturing-to-an-opencv-object

My hope is to integrate machine learning (http://scikit-image.org/docs/dev/auto_examples/) to enable some pretty sophisticated object recognition within HA. Not sure if all this is possible on the pi, or if post processing on my mac will be required…
Anybody else headed down the picamera route?
Cheers

OK got v1 working, not hooked up to my door but watching my bird feeder. Will train a classifier to identify birds by type and ping HA when particular species are identified :slight_smile:

2 Likes

Now I have the zero W, I think Im going to bin the PIR from my project and use motion detection via the camera. I still want to implement face recognition also. I believe its possible but not sure how effective it will be in regards to processing speeds.
When I get some time Ill be playing/ adjusting my original code to include these changes.

So have you got any detection running within your code or are you just continually streaming?
If you have How have you managed it? and is there any latency?

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