Wie man eine Bewegung an einer ESP32-CAM mithilfe von Python3 erkennt, möchte ich dir gerne in diesem Beitrag zeigen.

Wie man mit Python ein Bild von einer ESP32-CAM auf dem Computer speichert, habe ich dir bereits im Beitrag Python – ESP32 CAM Bild speichern gezeigt.
Die ESP32-CAM habe ich dir bereits im Beitrag Einrichten der ESP32-CAM und erster betrieb gezeigt und die verfügbaren Kameralinsen im Beitrag ESP32 CAM – Vergleich der verfügbaren Kameralinsen vorgestellt.
Benötigte Ressourcen für dieses Projekt
Wenn du dieses Projekt nachbauen möchtest, dann benötigst du:
- eine ESP32-CAM*,
- ein FTDI Modul* mit Breadboarkabel*, oder
- ein ESP32-CAM-MB*
Hinweis von mir: Die mit einem Sternchen (*) markierten Links sind Affiliate-Links. Wenn du über diese Links einkaufst, erhalte ich eine kleine Provision, die dazu beiträgt, diesen Blog zu unterstützen. Der Preis für dich bleibt dabei unverändert. Vielen Dank für deine Unterstützung!



Die ESP32-CAM wird mit einer einfachen 66° Linse ausgeliefert, welche für den nachfolgenden Aufbau gut funktionieren würde, jedoch möchte ich einen größeren Winkel kontrollieren und verwende eine 120° Linse.


Zum Programmieren des Python3 Programmes verwende ich die Community Edition von PyCharm.
Wie wird eine Bewegung erkannt?
Im Grunde muss man nur zwei Bilder in einem Abstand von x Minuten / Sekunden aufnehmen und diese beiden Bilder miteinander vergleichen. Der Vergleich jeweils ist der entscheidende Part.




