@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!