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

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.shape[1], stream.array.shape[0], stream.array.shape[2])
if prev_image is None:
prev_image = stream.array.reshape([256*144, 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.

3 thoughts on “Skeleton of a motion detecting video capture program for the Raspberry Pi + Camera…

  1. Cesar Tobar

    Dear Mr. Mark, I have a Raspberry pi with its picamera conected and also have a Macbook. I want to know how can i save the video directly in a folder in my Macbook out of the flash card w/o writing and deleting as you said. My software programming skills and network configuring are limited so thats why im asking for help. My goal is to be able to save the video in a Mac´s folder and also watch the video in streaming (at least save it).
    Thanks for your attention,
    Cesar.

  2. llaabbss

    Dear Mark,

    Could you please help me understand the meaning of ‘seconds’?
    I decreased the bitrate of the recorded video down to 1.8 Mbps like this:

    stream = picamera.PiCameraCircularIO(camera, seconds=20)
    camera.start_recording(stream, format=”h264″, bitrate=1800000)

    With this I simply wanted to make a file much smaller for further procesing, but the problem is that the PiCameraCircularIO buffer seems stores up to 3 minutes of the video with this new bitrate. So instead of getting only the last 20 seconds I receive all 3 minutes of this video!

    What’s wrong with my code?

    If it runs correct and there is no possibility to get the last 20 seconds with this low bitrate, how can I cut the length of the stream (before saving it to the file) to grab only last 20 seconds?

    Thank you in advance.

Comments are closed.