Skeleton of a motion detecting video capture program for the Raspberry Pi + Camera…

Published on 2015-04-10 by Mark VandeWettering

Last week I was playing around with using “motion-mmal” to capture pictures of hummingbirds feeding at my feeder. That was fun, but if I wanted to get high resolution pictures, I could not get very high frame rates (maybe 2-5 fps at best). I thought that perhaps by writing my own capture application in C, perhaps I could do better. After all, the graphics processor in the Pi is capable of recording HD video and directly encode it as H264 video. There should be some way to use that hardware effectively, right?

As it turns out, there is.

As a tease, here is some of the video I captured yesterday:



It’s recorded at 1280×720 and 25fps (more on that later). It takes about 20% of the cpu available on one of my older Model B Raspberry Pi. The motion detection is done on the camera entirely in Python, and is a bit crufty, but works well enough to get some good video.

Warning: this code is presented as-is. If you aren’t a python programmer, you may not have the skills necessary to understand or use this code, but it is a good basic outline that spells out most of the parts you need. Feel free to adapt the code to your needs. If you redistribute it, it would be nice if you could give a nod to this code and my blog in some fashion, but I’m not going to be insulted if you don’t. And if you have any improvements, I’d love to hear about them.

[sourcecode lang=”python”]
#!/usr/bin/env python

# __ __
# _ _ _/ /__\_/ / ___ ____
# | |/|/ / _ `/ __/ __/ _ / -_) _/
# |__,__/_,_/\
_/\_/_//_/\_/_/

import numpy as np
import io
import os
import os.path
import fractions
import time
import random
import picamera
import picamera.array
import datetime as dt
import warnings
import platform
from pkg_resources import require
import subprocess

print platform.platform()
print "Using picamera version", require(‘picamera’)[0].version

#warnings.filterwarnings(‘default’, category=DeprecationWarning)

prev_image = None
image = None

def detect_motion(camera):
global image, prev_image
with picamera.array.PiYUVArray(camera, size=(256,144)) as stream:
camera.capture(stream, format=’yuv’, use_video_port=True, resize=(256,144))
#print "%dx%d:%d image" % (stream.array.shape1, stream.array.shape[0], stream.array.shape2)
if prev_image is None:
prev_image = stream.array.reshape([256144, 3])[:,0]
return False
else:
image = stream.array.reshape([256
144, 3])[:,0]
diff = np.abs(prev_image.astype(float)-image.astype(float))
diff = diff[diff>35]
# print diff.shape[0]
prev_image = image
return diff.shape[0] > 200

def write_video(stream, fname):
# Write the entire content of the circular buffer to disk. No need to
# lock the stream here as we’re definitely not writing to it
# simultaneously
with io.open(fname, ‘wb’) as output:
for frame in stream.frames:
if frame.frame_type == picamera.PiVideoFrameType.sps_header:
stream.seek(frame.position)
break
while True:
buf = stream.read1()
if not buf:
break
output.write(buf)
# Wipe the circular stream once we’re done
stream.seek(0)
stream.truncate()

with picamera.PiCamera(framerate=fractions.Fraction(’30/1′)) as camera:
dir = "/var/tmp/capture"
camera.resolution = (1280, 720)
camera.framerate = fractions.Fraction(’30/1′)
camera.vflip = True
camera.hflip = True
camera.start_preview()
seconds = 5
stream = picamera.PiCameraCircularIO(camera,seconds=seconds, bitrate=8000000)
print "[ Buffer %s seconds/%d bytes ]" % (seconds, stream.size)
camera.start_recording(stream, format=’h264′, bitrate=8000000)
try:
while True:
camera.wait_recording(1)
if detect_motion(camera):
print "Dumping."
# generate a filename…
base = ‘cam_’+dt.datetime.now().strftime("%H%M%S")
part1 = os.path.join(dir, base+"-A.h264")
part2 = os.path.join(dir, base+"-B.h264")
camera.split_recording(part2)
write_video(stream, part1)
camera.wait_recording(15)
while detect_motion(camera):
camera.wait_recording(1)
camera.split_recording(stream)
with open("files.txt", "a") as f:
f.write("file %s\n" % part1)
f.write("file %s\n" % part2)
print "Dumped %s %s" % (part1, part2)
# Copy files to remote server
dst = ‘markv@conceptron.local:capture’
print "Copying %s to %s…" % (part1, dst)
rc = subprocess.check_call([‘scp’, ‘-p’, ‘-q’, part1, dst])
if rc != 0:
print "PROBLEM: (rc = %d)" % rc
else:
os.unlink(part1)
print "Copying %s to %s…" % (part2, dst)
rc = subprocess.check_call([‘scp’, ‘-p’, ‘-q’, part2, dst])
if rc != 0:
print "PROBLEM: (rc = %d)" % rc
else:
os.unlink(part2)
# ready to record some more…
camera.wait_recording(seconds)
finally:
camera.stop_recording()
[/sourcecode]

This would not be possible without the awesome picamera Python module and lots of careful engineering by the Raspberry Pi + Camera designers. They clearly foresaw this kind of possible application, and did everything that they needed to make it run efficiently and reasonably.

A few more short notes:

  • The motion detection code is terrible. It works after a fashion, but clearly could be tuned better.
  • To save space on my Pi, after capture it uploads each video file to one of my local servers, and then delete the file. I hardcoded it to use scp via subprocess. If you want to do something else, you can figure out what that might be and do it there. It won’t record new video while the scp is occurring: you could spawn a thread or some such to handle the copy and then dump back to the loop if you like.
  • You might want to write to a tmpfs file space, so it doesn’t eventually wear out your flash card with repeated writes and deletes, particularly if you can transmit these video files off as they are generated.
  • The picamera documentation is quite helpful. Indeed, it was my reading of that documentation which formed the basis of this initial script, which likely could not have been done (or not as easily) without them.

I will probably produce a tidier, better annotated version of this code and put it on github soon.

Hope this is of interest to some of you.

Addendum: If you want to see what the hardware looks like, you can see it here. Really just a cardboard box holding a pi, a powered hub, and the pi camera taped to the top, hung in the window.