Die Hardware ist fertig, die Kamerasoftware auch – jetzt fehlt nur noch der Auslöser – ein automatischer versteht sich. Dafür gibt es viele Möglichkeiten, wie Bewegungserkennung, Bildauswertung oder eine Lichtschranke, einige werde ich später noch eingehender vorstellen. Den Anfang macht eine einfach zu realisierende Variante eines Kamera-Triggers, nämlich ein PIR-Bewegungssensor. Der soll die Bewegung eines Eichhörnchens erkennen, die Info an den Raspberry Pi weiterleiten und dort wird ein kleines Python-Programm die Schnittstelle zum Kameraprogramm ansteuern. Der Nachlauf der Kamera, also die Zeit, die die Kamera noch aufzeichnet nachdem keine Bewegung mehr erkannt wird, wird in dieser Variante auch vom Bewegungssensor gesteuert. Die Nachlaufzeit lässt sich dort mit einem Potentiometer einstellen.
Zuerst aber wieder ein Oachkatzl-Video. Mehr davon gibts in meinem YouTube-Kanal.
Zur Wahrung deiner Privatsphäre wird erst eine Verbindung zu YouTube hergestelt, wenn du den Abspielbutton betätigst.
PIR Bewegungssensor HC-SR501
Wer nicht alle vorhergehenden Artikel in dieser Serie gelesen hat, dem empfehle ich zum Einstieg zumindest diese drei:
- Als Grundlage die Seite über mögliche Aufnahmeauslöser mit ihren Vor- und Nachteilen.
- Die Hardwarebeschreibung zum PIR-Sensor HC-SR501. Hier sind alle Einstellmöglichkeiten dokumentiert und der Anschluss an den Raspberry Pi.
- Und den vorhergegangenen Artikel über die Ansteuerung der Kamera per Python. Hier ist auch die Software-Schnittstelle zwischen Trigger und Kamera beschrieben.
Rechts auch nochmal das Bild zu den Anschlüssen und den Einstellmöglichkeiten des Bewegungssensors HC-SR501. Im nachfolgenden Python-Programm wird davon ausgegangen, dass wir mit dem Jumper den Mode II eingestellt haben und dass der Signalausgang des HC-SR501 mit Pin 12 (entspricht GPIO 18) am Raspberry Pi verbunden ist.
Sensorabfrage per Programm
Der Bewegungssensor liefert ein Low-Signal (logisch 0) an den GPIO-Pin des Raspberry Pi wenn keine Bewegung erkannt wird. Umgekehrt legt er ein High-Signal (logisch 1) an, sobald und so lange eine Bewegung detektiert wird und sogar noch ein wenig länger, entsprechend der eingestellten Ausschaltverzögerung (=Nachlaufzeit).
Prinzipiell haben wir zwei Möglichkeiten das Signal des PIR-Sensors programmiertechnisch auszuwerten: per Polling und per Interrupt.
Polling
Bei diesem Verfahren prüft das Programm ständig den GPIO-Pin ab, ob sich der Signalstatus von 0 auf 1 verändert hat. Das muss einigermaßen schnell hintereinander erfolgen, um einen Signalwechsel auch zeitnah zu erkennen. Wer sich für eine Programmumsetzung interessiert, wird zum Beispiel hier fündig. Dieses Verfahren ist aber eher unschön, weil das Programm ständig am Arbeiten ist, nur um den Pin abzufragen. Wir verwenden deshalb besser die folgende Methode.
Interrupt
Hier überlassen wir es dem Betriebssystem, uns eine Meldung zu geben, wenn sich der Signalzustand am GPIO-Pin ändert. Das Hardwareereignis erzeugt einen Interrupt (also eine Unterbrechung), den das Betriebssystem an unser Python-Programm weiterleiten kann. Wir sparen uns also das aufwendige Polling und lassen den Interrupt eine von uns definierte Funktion starten (Callback-Funktion).
PIR in Python (motioninterrupt.py)
So schaut das Interrupt-Programm in Python aus, das wir unter dem Namen motioninterrupt.py
im Homeverzeichnis (von User Pi) ablegen, also in /home/pi
:
import RPi.GPIO as GPIO
import time
import datetime
import os
trigger_fileextension = '.trg'
trigger_path = 'trigger/'
trigger = ""
# define GPIO mode, pin and use pin as input
GPIO.setmode(GPIO.BOARD)
GPIO_PIR = 12
GPIO.setup(GPIO_PIR,GPIO.IN)
print("PIR started")
print("Waiting for PIR to get idle ...")
# loop until PIR == 0
while GPIO.input(GPIO_PIR) != 0:
time.sleep(0.2)
print("Now ready for motion detection")
# Callback-Function
def PIR_signal(GPIO_PIR):
global trigger
triggertime = datetime.datetime.now()
# Signal from low to high
if GPIO.input(GPIO_PIR) == 1:
trigger = triggertime.strftime("%Y-%m-%d-%H-%M-%S")
open(trigger_path+trigger+trigger_fileextension, 'w').close()
print("{0} - Motion detected".format(trigger))
# Signal from high to low
else:
os.remove(trigger_path+trigger+trigger_fileextension)
print("{0:%Y-%m-%d-%H-%M-%S} - Motion terminated".format(triggertime))
print()
# Event definition: on gpio state change call PIR_signal function
GPIO.add_event_detect(GPIO_PIR, GPIO.BOTH, callback=PIR_signal)
print("{0:%Y-%m-%d-%H-%M-%S} - Waiting for motion".format(datetime.datetime.now()))
# Main loop
while True:
time.sleep(60)
In den ersten vier Zeilen werden die benötigten Module geladen, darunter auch das RPi.GPIO
Modul, das die Interaktion mit mit den IO-Pins ermöglicht. Das python3-rpi.gpio Modul haben wir zuvor auf dem Raspberry Pi installiert. Es ist üblich, es beim Import unter dem Namen GPIO
zugänglich zu machen. In den folgenden drei Zeilen definieren wir die Dateierweiterung und das Verzeichnis für die Triggerdatei – das kennen wir bereits vom Kamera-Programm. Und wir erzeugen eine globale Variable trigger
, die den Triggerstatus in Form eines Datum-Uhrzeit-Strings halten soll. Am Anfang ist der String leer.
Nun geht es an die Konfiguration des GPIO-Pins. Mit setmode
definieren wir, das wir GPIO-Pins mit ihrer Pinnummer auf der Stiftleiste ansprechen wollen. Dann legen wir eine Variable GPIO_PIR
an und geben ihr gleich die verwendete Pinnummer. In diesem Beispiel ist das Pin 12, aber es können natürlich auch andere GPIO-Pins verwendet werden. Und schließlich legen wir mit setup
fest, dass unser GPIO_PIR
als Eingang geschaltet wird.
Nach der Ausgabe einer Startmeldung geht es darum, einen definierten Ausgangszustand zu erreichen. Theoretisch könne der Bewegungssensor ja bereits jetzt ein High-Signal liefern, deshalb warten wir ab, bis ein Low-Zustand erreicht ist. Das passiert in einer kleinen While-Schleife. Die time.sleep
Verzögerung von 200ms im Schleifenkörper dient nur dazu, dass die Pin-Abfrage nicht ununterbrochen erfolgt, sondern mit kleinen zeitlichen Abständen.
Jetzt kommt der Kern des Programms, die Callback-Funktion mit dem Namen PIR-signal
. Sie soll immer dann aufgerufen werden, wenn ein Interrupt erzeugt wurde. Die Funktion macht sich zuerst mit global
die Variable trigger
verfügbar. In der wird der Status des Triggers gespeichert, um ihn auch zwischen zwei Aufrufen der Callback-Funktion nicht zu verlieren. Dann wird die aktuelle Zeit abgefragt und in der Variablen triggertime
gespeichert.
Es müssen nun zwei Fälle unterschieden werden: Der Wechsel von Low nach High und umgekehrt der ‚Wechsel von High nach Low. In beiden Fällen wird der Interrupt ausgelöst. Deshalb fragen wir mit input
den GPIO_PIR
Signalzustand ab und machen mit if ... else
eine kleine Fallunterscheidung. Im Fall, dass das Inputsignal 1 also High ist, wird eine Triggerdatei erzeugt. Die kommt ins Verzeichnis trigger
und hat folgendes Format:
YYYY-mm-dd-HH-MM-SS.trg
also zum Beispiel:
2016-08-26-08-05-00.trg
Der Timestamp repräsentiert also den Zeitpunkt des ersten Erkennens einer Bewegung. Das Python-Kamera-Programm wertet den Dateinamen dann aus und startet entsprechend die Videoaufzeichnung. Einen Inhalt hat diese Datei nicht. Im Else-Zweig der Fallunterscheidung, wenn das Signal Low (0) ist, wird die Triggerdatei wieder gelöscht. Damit haben wir die Callback-Funktion definiert.
Jetzt müssen wir nur noch eine Event-Verarbeitung (einen Listener) einrichten. Hier wird die Callback-Funktion einem Interruptereignis des GPIO-Pins zugeordnet. Wir machen das mit add_event_detect
und übergeben die Pin-Nummer und den Namen der Callback-Funktion. Der Parameter GPIO.BOTH besagt, dass auf beide Ereignisse reagiert werden soll, auf eine ansteigende Flanke und auf eine fallende, denn beide Events müssen wir auswerten.
Nun ist alles definiert, wir kommen quasi zum Hauptteil des Programms. Und wir sehen, dass hier nichts weiter passiert, als dass sich das Programm in einer Endlosschleife immer wieder für 60 Sekunden schlafen legt. Es wird allerdings geweckt, sobald ein Interrupt auftritt, der dann durch die Callback-Funktion abgearbeitet wird.
Testen
Nennen wir das Triggerprogramm motioninterrupt.py
und starten wir es in einer SSH-Session einfach mal:
$ python3 motioninterrupt.py
PIR started
Waiting for PIR to get idle ...
Now ready for motion detection
2017-04-02-15-43-12 - Waiting for motion
2017-04-02-15-45-08 - Motion detected
2017-04-02-15-45-22 - Motion terminated
Kurz die Hand vor den Bewegungssensor gehalten ergibt dann in etwa diese Ausgabe. Gleichzeitig befindet sich im Zeitraum von Motion detected bis Motion terminated im Verzeichnis trigger
eine Triggerdatei 2017-04-02-15-45-08.trg.
Mit Strg-C können wir das Programm wieder abbrechen.
Nachdem wir das Programm für die Kamera bereits zur Verfügung haben, können wir nun das Gesamtsystem zusammenfügen. Dazu öffnen wir entweder zwei SSH-Fenster, oder wir schicken das Kamera-Programm in den Hintergrund, dann funktioniert es auch innerhalb einer einzigen SSH-Session:
$ python3 record.py &
[1] 16054
Ready for trigger
$
Man beachte das kaufmännische Und am Ende des Befehls. Das bewirkt, dass das aufgerufene Kommando im Hintergrund ausgeführt wird und auf der Konsole weitere Eingaben möglich sind. Die Ausgaben des Hintergrundprogramms kommen aber trotzdem auf der Konsole an, was zu etwas Durcheinander führen kann. Die Zahl, die nach dem Programmaufruf vom Betriebssystem ausgegeben wird ist die Prozessnummer des eben gestarteten Programms. Mit kill 16054
könnten wir den Prozess später wieder beenden. Jetzt starten wir im Vordergrund noch motioninterrupt.py
und simulieren eine Bewegung vor der Kamera:
$ python3 motioninterrupt.py
PIR started
Waiting for PIR to get idle ...
Now ready for motion detection
2017-04-02-16-05-40 - Waiting for motion
2017-04-02-16-06-27 - Motion detected
Trigger detected!
2017-04-02-16-06-27
11.08883
2017-04-02-16-06-48 - Motion terminated
Trigger stopped!
Connect files
Files connected
Appending file Videos/b-2017-04-02-16-06-27.h264
No suitable destination track found - creating new one (type vide)
Appending file Videos/a-2017-04-02-16-06-27.h264
Saving Videos/2017-04-02-16-06-27-raspi166.mp4: 0.500 secs Interleaving
Wir sehen hier die Ausgaben von drei Programmen, motioninterrupt.py, record.py
und postprocess.py
, also Trigger-, Aufnahme- und Videonachbearbeitungsprogramm. Und im Verzeichnis Videos
befindet sich nun eine Videodatei mit dem Namen 2017-04-02-16-06-27-raspi166.mp4
.
Damit ist unser erstes lauffähiges Videosystem fertig gestellt, auch wenn wir die Programme im Moment noch von Hand starten müssen. Später werden wir das automatisieren.
Abgleich
Spätestens jetzt müssen wir uns noch über die Stellschrauben des PIR-Sensors hermachen. Eine Nachlaufzeit zwischen 20 Sekunden und einer halben Minute sollte reichen, damit das Video nicht gleich abbricht, wenn das Tier sich kurz nicht bewegt. Die Empfindlichkeit würde ich zuerst ganz zurückdrehen. Für ein Eichhörnchen in ca. 1m Entfernung ist das immer noch bei weitem empfindlich genug. Bei anderen Konstellationen und größeren Entfernungen ist schon ein wenig Tuning erforderlich. Es soll ja das gewünschte Objekt auslösen und nicht jede Bewegung im Hintergrund. Wobei der Bewegungssensor nicht in allen Fällen glücklich machen wird. Das geht gut bei einem statischen Hintergrund, einer Mauer zum Beispiel. Ein Baum, so wie in meinem Fall, ist denkbar schlecht. Jeder Windstoß, der einen Ast bewegt, führt zu einem Video, so dass am Ende des Tages schon mal 30 Fehlauslösungen auf ein gutes Oachkatzlvideo kommen können. Da gibt es noch reichlich Verbesserungspotential, aber das Prinzip eines Auslösetriggers ist anhand dieses kleinen Programms gut erkennbar.
Weitere Artikel in dieser Kategorie:
- Raspberry Video Camera – Teil 1: Oachkatzl-Cam
- Raspberry Video Camera – Teil 2: Komponenten & Konzepte
- Raspberry Video Camera – Teil 3: Raspberry Pi Kamera Modul V2.1
- Raspberry Video Camera – Teil 4: Aufnahmeauslöser
- Raspberry Video Camera – Teil 5: Passiver Infrarot Bewegungssensor
- Raspberry Video Camera – Teil 6: Stromversorgung
- Raspberry Video Camera – Teil 7: Spannungsregler 5V
- Raspberry Video Camera – Teil 8: Montage Modell 850
- Raspberry Video Camera – Teil 9: Montage Kamera Modul
- Raspberry Video Camera – Teil 10: SW Installation Betriebssystem und Module
- Raspberry Video Camera – Teil 11: SW Python für die Kamera
- Raspberry Video Camera – Teil 13: SW Autostart und Überwachung
- Raspberry Video Camera – Teil 14: SW Installation Computer Vision (OpenCV 3.2)
- Raspberry Video Camera – Teil 15: SW Einzelbilder exportieren für die Farberkennung
- Raspberry Video Camera – Teil 16: SW Trigger per Farberkennung
- Raspberry Video Camera – Teil 17: Exkurs – Wie Computer Farben sehen
- Raspberry Video Camera – Teil 18: SW Farbkalibrierung
- Raspberry Video Camera – Teil 19: SW Kombinationstrigger
- Raspberry Video Camera – Teil 20: Exkurs – Farbdarstellung per 2D-Histogramm
- Raspberry Video Camera – Teil 21: Konzept einer selbstlernenden Farberkennung
- Raspberry Video Camera – Teil 22: SW selbstlernende Farberkennung in Python
- Raspberry Video Camera – Teil 23: Verbesserung durch ROI und Aufnahmezeitbegrenzung
- Raspberry Video Camera – Teil 24: Anpassung von Programmparametern
- Raspberry Video Camera – Teil 25: Zweite Kamera
- Raspberry Video Camera – Teil 26: Optimierungen gegen Frame Drops
- Raspberry Video Camera – Teil 27: Was kostet der Spaß?
- Raspberry Video Camera – Teil 28: Kamera Modell 200 mit Raspberry Pi Zero
- Raspberry Video Camera – Teil 29: Stromversorgung für Kamera Modell 200
- Raspberry Video Camera – Teil 30: Software für Kamera Modell 200
Hallo,
Danke für das Tutorial, es ist unglaublich hilfreich! Ich habe nur aktuell das Problem, dass meine Aufnahmen viel zu schnell abbrechen. Ich benutze den Bewegungssensor und Sie haben geschrieben: „Spätestens jetzt müssen wir uns noch über die Stellschrauben des PIR-Sensors hermachen. Eine Nachlaufzeit zwischen 20 Sekunden und einer halben Minute sollte reichen, damit das Video nicht gleich abbricht, wenn das Tier sich kurz nicht bewegt.“ Leider habe ich noch nicht herausgefunden wo genau und wie dies eingestellt werden kann?
Hallo Yvonne, vielen Dank für das Lob.
zum Bewegungssensor und seinen Einstellmöglichkeiten hab ich einen eigenen Artikel gemacht.
Vielen Dank für die schnelle Antwort, das funktioniert nun!
Ich hätte allerdings noch eine weitere Frage: Meine Kamera zeigt leider etwas zu wenig Umgebung an und ich würde das Bild gerne breiter und länger machen.
Der Befehl in Teil 3: „raspistill -w 2000 -h 1000 -o image.jpg“ hat gut geklappt, wenn ich die Auflösung allerdings in record.py verändere funktioniert es nicht. Haben Sie vielleicht eine Idee?
Da hilft vermutlich nur Einlesen in die Picamera Dokumentation.