picam Aufnahme getrennt starten und stoppen

Python auf Einplatinencomputer wie Raspberry Pi, Banana Pi / Python für Micro-Controller
Antworten
x13a
User
Beiträge: 5
Registriert: Donnerstag 13. August 2015, 14:11

Und zwar in meinem Script wird die Aufnahme in zwei if Anweisungen gestartet und gestopt.
In der Theorie funktioniert das Script (also wenn ich die Befehle (camera.start_recording camera.stop_recording) durch print("****") ersetze )

Jemand eine Idee was hir Schief leuft bzw. geht das überhaut ?

Code: Alles auswählen

                
                if num > NO:   # Script wurde gekürzt 
                    if play == 1:
                        d = time.strftime("Aufnahmen/%d-%m-%Y")
                        if not os.path.exists(d):
                            os.makedirs(d)
                        print("Video start")
                        fileName = time.strftime("Aufnahmen/%d-%m-%Y/%d-%m-%Y_%H:%M:%S_%02d.h264")
                        camera.start_recording(fileName)
                        play = 0

                if mo == 1:
                    print("Video stop")
                    camera.stop_recording()
                    play = 0
Als Error bekomme ich:

Code: Alles auswählen

  File "/usr/lib/python2.7/dist-packages/picamera/camera.py", line 2271, in _set_resolution
    self._check_recording_stopped()
  File "/usr/lib/python2.7/dist-packages/picamera/camera.py", line 772, in _check_recording_stopped
    raise PiCameraRuntimeError("Recording is currently running")
picamera.exc.PiCameraRuntimeError: Recording is currently running
BlackJack

@x13a: Nicht so viel kürzen wäre eine gute Idee, also nicht beim Skript, denn so ist mir jetzt nicht klar wo die Werte für die Namen herkommen und in welcher Reihenfolge dort was ausgeführt wird. Und wo der Fehler denn nun genau auftritt können wir auch nicht sehen weil Du den Traceback gekürzt hast.

`num`, `NO`, `mo`, und `d` sind übrigens keine guten Namen. Bei `d` kann man sagen was der Wert bedeutet, weil der zufällig gerade im gezeigten Teil definiert wird und man das an dem Ausdruck sehen kann. Wenn man die Zuweisungen nicht sieht, dann muss man raten was das wohl alles bedeuten mag.

Eine wichtige Sache beim Programmieren ist es Wiederholungen, sowohl im Code als auch in den Daten zu vermeiden, weil ein Programm damit schlechter wartbar und fehleranfälliger wird. Wenn man hier beispielsweise den Pfad zu den Aufnahmen ändern möchte, muss man alle Vorkommen von dem Pfad in den Zeichenketten suchen und darf a) nicht vergessen alle zu ändern, und muss b) aufpassen das man alle Änderungen exakt gleich macht, damit die nicht auseinanderdriften.

Wenn `play` nur die Werte 0 und 1 annehmen kann, dann würde man das eher durch einen Wahrheitswert, also `True` und `False` ausdrücken. Das ist eindeutiger und ein Leser muss sich nicht fragen ob der Namen vielleicht auch den Wert 2 oder 3 annehmen kann, und was das dann wohl bedeuten mag. `is_playing` würde in dem Fall dann auch einen etwas besseren Bezeichner abgeben, denn `play` klingt mehr nach dem Namen einer Funktion oder Methode zum abspielen.

Per Konvention werden Namen für ”normale” Werte in Python klein_mit_unterstrichen geschrieben. Also `file_name` statt `fileName`, wobei hier natürlich greift dass das eigentlich `filename` heissen müsste, denn das ist im Englischen genau wie im Deutschen *ein* Wort.

Anstelle des `time`-Moduls würde ich das `datetime`-Modul verwenden. Die Objekte kann man in normaler Zeichenkettenformatierung mit der `format()`-Methode verwenden und dort beim Platzhalter auch die von `time.strftime()` bekannten Formatierungsangaben verwenden.
x13a
User
Beiträge: 5
Registriert: Donnerstag 13. August 2015, 14:11

@BlackJack: Jap stimmt,

Code: Alles auswählen

#! /usr/bin/env python3

import io
import os
import time
import picamera
import numpy as np

threshold = 30
minPixelsChanged = int(25000) 
print("minPixelsChanged=",minPixelsChanged) 

print("Creating in-memory stream")
stream = io.BytesIO()
step = 1
numImages = 1
motion = 0
rec = 0