Pixel vergleichen
Am einfachsten (jedenfalls dachte ich es) ist es, die beiden Bilder Pixel für Pixel zu vergleichen.
Da die ESP32-CAM jedoch selbst bei gleich guten / schlechten Lichtverhältnissen manchmal unterschiedlich gute Bilder liefert, muss hier ein Offset in den RGB Wert des Pixels eingerechnet werden.
Selbst aber mit dem Offset am RGB Wert wurde dann nicht zuverlässig ein Unterschied erkannt und ich habe diese Idee wieder „eingestampft“.
Bilder vergleichen mit OpenCV
Das Modul OpenCV bietet Funktionen zum Vergleichen von Bildern. Das nachfolgende Programm habe ich von der Seite Image Difference with OpenCV and Python von pyimagesearch.com entnommen und für die Bedürfnisse an dieses Projekt angepasst.
Programmieren
Im Nachfolgenden wollen wir nun Schritt für Schritt das Programm aufbauen und Bilder von der ESP32-CAM vergleichen.
Schritt 1 – Bilder von der ESP32-CAM auf dem Computer speichern
Wie bereits erwähnt, habe ich dir bereits gezeigt, wie man ein Bild von der ESP32 CAM auf dem Computer speichern kann.
import requests from PIL import Image from io import BytesIO url = 'http://192.168.178.33/capture' output = 'data/output.jpg' r = requests.get(url) img = Image.open(BytesIO(r.content)) img.save(output)
Diese paar Zeilen lagern wir nun in eine Funktion aus und geben dann den Dateinamen des Bildes zurück.
outputFolder = 'data/'; outputFileExt = '.jpg'; def takePicture(): r = requests.get(url) img = Image.open(BytesIO(r.content)) timestamp = round(time.time()); outputfilename = outputFolder + str(timestamp) + outputFileExt; img.save(outputfilename); return outputfilename;
Anpassen der Auflösung
Über das Webinterfache können wir einen Stream starten und auch Standbilder speichern, dabei wird die Auflösung über eine Dropdownliste eingestellt.
Die Bilder werden aber in unserem Programm zunächst im Defaultformat von 400×276 Pixel aufgenommen. Wenn wir eine andere Auflösung wollen, so müssen wir diese Einstellung über einen HTTP Request setzen.
import requests """ Index | Auflösung 13 | 1600 x 1200 12 | 1280 x 1024 11 | 1280 x 720 10 | 1024 x 768 9 | 800 x 600 8 | 640 x 480 7 | 480 x 320 6 | 400 x 296 5 | 320 x 240 4 | 240 x 240 3 | 240 x 176 2 | 176 x 144 1 | 160 x 120 0 | 96 x 96 """ espCamIpAdress = 'http://192.168.178.33'; def setResolution(): url = espCamIpAdress + '/control?var=framesize&val=' + str(index); r = requests.get(url) if r.status_code > 200: print("Fehler", str(r.status_code), "bei setzen der Auflösung an der ESP32 CAM!") time.sleep(1)
Schritt 2 – Zeit x warten und ein weiteres Bild aufnehmen
Mit der Funktion „sleep“ aus dem Modul time können wir das Programm eine Zeit x Pausieren.
import time time.sleep(0.5) //500 ms. time.sleep(1) //1 Sekunde time.sleep(60) //1 Minute time.sleep(3600) //1 Stunde time.sleep(86400) //1 Tag
Wenn wir also das nächste Bild in 5 Minuten aufnehmen möchten, müssen wir schreiben:
import time time.sleep(60 * 5) //5 Minuten Pause, oder time.sleep(300) //5 Minuten Pause
Nehmen wir nun 2 Bilder mit einem Abstand von 300 Sekunden bzw. 5 Minuten auf.
print("Take first image.") first_image = takePicture() print("Wait 300 seconds.") time.sleep(300); print("Take second image.") second_image = takePicture()
Schritt 3 – Bilder vergleichen
Nun vergleichen wir diese beiden Bilder mit dem Modul OpenCV.
Die beiden Bilder werden dazu in Graustufen umgewandelt und erneut eingelesen.
image1 = cv2.cvtColor(cv2.imread(first_image), cv2.COLOR_BGR2GRAY) image2 = cv2.cvtColor(cv2.imread(takePicture()), cv2.COLOR_BGR2GRAY) (score, diff) = compare_ssim(image1, image2, full=True) diff = (diff * 255).astype("uint8") print("SSIM: {}".format(score))
Danach vergleichen wir diese mit der Funktion „compare_ssim“.
(score, diff) = compare_ssim(image1, image2, full=True)
Wenn du mehr über diese Funktion lesen möchtest dann empfehle ich dir die offizielle Seite https://scikit-image.org/docs/0.12.x/api/skimage.measure.html#skimage.measure.compare_ssim empfehlen.
Als Rückgabewert erhältst du zwei Werte in einem Tupel, zum einen den Score mit einem Gleitkommawert zwischen 0 und 1 und einen Wert „diff“ welcher in der verlinkten Dokumentation wie folgt beschrieben ist:
The gradient of the structural similarity index between X and Y [R262]. This is only returned if gradient is set to True.
Jedoch brauchen wir diesen in unserem Beispiel nicht! Da wir jedoch ein Tupel mit diesen beiden Werten von der Funktion zurückbekommen, müssen wir dieses initialisieren.
print("SSIM: {}".format(score))
Wir arbeiten nun mit dem Score weiter…
Schritt 4 – Aktion bei gleichen Bildern
Wenn die beiden Bilder gleich bzw. sehr identisch sind, wollen wir mit dem nächsten Zyklus des Programmes durchlaufen…
Schritt 5 – Aktion bei ungleichen Bildern
Nehmen wir an, dass ein Score kleiner als 0.8 für zwei ungleiche Bilder gilt. In diesem Fall wollen wir eine Aktion auslösen.
if score < 0.8: imageResolution = resolutions[13] setResolution() filename = takePicture() sendMail(filename)
In meinem Fall ändere ich die Auflösung auf das Maximum von 1600 x 1200 Pixel, mache ein Foto und versende dieses per E-Mail an mich selber. Wie man eine E-Mail aus einem Python3 Skript sendet, habe ich dir bereits im gleichnamigen Beitrag Senden einer E-Mail aus einem Python3 Skript ausführlich erläutert.
Natürlich könnten wir auch einen GPIO aktivieren und so eine Sirene, Lampe etc. steuern und somit darauf reagieren.
import smtplib from email.mime.multipart import MIMEMultipart from email.mime.text import MIMEText from email.mime.application import MIMEApplication from os.path import basename def sendMail(filename): # Betreff & Inhalt subject = "Hallo Welt!" body = "Hier steht ein Text." # Message Objekt für die E-Mail # später kann an dieses Objekt eine # oder mehrere Dateien angehängt # werden. msg = MIMEMultipart() msg['Subject'] = subject msg['From'] = sender msg['To'] = reciever msg.attach(MIMEText(body, 'plain')) with open(filename, "rb") as file: part = MIMEApplication( file.read(), Name=basename(filename) ) # After the file is closed part['Content-Disposition'] = 'attachment; filename="%s"' % basename(filename) msg.attach(part) # Erzeugen einer Mail Session smtpObj = smtplib.SMTP(smtpServer, smtpPort) # Debuginformationen auf der Konsole ausgeben smtpObj.set_debuglevel(1) # Wenn der Server eine Authentifizierung benötigt dann... smtpObj.starttls() smtpObj.login(username, password) # absenden der E-Mail smtpObj.sendmail(sender, reciever, msg.as_string()) smtpObj.close()
In meinem Fall habe ich die Mail mit Anhang an meinen Account „info@draeger-it.blog“ von der Adresse „esp32_mailer@web.de“ gesendet.

Die Adresse „esp32_mailer@web.de“ habe ich mir eigens für diesen Beitrag angelegt.
Download des Python Skriptes zum erkennen einer Bewegung an der ESP32-CAM
Hier nun das komplette Skript um eine Bewegung an der ESP32-CAM mit Python3 zu erkennen und bei unterschiedlichen Bildern eine E-Mail zu senden.
Hi Stefan!
Das sieht sehr cool aus! Weißt du, ob es auch die Möglichkeit gibt, die Bildwerte im Stream auszulesen, ohne das Bild zu speichern?
Ich arbeite mit der Arduino IDE und würde gerne LEDs steuern, jenachdem wie das Kamerabild aussieht.
Zum Beispiel dachte ich an den mittleren Grauwert. Wenn er dunkler ist, werden die LEDs dunkler und umgekehrt.
Das wäre super, wenn das in Echtzeit ginge, habe dazu aber noch nichts sinnvolles gefunden.
LG
Nane
Hi,
wie das in Echtzeit geht weiß ich derzeit leider nicht, es gibt aber auch eine einfache VGA Kamera für den Arduino.
Bei der könnte ich mir das gut vorstellen.
Gruß, Stefan