In my last crittercam installment,
NoIR night-vision crittercam, I was having trouble with false positives,
where the camera would trigger repeatedly after dawn as leaves moved
in the wind and the morning shadows marched across the camera's field of view.
I wondered if a passive infra-red (PIR) sensor would be the answer.
I got one, and the answer is: no. It was very easy to hook up, and
didn't cost much, so it was a worthwhile experiment; but it gets
nearly as many false positives as camera-based motion detection.
It isn't as sensitive to wind, but as the ground and the foliage heat
up at dawn, the moving shadows are just as much a problem as they were
with image-based motion detection.
Still, I might be able to combine the two, so I figure it's worth
Reading inputs from the HC-SR501 PIR sensor
The PIR sensor I chose was the common HC-SR501 module.
It has three pins -- Vcc, ground, and signal -- and two potentiometer
It's easy to hook up to a Raspberry Pi because it can take 5 volts
in on its Vcc pin, but its signal is 3.3v (a digital signal -- either
motion is detected or it isn't), so you don't have to fool with
voltage dividers or other means to get a 5v signal down to the 3v
the Pi can handle.
I used GPIO pin 7 for signal, because it's right on the corner of the
Pi's GPIO header and easy to find.
There are two ways to track a digital signal like this. Either you can
poll the pin in an infinfte loop:
import RPi.GPIO as GPIO
pir_pin = 7
sleeptime = 1
print "Motion detected!"
or you can use interrupts: tell the Pi to call a function whenever it
sees a low-to-high transition on a pin:
import RPi.GPIO as GPIO
pir_pin = 7
sleeptime = 300
print "Motion Detected!"
GPIO.add_event_detect(pir_pin, GPIO.RISING, callback=motion_detected)
print "Sleeping for %d sec" % sleeptime
Obviously the second method is more efficient. But I already had a
loop set up checking the camera output and comparing it against
previous output, so I tried that method first, adding support to my
script. I set up the camera pointing at the wall, and, as root, ran the script
telling it to use a PIR sensor on pin 7, and the local and remote
directories to store photos:
# python motion_detect.py -p 7 /tmp ~pi/shared/snapshots/
and whenever I walked in front of the camera, it triggered and took
a photo. That was easy!
Reliability problems with add_event_detect
So easy that I decided to switch to the more efficient interrupt-driven
model. Writing the code was easy, but I found it triggered more often:
if I walked in front of the camera (and stayed the requisite 7 seconds
or so that it takes raspistill to get around to taking a photo),
when I walked back to my desk, I would find two photos, one showing my
feet and the other showing nothing. It seemed like it was triggering
when I got there, but also when I left the scene.
A bit of web searching indicates this is fairly common: that with RPi.GPIO
a lot of people see triggers on both rising and falling edges -- e.g. when
the PIR sensor starts seeing motion, and when it stops seeing motion
and goes back to its neutral state -- when they've asked for just
GPIO.RISING. Reports for this go back to 2011.
On the other hand, it's also possible that instead of seeing a GPIO
falling edge, what was happening was that I was getting multiple calls
to my function while I was standing there, even though the RPi hadn't
finished processing the first image yet. To guard against that, I put
a line at the beginning of my callback function that disabled further
callbacks, then I re-enabled them at the end of the function after the
Pi had finished copying the photo to the remote filesystem. That reduced
the false triggers, but didn't eliminate them entirely.
Oh, well, The sun was getting low by this point, so I stopped
fiddling with the code and put the camera out in the yard with a pile
of birdseed and peanut suet nuggets in front of it. I powered on,
sshed to the Pi and ran the motion_detect script, came back inside
and ran a tail -f on the output file.
I had dinner and worked on other things, occasionally checking the
output -- nothing! Finally I sshed to the Pi and ran
and discovered the script was no longer running.
I started it again, this time keeping my connection to the Pi active
so I could see when the script died. Then I went outside to check the
hardware. Most of the peanut suet nuggets were gone -- animals had
definitely been by. I waved my hands in front of the camera a few
times to make sure it got some triggers.
Came back inside -- to discover that Python had gotten a segmentation
fault. It turns out that nifty GPIO.add_event_detect() code isn't all
that reliable, and can cause Python to crash and dump core. I ran it
a few more times and sure enough, it crashed pretty quickly every time.
GPIO.add_event_detect needs a bit more debugging,
and isn't safe to use in a program that has to run unattended.
Back to polling
Bummer! Fortunately, I had saved the polling version of my program, so
I hastily copied that back to the Pi and started things up again.
I triggered it a few times with my hand, and everything worked fine.
In fact, it ran all night and through the morning, with no problems
except the excessive number of false positives, already mentioned.
False positives weren't a problem at all during the night. I'm fairly
sure the problem happens when the sun starts hitting the ground. Then
there's a hot spot that marches along the ground, changing position in
a way that's all too obvious to the infra-red sensor.
I may try cross-checking between the PIR sensor and image changes from
the camera. But I'm not optimistic about that working: they both get
the most false positives at the same times, at dawn and dusk when the
shadow angle is changing rapidly. I suspect I'll have to find a
smarter solution, doing some image processing on the images as well
as cross-checking with the PIR sensor.
I've been uploading photos from my various tests here:
Tests of the
Raspberry Pi Night Vision Crittercam.
And as always, the code is on
scripts/motioncam with some basic documentation on my site:
a motion sensitive camera for Raspberry Pi or other Linux machines.
(I can't use github for the documentation because I can't seem to find
a way to get github to display html as anything other than source code.)