with picamera.PiCamera() as camera:
    time.sleep(1) #warm up
    try:
        while threshold > 0:
            camera.resolution = (1440,1080)
            
            #print ('Capture ' , numImages)
            if step == 1:
                stream.seek(0)
                camera.capture(stream, 'rgba',True)
                data1 = np.fromstring(stream.getvalue(), dtype=np.uint8)
                step = 2
            else:
                stream.seek(0)
                camera.capture(stream, 'rgba',True)
                data2 = np.fromstring(stream.getvalue(), dtype=np.uint8)
                step = 1
            numImages = numImages + 1


            if numImages > 4: #warm up
                motion = motion - 1
                print(motion)
                data3 = np.abs(data1 - data2)                                               
                numTriggers = np.count_nonzero(data3 > threshold) / 4 / threshold           

                print("Trigger cnt=",numTriggers)

                if numTriggers > minPixelsChanged:
                    motion = 10
                    
                    if rec == 0:
                        d = time.strftime("Aufnahmen/%d-%m-%Y")
                        if not os.path.exists(d):
                            os.makedirs(d)

                        print("Video start")
                        fileName = time.strftime("Aufnahmen/%d-%m-%Y/%d-%m-%Y_%H:%M:%S_%02d.h264")
                        camera.start_recording(fileName)
                        rec = 1

                if motion == 0:
                    print("Video stop")
                    camera.stop_recording()
                    rec = 0

    finally:
        camera.close()
        print("Program Terminated")
Sirius3
User
Beiträge: 18227
Registriert: Sonntag 21. Oktober 2012, 17:20

@x13a: Was stimmt? Bei so langem Code ist es sinnvoll, diesen in mehrere Funktionen einzuteilen. Dann gibt es auch keine 6 Ebenen von Einrückung. Und man muß den "warm-up" nicht in die Arbeitsschleife schreiben. Und man sieht besser, welche Variablen wo gebraucht werden.
BlackJack

@x13a: Auf Modulebene sollte nach Möglichkeit kein Code stehen der nicht Konstanten, Funktionen, und Klassen definiert. Also auch das Hauptprogramm steht üblicherweise in einer Funktion, die üblicherweise `main()` heisst, und wird durch ein bestimmtes Idiom aufgerufen das verhindert das diese Funktion auch aufgerufen wird wenn man das Modul importiert statt es als Programm auszuführen. So kann man es importieren ohne das etwas passiert und zum Beispiel einzelne Funktionen isoliert testen bei der Entwicklung oder der Fehlersuche. Und man kann automatisierte Tests gegen den Code schreiben und/oder Funktionen in anderen Modulen wiederverwenden. Einige Werkzeuge, zum Beispiel zum extrahieren von Dokumentation aus Modulen, erwarten ebenfalls das Module ohne Nebeneffekte importiert werden können.

Zur Namensschreibweise und Quelltextformatierung (Einrückung, Leerzeichensetzung, und so weiter) lohnt ein Blick in den Style Guide for Python Code. Konstanten werden Beispielweise per Konvention durchgehend in Grossbuchstaben geschrieben.

`int()` mit einer literalen ganzen Zahl als Argument aufzurufen hat keinen Effekt. Es bleibt die gleiche ganze Zahl die man als Argument übergibt, also kann man die auch gleich ohne den Funktionsaufruf hinschreiben.

Anstelle von dem Muster `numThings` würde ich `thing_count` für Zähler verwenden. Dann hat man einen vollständig ausgeschriebenen Namen. Abkürzungen sollte man vermeiden solange sie nicht allgemein bekannt sind. Bei `numTriggers` steht die andere Variante ja im Grunde sogar im beschreibenden Text der mit `print()` ausgegeben wird.

Beim Programmieren wird mit der Zählung üblicherweise bei 0 angefangen. Was zum Beispiel bei `numImages` mehr Sinn machen würde, denn an der Stelle wo der Name an 1 gebunden wird, wurden noch gar keine Bilder geschossen, also müsste der Zähler an der Stelle 0 Bilder zählen und nicht 1.

Das ``try``/``finally`` ist überflüssig denn das ``with`` sorgt bereits dafür dass das Kamera-Objekt geschlossen wird wenn der ``with``-Block verlassen wird — egal warum der verlassen wird. Damit hat man gleich mal eine Einrückebene weniger.

Die ``while``-Bedingung ist unnötig kompliziert. Das ist eine Endlosschleife, was man aber erst erkennen kann nachdem man das gesamte Programm untersucht hat und festgestellt hat das sich `threshold` niemals ändert und immer grösser als 0 ist, also die Bedingung *immer* wahr ist. Da ist ``while True:`` doch etwas deutlicher. ;-)

Muss man die Kameraauflösung tatsächlich in jedem Schleifendurchlauf erneut auf den die gleichen Werte setzen?

Das schiessen eines Fotos steht zweimal fast identisch im Quelltext. Da wäre schon der erste offensichtliche Kandidat für eine eigene Funktion.

`step` kann man sich sparen wenn man das erste Bild einfach *vor* der Schleife schiesst. Und statt `data1` könnte man das dann `reference_image` nennen, dann weiss der Leser auch was die Daten bedeuten und nicht nur dass es Daten sind und das es wegen der Nummer wohl auch noch andere Daten geben wird.

