Motion-activated camera with Raspberry Pi

This notebook does motion-activated image capture, using a simple motion sensor and OpenCV for catpure.

In [29]:
from IPython.display import display, Image
Image(filename="setup.png")
Out[29]:

The Raspberry Pi is hooked up to the breadboard via an Adafruit Pi T-Cobbler, with the motion sensor at 11 volts by chaining 4 AA batteries with the +5V pin. A green LED and GPIO pin 0 are connected to the motion sensor's alarm pin, and a small speaker is connected to GPIO pin 1.

In [1]:
import time
import cv2
import cv
import numpy as np
import wiringpi2

Some image formatters, so our image data are displayed automatically (significantly more than is actually necessary).

In [2]:
"""
PNG formatter(s) for PIL and OpenCV Image objects

Adapted from pil_display extension: https://github.com/minrk/ipython_extensions

Now when displayhook gets an image, it will be drawn in the browser.
"""

from io import BytesIO
from PIL import Image
import os
import tempfile

import cv2

def pil2imgdata(img, format='PNG'):
    fp = BytesIO()
    img.save(fp, format=format)
    return fp.getvalue()

def array2imgdata_pil(cvimg, format='PNG'):
    """get png data via converting to PIL Image"""
    img = Image.fromstring("L", cvimg.shape[:2], cvimg.tostring())
    return pil2imgdata(img, format)

def array2imgdata_fs(cvimg, format='PNG'):
    """get png data via filesystem, using cv2.imwrite
    
    This is much faster than in-memory conversion with PIL on the rPi
    for some reason.
    """
    fname = os.path.join(tempfile.gettempdir(), "_ipdisplay.%s" % format)
    cv2.imwrite(fname, cvimg)
    with open(fname) as f:
        data = f.read()
    os.unlink(fname)
    return data

def display_image_array(a):
    """If an array looks like RGB data, display it as one"""
    if len(a.shape) != 3 or a.shape[-1] != 3:
        return
    return array2pngdata_fs(a)

def register_image_formatters(ip):
    png_formatter = ip.display_formatter.formatters['image/png']
    # both, in case of pillow or true PIL
    png_formatter.for_type_by_name('PIL.Image', 'Image', pil2imgdata)
    png_formatter.for_type_by_name('Image', 'Image', pil2imgdata)
    # png_formatter.for_type_by_name('cv2.cv', 'iplimage', display_opencv_image)
    png_formatter.for_type_by_name("numpy", "ndarray", array2imgdata_fs)
In [3]:
ip = get_ipython()
png_formatter = ip.display_formatter.formatters['image/png']
register_image_formatters(ip)

Some basic GPIO setup

In [4]:
GPIO = wiringpi2.GPIO
wiringpi2.wiringPiSetup()
mode = wiringpi2.pinMode
write = wiringpi2.digitalWrite
read = wiringpi2.digitalRead

The motion sensor alarm input is hooked up to GPIO pin 0, and the speaker is hooked up to GPIO pin 1. We set pin 0 as digital input, and pin 1 for tonal output.

In [7]:
pin = 0
mode(pin, GPIO.INPUT)
speaker = 1
wiringpi2.softToneCreate(speaker)
Out[7]:
0
In [8]:
def beep(f=261, pin=speaker):
    wiringpi2.softToneWrite(pin, f)

Image Capture with OpenCV

We have a simple webcam on USB, and will capture with OpenCV.

In [9]:
import cv2
import cv
cam = cv2.VideoCapture(-1)

Set it to 640 pixels wide at 16x9 aspect ratio, with 50% brightness and contrast.

In [10]:
width = 640
height = int(9 * width / 16)
cam.set(cv.CV_CAP_PROP_FRAME_WIDTH, width)
cam.set(cv.CV_CAP_PROP_FRAME_HEIGHT, height)
cam.set(cv. CV_CAP_PROP_BRIGHTNESS, 0.5)
cam.set(cv. CV_CAP_PROP_CONTRAST, 0.5)
Out[10]:
False

Define our capture function - it flushes a few frames, because sometimes it seems there are stale images in the buffer.

It also can rotate the image, since sometimes we capture in portrait orientation.

In [22]:
CCW = lambda img: cv2.flip(cv2.transpose(img), 0)

def capture(rotate=False, flush=5):
    for i in range(flush):
        data = cam.read()
    if rotate:
        return CCW(data[1])
    else:
        return data[1]

Test a capture

In [25]:
frame = capture()
In [26]:
frame
Out[26]:

Automating capture

Now we just start a simple polling loop, to capture images any time the motion sensor detects movement. It also emits a short beep on the speaker to indicate the capture.

In [23]:
import sys
from IPython.display import display, clear_output, Javascript
In [24]:
# little javascript trick to avoid bouncing
display(Javascript("$(this.element).show().css('min-height', '500px');"))

while True:
    if read(pin):
        sys.stdout.flush()
        time.sleep(.01)
    else:
        # short beep to indicate capture
        beep()
        time.sleep(0.025)
        beep(0)
        tic = time.time()
        frame = capture()
        toc = time.time()
        clear_output()
        print toc - tic
        tic = time.time()
        display(frame)
        print time.time() - tic
0.519706964493