ESP32-CAM – Bewegung ermitteln mit Python3

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

Bewegung mit Python3 an einer ESP32-CAM erkennen
Bewegung mit Python3 an einer ESP32-CAM erkennen

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:

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.

ESP32-CAM WebInterface verfügbare Auflösungen
ESP32-CAM Webinterface verfügbare Auflösungen

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.

Kommentar hinterlassen

Deine E-Mail-Adresse wird nicht veröffentlicht.