Da immer wieder Bilder geschossen werden und die Schleife sozusagen über diese Bilder geht, könnte man einen Generator schreiben der genau das tut — das Bilderschiessen als iterierbares Objekt über diese Bilder zur Verfügung stellen.

Den „warm up“ kann man dann auch aus der Verarbeitungsschleife heraus ziehen und einfach vier Bilder aus dem Iterator holen und verwerfen. Damit hätte man dann in der Verarbeitungsschleife ein ``if`` und damit eine Einrückebene weniger.

Das identische Teile des Pfades für die Aufnahme zweimal erstellt werden ist nicht nur unschöne Code- und Datenwiederholung sondern auch ein Programmfehler denn für beide Erstellungen wird die jeweils zu dem Zeitpunkt aktuelle Zeit verwendet. Wenn der Einbrecher also ganz knapp vor Mitternacht kommt, wird ein Verzeichnis für den Tag vor Mitternacht erstellt und dann kann es passieren das versucht wird das Video in ein Verzeichnis für den Tag nach Mitternacht zu speichern welches noch gar nicht existiert.

Ich hoffe es sinnerhaltent, also noch mit Deinen logischen Fehlern, so umgeschrieben zu haben (ungetestet):

Code: Alles auswählen

#!/usr/bin/env python
from __future__ import absolute_import, division, print_function
import errno
import io
import os
from datetime import datetime as DateTime
from functools import partial
from itertools import islice
from time import sleep

import numpy as np
from picamera import PiCamera


CAMERA_RESOLUTION = 1440, 1080
THRESHOLD = 30
MIN_PIXELS_CHANGED = 25000
RECORDING_PATH_TEMPLATE = 'Aufnahmen/{0:%d-%m-%Y}'
RECORDING_FILENAME_TEMPLATE = '{0:%d-%m-%Y_%H:%M:%S_%02d}.h264'


def get_image_array(camera):
    stream = io.BytesIO()
    camera.capture(stream, 'rgba', True)
    return np.fromstring(stream.getvalue(), dtype=np.uint8)


def count_triggers(reference_image, image):
    return (
        np.count_nonzero(np.abs(reference_image - image) > THRESHOLD)
        / 4
        / THRESHOLD
    )


def start_recording(camera, path_template, filename_template):
    now = DateTime.now()
    path = path_template.format(now)
    try:
        os.makedirs(path)
    except OSError as error:
        if error.errno != errno.EEXIST:
            raise
    camera.start_recording(os.path.join(path, filename_template.format(now)))


def main():
    print('MIN_PIXELS_CHANGED:', MIN_PIXELS_CHANGED)
    with PiCamera() as camera:
        sleep(1)  # Warm up.
        camera.resolution = CAMERA_RESOLUTION
        images = iter(partial(get_image_array, camera), None)
        # 
        # TODO Like in the original the reference image is the very first image
        #   shot, i.e. it precedes the discarded warm up images.  Is that really
        #   intendet?
        # 
        reference_image = next(images)
        # 
        # Shoot some images just for warm up.
        # 
        for _ in islice(images, 4):
            pass

        motion = 0
        is_recording = False
        for current_image in images:
            trigger_count = count_triggers(reference_image, current_image)
            print('Trigger count:', trigger_count)
            if trigger_count > MIN_PIXELS_CHANGED:
                motion = 10
                if not is_recording:
                    print('Video start')
                    start_recording(
                        camera,
                        RECORDING_PATH_TEMPLATE,
                        RECORDING_FILENAME_TEMPLATE,
                    )
                    is_recording = True

            motion -= 1
            print(motion)
            if motion == 0:
                print('Video stop')
                camera.stop_recording()
                is_recording = False

    print('Program Terminated.')


if __name__ == '__main__':
    main()
Wenn ich das richtig sehe wird bei jedem Schleifendurchlauf ein Bild geschossen. Auch wenn die Videoaufnahme läuft. Gibt das die Hardware beziehungsweise `picamera` her, das man während einer Videoaufnahme gleichzeitig Fotos schiessen kann?

Edit: Code aus Hauptfunktion in Funktionen herausgezogen.

Edit2: Habe noch einen Fehler gefunden: Das zählen der Trigger ist fehlerhaft. Denk mal darüber nach warum der `np.abs()`-Aufruf sinnlos ist weil da niemals negative Zahlen auftreten können und was an der Stelle *stattdessen* passiert!
x13a
User
Beiträge: 5
Registriert: Donnerstag 13. August 2015, 14:11

@BlackJack: Danke für deine Antwort !!!
Wenn ich das richtig sehe wird bei jedem Schleifendurchlauf ein Bild geschossen. Auch wenn die Videoaufnahme läuft. Gibt das die Hardware beziehungsweise `picamera` her, das man während einer Videoaufnahme gleichzeitig Fotos schiessen kann?
An das habe ich jetzt gar nicht gedacht. Werd es mal googeln
Antworten