motion-detect.py: a motion sensitive camera for Raspberry Pi or other Linux machines

[Motion detector camera with external high-res camera] This is the beginning of documentation on how to run my motion detection camera system, which I use for taking wildlife photos.

For background, a quick-and-dirty enclosure, and sample images, you can read my blog articles: A Raspberry Pi motion-detecting wildlife camera: Part 1 and A Raspberry Pi motion-detecting wildlife camera: Part 2.

In short, the system can drive three types of cameras: the Raspberry Pi camera module, a standard USB webcam (as long as it's supported by the Linux video driver and fswebcam), or an external camera that's supported by gphoto2.

If you wish, you can use two different cameras at once, a low-res camera for taking the constantly repeating snaps to see if anything has changed, and a high-res camera for taking photos once a change has been noticed.

You can specify the sensitivity and threshold for deciding when a change has happened, the test region of interest (only motion inside this rectangle will be detected), a crop rectangle for the high-resolution photos, a remotely mounted directory to save the images (so you don't have to wear out your Pi's SD card), and a local directory to save images if the connection fails to the remote directory. Run motion-detect.py --help for more details.

You might also want to Disable the bright red light on the camera.

Setting up the camera

The steps for setting up the camera in a new location:
  1. Position the camera approximately and plug it in
  2. Go inside, wait a while then ssh to the pi. (This assumes you've configured the pi to use a fixed IP address.)
  3. Mount your remote filesystem on the pi:
    sudo sshfs host:/path/to ~/localpath
    
    .
  4. On the pi, take a test photo to the ssh-mounted directory:
    raspistill -w 320 -h 240 -o ~pi/localpath/test.jpg
    
  5. View the test photo on your desktop and make sure it includes the area you want to cover.
  6. If you're using a separate gphoto camera for the high-res shots, set it up and turn it on. Then, on the pi, check to make sure the image will be correct. cd into the shared directory (gphoto's capture will fail if the current working directory isn't the same as the one you're capturing to). This is the command the motion camera will use to capture high-res photos:
    # gphoto2 --set-config syncdatetime=1 --set-config --set-config capturetarget=sdram --capture-image-and-download --filename canon.jpg
    
    If you need to set a zoom level or other configuration parameters, you can test that before starting the motion camera by running manual gphoto2 commands, such as:
    # gphoto2 --set-config syncdatetime=1 --set-config zoom=5 --set-config capturetarget=sdram --capture-image-and-download --filename canon5.jpg
    
    Then make sure the changes stick by testing with the base command again:
    # gphoto2 --set-config syncdatetime=1 --set-config --set-config capturetarget=sdram --capture-image-and-download --filename canon.jpg
    
  7. On the desktop, open the test photo in gimp (or any image editor you prefer) and measure the X, Y, width, and height of the test region you want to use, and the crop region if it's different. In gimp I made a selection with the rectangular selection tool, then looked at the numbers reported in the Tool Options area of the Toolbox.
  8. On the Pi, su to root and run the motion detection script with whatever arguments you want: for instance,
    python ./motion_detect.py -v -s 250 -t 30 -r 320x240 -b 150x125+100+100 -c - /tmp ~pi/moontrade/snapshots/ > ~/localpath/picam.out 2>&1
    -- redirecting the output to a file on the shared filesystem so you'll be able to follow it even if you close your initial ssh session.

How do you determine what to pass in as sensitivity and threshold? Remember, sensitivity is how many pixels must change; threshold is how much each pixel must change. And unfortunately I don't have a good answer to that. A threshold of 30 seems to be working fairly well in my tests for daylight use; for infra-red or other monochrome use, you'll want something much smaller, maybe around 10.

Sensitivity depends on the size of your test region, but it also depends on how much the light conditions are changing -- do you have scattered clouds sometimes covering the sun, or plants waving around that might be generating shadows that change? My best advice is to take a guess at some initial arguments, or run it with the defaults, with verbose mode (-v) on, and see how much change you typically get from one image to the next.

Two commands that I've used, the first for daytime photos with a gphoto2 camera, the second for night vision with the Pi NoIR camera:

# python ./motion_detect.py -v -s 250 -t 30 -r 320x240 -b 150x125+100+100 -c - /tmp ~pi/moontrade/snapshots/ > ~/localpath/picam.out 2>&1
# python ./motion_detect.py -v -s 150 -t 9 -r 320x240 -b 318x168+1+69 -c - /tmp ~pi/moontrade/snapshots/noir/ >~pi/moontrade/snapshots/noir/noir.out 2>&1 &

Watching the output

Okay, now the camera is running. You can monitor it by watching the output window. If you see a line like

===================== 1432 pixels changed
that means it detected something and will snap a high-res image.

Or you can periodically ls the ssh-mounted directory. You'll see a debug.png which shows you the latest image with the test region and any changed pixels highlighted, a file with a name like first-2014-05-18-09-09-24.jpg which is a high-res snapshot taken at the beginning of the run. Any subsequent motion-induced photos will be named something like snap-2014-05-18-09-22-57.jpg (snap instead of first).

Happy snapping!

[Rock squirrel using Raspberry Pi camera]