Raspberry Video Camera – Teil 12: SW Trigger per Bewegungssensor

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:

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:

4 Kommentare

  1. Yvonne Bender

    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?

    Antworten
    1. Helmut (Beitrag Autor)

      Hallo Yvonne, vielen Dank für das Lob.
      zum Bewegungssensor und seinen Einstellmöglichkeiten hab ich einen eigenen Artikel gemacht.

      Antworten
  2. Yvonne Bender

    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?

    Antworten
    1. Helmut (Beitrag Autor)

      Da hilft vermutlich nur Einlesen in die Picamera Dokumentation.

      Antworten

Schreiben Sie einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert