python picamera Motion Detection

Motion detection on the Pi is a very common task that many people have done on the net. The best performance is accomplished using c, however I ran into the picamera python library and decided that I wanted to give it a try since I needed to get more proficient at python code for work. I have been using a modified version of the ‘motion’ program that was customized for the pi, but I did not like how it worked, so why not try something else?

I was not able to find an example on the net that gave me exactly what I wanted, so I decided I would post my work in case it would help someone else who wants to build a very simple motion detection system.

I used a Pi Model A with a Raspberry Pi camera modified to remove the IR filter for this project, and a wifi usb dongle.

Using the picamera python package was very easy, and resulted in a decent motion detection program. It can only pull and compare images at 1 frame per second, I have not looked into speeding it up yet, I will try to improve it later as i get more familiar with python…

To install the picamera software:

sudo apt-get update
sudo apt-get install python3-picamera

Now save the following code in the file /home/pi/motion.py

#! /usr/bin/env python3
#
#The MIT License (MIT)
#Copyright (c) 2014 Ron Ostafichuk
#
#Permission is hereby granted, free of charge, to any person obtaining a copy
#of this software and associated documentation files (the "Software"), to deal
#in the Software without restriction, including without limitation the rights
#to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
#copies of the Software, and to permit persons to whom the Software is
#furnished to do so, subject to the following conditions:
#
#The above copyright notice and this permission notice shall be included in all
#copies or substantial portions of the Software.
#
#THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
#IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
#FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
#AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
#LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
#OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
#SOFTWARE.

import io
import os
import time
import picamera
import numpy as np

widthH = 2592 # too slow for motion check, only use for the save sequence 
heightH = 1944
width = 1440 # use lower resolution for motion check
height = 1080

threshold = 30 # how much must the color value (0-255) change to be considered a change
minPixelsChanged = width * height * 2 / 100 # % change  # how many pixels must change to begin a save sequence
print("minPixelsChanged=",minPixelsChanged) # debug

print ('Creating in-memory stream')
stream = io.BytesIO()
step = 1  # use this to toggle where the image gets saved
numImages = 1 # count number of images processed
captureCount = 0 # flag used to begin a sequence capture

# function to generate a sequence of 20 filenames for the picamera capture_sequence command to use
def filenames():
    frame = 0
    fileName = time.strftime("NAS/%Y%m%d/%Y%m%d-%H%M%S-",time.localtime())
    while frame < 20:
        yield '%s%02d.jpg' % (fileName, frame)
        frame += 1

# begin monitoring
with picamera.PiCamera() as camera:
    time.sleep(1) # let camera warm up
    try:
        while threshold > 0:
            camera.resolution = (1440,1080) # use a smaller resolution for higher speed compare

            print ('Capture ' , numImages)
            if step == 1:
                stream.seek(0)
                camera.capture(stream, 'rgba',True) # use video port for high speed
                data1 = np.fromstring(stream.getvalue(), dtype=np.uint8)
                step = 2
            else:
                stream.seek(0)
                camera.capture(stream, 'rgba',True)
                data2 = np.fromstring(stream.getvalue(), dtype=np.uint8)
                step = 1
            numImages = numImages + 1

            if numImages > 4:  # ignore first few images because if the camera is not quite ready it will register as motion right away
                # look for motion unless we are in save mode
                if captureCount <= 0:
                    print("Compare")
                    # not capturing, test for motion (very simplistic, but works good enough for my purposes)
                    data3 = np.abs(data1 - data2)  # get difference between 2 successive images
                    numTriggers = np.count_nonzero(data3 > threshold) / 4 / threshold #there are 4 times the number of pixels due to rgba

                    print("Trigger cnt=",numTriggers)

                    if numTriggers > minPixelsChanged:
                        captureCount = 1 # capture ? sequences in a row
                        # make sure directory exists for today
                        d = time.strftime("NAS/%Y%m%d") #unfortunately this saves as UTC time instead of local, will fix it later sorry
                        if not os.path.exists(d):
                            os.makedirs(d)

                if captureCount > 0:
		    # in capture mode, save an image in hi res
                    camera.resolution = (widthH,heightH)
                    dtFileStr = time.strftime("NAS/%Y%m%d/%Y%m%d-%H%M%S-00.jpg",time.localtime()) # once again, UTC time instead of local time
                    print("Saving sequence ",dtFileStr)
                    # save full resolution images to the NAS
                    camera.capture_sequence(filenames(),'jpeg',use_video_port=True, quality=92)
                    captureCount = captureCount-1

    finally:
        camera.close()
        print ('Program Terminated')

If you make this file executable using

chmod a+x motion.py

Then you can run the program by typing

./motion.py

To make the capture of large numbers of files practical, the program needs to save the files to a network share. I have set up my network share on a NAS box running linux, added a user named pi with a password and given that user a share name ‘Security’ with a subdirectory named ‘Pi’.

On the pi type:

mkdir ~/NAS
sudo nano /etc/fstab

Put this line at the bottom of the /etc/fstab file

//192.168.1.100/Security/Pi     /home/pi/NAS cifs       credentials=/home/pi/.smbcredentials,uid=pi,file_mode=0777,dir_mode=0777        0       0

You must create the credentials entry as follows, and it must match what you have set up on your NAS box:

sudo nano ~/.smbcredentials

Put the following lines into the file and save it:

username=pi
password=YourPasswordOnTheNASBox
domain=workgroup

To get this program to run on the pi after every boot you need to edit the /etc/rc.local file

sudo nano /etc/rc.local

Place this line BEFORE the ‘Exit 0’ line

(sleep 8;sudo su - pi -c "/home/pi/motion.py")&

This will cause the program to start about 8 seconds after the bootup, and it will run as the ‘pi’ user instead of root, which you need if you want the NAS box to work. Remember the credentials for the NAS are based on the pi user.

The hardest part of this exercise is getting your NAS box to play nice with the pi. There are so many different NAS boxes out there that I have not included any instructions. I got tired of low performance NAS boxes and built my own Linux based NAS using a very old atom based netbook. It was still way faster than my DNS-323, glad to see that thing gone.

Well, I hope this is helpful for someone! I will be tweaking it once in a while and posting any improvements back to this page.

1 thought on “python picamera Motion Detection”

Comments are closed.