Skip to Content

Objekterkennung mit ESP32-CAM und YOLO

Objekterkennung mit ESP32-CAM und YOLO

In diesem Tutorial lernst du, wie man mit dem ESP32-CAM Modul und YOLO, einem Deep-Learning-System zur Objekterkennung, Objekte erkennt und klassifiziert.

Ich führe dich durch den Aufbau eines Projekts, bei dem der ESP32-CAM Bilder aufnimmt, als Webserver fungiert und die Bilder zur Analyse an einen Computer sendet. Der Computer nutzt YOLO, um Objekte zu erkennen und zu klassifizieren. Du lernst, die Hardware zusammenzubauen, die Kamera zu konfigurieren und JPEG-Bilder über HTTP bereitzustellen.

Am Ende hast du eine funktionierende Weboberfläche, um Schnappschüsse deines ESP32-CAM anzusehen, sowie ein Objekterkennungssystem für 80 verschiedene Objekttypen.

Benötigte Teile

Im Folgenden findest du die Komponenten, die für den Aufbau des Projekts erforderlich sind. Anstelle des FTDI Programmers könntest du auch ein Programmier-Shield für den ESP32-CAM verwenden, aber ich empfehle den erstgenannten.

ESP32-CAM

FTDI USB-TTL Adapter

USB data cable

USB-Datenkabel

Makerguides is a participant in affiliate advertising programs designed to provide a means for sites to earn advertising fees by linking to Amazon, AliExpress, Elecrow, and other sites. As an Affiliate we may earn from qualifying purchases.

Systemarchitektur

Das System, das wir bauen, besteht aus zwei Hauptkomponenten: 1) einem ESP32-CAM Modul, das Bilder aufnimmt und als Webserver fungiert, der die Bilder per WiFi sendet. 2) einem PC, auf dem das YOLO Objekterkennungssystem läuft. Es analysiert die Bilder und markiert die erkannten Objekte. Das folgende Diagramm gibt dir einen Überblick über die Systemarchitektur:

System Architecture Object Detection System
Systemarchitektur Objekterkennungssystem

Das folgende Bild zeigt ein Beispiel für eine Erkennung durch das System. Auf meinem unordentlichen Schreibtisch konnte YOLO eine Tasse mit 88% Sicherheit, eine Schere mit 68% und einen Laptop mit 59% erkennen. Du siehst die Begrenzungsrahmen um die Objekte mit ihren Namen und der Erkennungssicherheit oben links annotiert:

Objects detected with YOLO
Mit YOLO erkannte Objekte

In den nächsten Abschnitten lernst du, wie du den Webserver auf dem ESP32-CAM programmierst und wie du das YOLO-Erkennungssystem auf einem PC einrichtest.

Das ESP32-CAM Entwicklungsboard

Das ESP32-CAM Entwicklungsboard ist ein kompaktes Modul, das einen ESP32-S Chip, eine Kamera, einen eingebauten Blitz und einen microSD-Kartensteckplatz kombiniert. Das Board verfügt über integriertes Wi-Fi und Bluetooth und unterstützt eine OV2640 oder OV7670 Kamera mit bis zu 2 Megapixel Auflösung.

Front and Back of ESP32-CAM
Vorder- und Rückseite des ESP32-CAM

Im Tutorial beziehen wir uns auf das originale AI-Thinker model ESP32-CAM Board, aber es gibt viele Klone mit genau denselben Spezifikationen. Sie werden auf die gleiche Weise programmiert und verwendet – einschließlich desjenigen, das wir unter den benötigten Teilen aufgeführt haben.

Anschluss des FTDI Programmers

Du kannst den ESP32-CAM über einen Programming Shield oder über einen FTDI Programmer programmieren. Letzteres ist einfacher zu verwenden und flexibler. Es wandelt USB-Signale in serielle Signale um und ermöglicht die Programmierung von Mikrocontrollern wie Arduino und ESP32 über die UART-Schnittstelle. Das folgende Bild zeigt, wie du den FTDI Programmer mit dem ESP32-CAM Modul verbindest.

Wiring of FTDI Programmer with ESP32-CAM
Verdrahtung des FTDI Programmers mit ESP32-CAM

Die Verbindungen sind einfach. Beginne damit, GND des Programmers mit GND des ESP32-CAM Moduls zu verbinden (blaues Kabel). Dann verbinde die 5V Stromversorgung (rotes Kabel). Beachte, dass einige FTDI Programmer Jumper oder Schalter haben, um zwischen 3,3V und 5V zu wechseln. Achte darauf und verwende wenn möglich 5V.

Als nächstes verbinden wir den U0T (U0TXD) Pin des ESP32-CAM mit dem RXD Pin des Programmers (gelbes Kabel). Ebenso wird U0R mit TXD verbunden (grünes Kabel). Damit ist die serielle Kommunikation hergestellt.

Um den ESP32-CAM in den Programmiermodus zu versetzen, muss der IO0 Pin mit Masse (GND) verbunden werden. Möchtest du das Programm ausführen, darf der IO0 Pin nicht verbunden sein. Ich habe daher einen Schalter zwischen IO0 und GND (lila Kabel) eingebaut, mit dem ich zwischen Programmier- und Ausführmodus wechseln kann. Das Foto unten zeigt meine Verdrahtung des ESP32-CAM mit dem Schalter und dem FTDI Programmer:

Switch to enable programming mode of ESP32-CAM
Schalter zum Aktivieren des Programmiermodus des ESP32-CAM

Installation des ESP32 Cores

Wenn dies dein erstes Projekt mit einem Board der ESP32-Serie ist, musst du zuerst die Board-Installation durchführen. Sind ESP32 Boards bereits in deiner Arduino IDE installiert, kannst du diesen Abschnitt überspringen.

Öffne zunächst den Einstellungen-Dialog über „Preferences…“ im „Datei“-Menü. Es öffnet sich der unten gezeigte Einstellungen-Dialog.

Unter dem Reiter Einstellungen findest du unten ein Eingabefeld mit der Bezeichnung „Additional Board-Manager URLs“:

In dieses Eingabefeld kopiere die folgende URL: „https://espressif.github.io/arduino-esp32/package_esp32_dev_index.json

Damit weiß die Arduino IDE, wo sie die ESP32 Core Bibliotheken findet. Als nächstes installieren wir die ESP32 Core Bibliotheken über den Board-Manager.

Öffne den Board-Manager über „Tools-> Board -> Board-Manager“. Der Board-Manager erscheint in der linken Seitenleiste. Gib „ESP32“ in das Suchfeld oben ein und du solltest zwei Arten von ESP32 Boards sehen; die „Arduino ESP32 Boards“ und die „esp32 von Espressif“ Boards. Wir wollen die esp32 Bibliotheken von Espressif. Klicke auf den INSTALL Button und warte, bis der Download und die Installation abgeschlossen sind.

Install ESP32 Core libraries
ESP32 Core Bibliotheken installieren

Auswahl des ESP32-CAM Boards

Klicke auf das Dropdown-Menü und dann auf „Select other Board and Port auswählen…“:

Drop-down Menu for Board Selection
Dropdown-Menü zur Board-Auswahl

Es öffnet sich ein Dialog, in dem du „ESP32-CAM“ in die Suchleiste eingibst. Du siehst das Board „AI Thinker ESP32-CAM“ unter Boards. Klicke darauf, wähle den COM-Port aus, um es zu aktivieren, und klicke dann auf OK:

Board Selection Dialog with AI Thinker ESP32-CAM
Board-Auswahl-Dialog mit AI Thinker ESP32-CAM

Wenn du trotz angeschlossenem ESP32-CAM über den FTDI Programmer keinen PORT auswählen kannst, fehlt der CP210X Treiber. Gehe zu SILICON LABS Software Downloads und lade den CP210x Treiber für dein Betriebssystem herunter, z.B. für Windows „CP210x VCP Windows“:

Download CP210X Driver
CP210X Treiber herunterladen

Dies lädt eine ZIP-Datei herunter. Entpacke sie und führe den Installer aus. Danach sollte dein ESP32-CAM als verbunden an einem USB-Port erscheinen. Falls weiterhin Probleme auftreten, musst du eventuell auch einen FTDI Driver installieren.

Installation der ESP32-CAM Bibliothek

Für unseren Webserver verwenden wir die esp32cam library. Gehe zum github repo, klicke auf den grünen CODE-Button und dann auf „Download ZIP“, um die Bibliothek herunterzuladen:

Download esp32cam library
esp32cam Bibliothek herunterladen

Klicke dann auf „Sketch -> Include Library -> Add .ZIP Library“:

und wähle den Pfad zur gerade heruntergeladenen ZIP-Datei aus, um die Bibliothek zu installieren. Im nächsten Abschnitt schreiben und erklären wir den Code für den Webserver auf dem ESP32-CAM.

Code für ESP32-CAM Webserver

Der folgende Code richtet ein ESP32-CAM Modul ein, um Bilder über ein Wi-Fi Netzwerk zu senden. Es nimmt Bilder auf und stellt sie als JPEG-Dateien für anfragende Clients bereit. Der Server läuft auf Port 80, dem Standard-HTTP-Port.

// www.makerguides.com
#include "WebServer.h"
#include "WiFi.h"
#include "esp32cam.h"

const char* WIFI_SSID = "SSID";
const char* WIFI_PASS = "PASSWORD";
const char* URL = "/cam.jpg";

static auto RES = esp32cam::Resolution::find(800, 600);

WebServer server(80);

void serveJpg() {
  auto frame = esp32cam::capture();
  if (frame == nullptr) {
    Serial.println("CAPTURE FAILED!");
    server.send(503, "", "");
    return;
  }
  Serial.printf("CAPTURE OK %dx%d %db\n",
                frame->getWidth(), frame->getHeight(),
                static_cast<int>(frame->size()));

  server.setContentLength(frame->size());
  server.send(200, "image/jpeg");

  WiFiClient client = server.client();
  frame->writeTo(client);
}

void handleJpg() {
  if (!esp32cam::Camera.changeResolution(RES)) {
    Serial.println("CAN'T SET RESOLUTION!");
  }
  serveJpg();
}

void initCamera() {
  {
    using namespace esp32cam;
    Config cfg;
    cfg.setPins(pins::AiThinker);
    cfg.setResolution(RES);
    cfg.setBufferCount(2);
    cfg.setJpeg(80);

    bool ok = Camera.begin(cfg);
    Serial.println(ok ? "CAMERA OK" : "CAMERA FAIL");
  }
}

void initWifi() {
  WiFi.persistent(false);
  WiFi.mode(WIFI_STA);
  WiFi.begin(WIFI_SSID, WIFI_PASS);
  while (WiFi.status() != WL_CONNECTED)
    ;
  Serial.printf("http://%s%s\n",
                WiFi.localIP().toString().c_str(), URL);
}

void initServer() {
  server.on(URL, handleJpg);
  server.begin();
}

void setup() {
  Serial.begin(115200);
  initWifi();
  initCamera();
  initServer();
}

void loop() {
  server.handleClient();
}

Lass uns den Code in seine Komponenten aufteilen, um zu verstehen, wie er funktioniert.

Bibliotheken und Konstanten

Am Anfang des Codes binden wir die notwendigen Bibliotheken für den Webserver, die Wi-Fi-Funktionalität und die Kamerasteuerung ein.

#include "WebServer.h"
#include "WiFi.h"
#include "esp32cam.h"

Wir definieren außerdem Konstanten für die Wi-Fi-Zugangsdaten und den URL-Endpunkt zum Zugriff auf das Kamerabild.

const char* WIFI_SSID = "SSID";
const char* WIFI_PASS = "PASSWORD";
const char* URL = "/cam.jpg";

Natürlich musst du die Zugangsdaten durch die SSID und das Passwort deines Wi-Fi Netzwerks ersetzen.

Kameraauflösung

Wir setzen die gewünschte Auflösung für die Kamera. In diesem Fall wählen wir 800×600 Pixel.

static auto RES = esp32cam::Resolution::find(800, 600);

Initialisierung des Webservers

Als nächstes erstellen wir eine Instanz des Webservers, der auf Port 80 lauscht.

WebServer server(80);

Serve JPEG Funktion

Die serveJpg() Funktion nimmt ein Bild von der Kamera auf und sendet es als JPEG-Datei an den Client. Wenn die Aufnahme fehlschlägt, wird eine „503 Service Unavailable“ Antwort gesendet.

void serveJpg() {
  auto frame = esp32cam::capture();
  if (frame == nullptr) {
    Serial.println("CAPTURE FAILED!");
    server.send(503, "", "");
    return;
  }
  Serial.printf("CAPTURE OK %dx%d %db\n",
                frame->getWidth(), frame->getHeight(),
                static_cast<int>(frame->size()));

  server.setContentLength(frame->size());
  server.send(200, "image/jpeg");

  WiFiClient client = server.client();
  frame->writeTo(client);
}

Hier versuchen wir zuerst, einen Frame aufzunehmen. Wenn das gelingt, protokollieren wir die Abmessungen und die Größe des Bildes, setzen die Inhaltslänge und senden das Bild an den Client zurück.

Handle JPEG Funktion

Die handleJpg() Funktion ändert die Kameraauflösung und ruft serveJpg() auf, um das Bild zu servieren.

void handleJpg() {
  if (!esp32cam::Camera.changeResolution(RES)) {
    Serial.println("CAN'T SET RESOLUTION!");
  }
  serveJpg();
}

Diese Funktion stellt sicher, dass die Kamera auf die gewünschte Auflösung eingestellt ist, bevor das Bild gesendet wird.

Kamerainitialisierung

Die initCamera() Funktion konfiguriert die Kameraeinstellungen, einschließlich Pinbelegung, Auflösung, Pufferanzahl und JPEG-Qualität.

void initCamera() {
  {
    using namespace esp32cam;
    Config cfg;
    cfg.setPins(pins::AiThinker);
    cfg.setResolution(RES);
    cfg.setBufferCount(2);
    cfg.setJpeg(80);

    bool ok = Camera.begin(cfg);
    Serial.println(ok ? "CAMERA OK" : "CAMERA FAIL");
  }
}

Wir erstellen ein Konfigurationsobjekt, setzen die notwendigen Parameter und initialisieren die Kamera. Eine Meldung wird im seriellen Monitor ausgegeben, die angibt, ob die Kamerainitialisierung erfolgreich war.

Wi-Fi Initialisierung

Die initWifi() Funktion verbindet den ESP32 mit dem angegebenen Wi-Fi Netzwerk.

void initWifi() {
  WiFi.persistent(false);
  WiFi.mode(WIFI_STA);
  WiFi.begin(WIFI_SSID, WIFI_PASS);
  while (WiFi.status() != WL_CONNECTED)
    ;
  Serial.printf("http://%s%s\n",
                WiFi.localIP().toString().c_str(), URL);
}

Wir deaktivieren persistente Wi-Fi Verbindungen, setzen den Modus auf Station und versuchen, eine Verbindung zum Wi-Fi Netzwerk herzustellen. Nach erfolgreicher Verbindung geben wir die URL zum Zugriff auf das Kamerabild aus.

Server-Initialisierung

Die initServer() Funktion richtet den Server ein, um Anfragen für das Kamerabild zu bearbeiten.

void initServer() {
  server.on(URL, handleJpg);
  server.begin();
}

Wir definieren den URL-Endpunkt und verknüpfen ihn mit der handleJpg() Funktion, dann starten wir den Server.

Setup-Funktion

Die setup() Funktion initialisiert die serielle Kommunikation, Wi-Fi, Kamera und den Server.

void setup() {
  Serial.begin(115200);
  initWifi();
  initCamera();
  initServer();
}

Loop-Funktion

Schließlich bearbeitet die loop() Funktion kontinuierlich eingehende Client-Anfragen.

void loop() {
  server.handleClient();
}

Diese Funktion stellt sicher, dass der Server auf Client-Anfragen reagiert und Clients die von der Kamera aufgenommenen Bilder abrufen können.

Test des ESP32-CAM Webservers

Lass uns nun den Webserver testen. Kompiliere und lade den obigen Code hoch. Um Code auf den ESP32-CAM hochzuladen, versetze das Board in den Programmiermodus, indem du den Schalter umlegst, drücke kurz den Reset-Knopf auf dem Board und klicke dann im Arduino IDE auf den Upload-Button.

Wenn du mehr Hilfe beim Hochladen des Codes auf den ESP32-CAM brauchst, schau dir das Programming the ESP32-CAM Tutorial an, das detailliertere Informationen bietet.

Nach einem erfolgreichen Upload wird die URL für die Kamerabilder im seriellen Monitor angezeigt und du solltest auch den Text „CAMERA OK“ sehen:

http://192.168.1.146/cam.jpg
CAMERA OK

Kopiere diese URL in die Adressleiste deines Webbrowsers und du solltest das Bild sehen, das die Kamera aufgenommen hat:

Address Bar with URL
Adressleiste mit URL

Jedes Mal, wenn du im Webbrowser auf den Aktualisieren-Button klickst, nimmt der Webserver diese Anfrage entgegen, fordert den ESP32-CAM auf, ein neues Bild aufzunehmen, und sendet dieses neue Bild an deinen Webbrowser. Unten ein Bild meines Schreibtischs, das auf diese Weise aufgenommen wurde:

Picture taken by ESP32-CAM and served via Web Server in Browser
Vom ESP32-CAM aufgenommenes Bild, das über den Webserver im Browser angezeigt wird

Im nächsten Abschnitt senden wir die Bilder an das YOLO Objekterkennungsmodell, um die Objekte in der Szene zu erkennen.

YOLO Objekterkennung

YOLO (You Only Look Once) ist ein Deep-Learning-Modell zur Objekterkennung, bekannt für seine Geschwindigkeit und Genauigkeit. Es wurde erstmals von Joseph Redmon et al. in 2016 vorgestellt. Seitdem gibt es viele verbesserte Versionen, wobei YOLO11 by Ultralytics die aktuellste (Stand Februar 2025) ist.

Wir verwenden jedoch ein älteres Modell YOLOv3v, da es kleiner und einfacher zu nutzen ist, aber nicht ganz so genau wie die neueren Modelle.

Architektur des YOLO Modells (source)

Das YOLO Modell ist ein tiefes konvolutionales Netzwerk, das ein RGB-Bild mit den Dimensionen 448x448x3 als Eingabe erhält und die Begrenzungsrahmen sowie Konfidenzwerte der erkannten Objekte in einem 7×7×30 Tensor ausgibt. Wir verwenden eine Version des Modells, die darauf trainiert ist, 80 different objects zu erkennen, wie zum Beispiel:

  • Person
  • Fahrrad
  • Auto
  • Motorrad
  • Schere
  • Teddybär
  • Haartrockner
  • Zahnbürste

Wir gehen hier nicht ins Detail des Modells, aber wenn du mehr lernen möchtest, findest du hier Links zur Originalpublikation von YOLO, einer Beschreibung der Verbesserungen in Version 3 von YOLO und einem Anwendungsartikel mit nützlichen Informationen:

Projektordnerstruktur

Um das YOLO Objekterkennungssystem auf einem PC auszuführen, müssen wir einen Projektordner erstellen, z.B. „esp32-cam-yolo-object-detection“. Innerhalb dieses Ordners erstellst du einen Unterordner namens „YOLO“ und eine Python-Datei namens „detect.py“. Deine Ordnerstruktur sollte wie folgt aussehen:

Projektordnerstruktur

YOLO Dateien herunterladen

Als nächstes musst du die benötigten YOLO Dateien (Gewichte, Architektur-Konfiguration, Klassennamen) herunterladen und im „YOLO“ Ordner des Projekts ablegen. Hier sind die Links zu diesen Dateien:

Der Inhalt deines „YOLO“ Ordners sollte dann so aussehen:

YOLO Folder
YOLO Ordner

Virtuelle Umgebung erstellen

Wir müssen auch einige Python-Bibliotheken installieren, die wir in einer virtuellen Umgebung mit venv installieren. Öffne eine Kommandozeile und führe folgende Befehle aus:

cd esp32-cam-object-detection
python -m venv venv
venv\Scripts\activate.bat    
pip install opencv-python opencv-python-headless numpy torch torchvision

Der cd Befehl wechselt in den Projektordner. Der venv Befehl erstellt die virtuelle Umgebung und darin installieren wir die benötigten Bibliotheken mit pip install. Dadurch wird ein „venv“ Ordner im Projektordner erstellt, der die Bibliotheken enthält:

venv folder within Project Folder
venv Ordner im Projektordner

Objekterkennungscode

Zum Schluss kopiere den folgenden Code in die detect.py Datei in deinem Projektordner.

import cv2
import numpy as np
import urllib.request

# Camera URL
url = "http://192.168.1.146/cam.jpg"

# YOLO model files
weights_path = r"./YOLO/yolov3.weights"
config_path = r"./YOLO/yolov3.cfg"
names_path = r"./YOLO/coco.names"

# Load the YOLO model and COCO class names
net = cv2.dnn.readNet(weights_path, config_path)
with open(names_path, "r") as f:
    classes = [line.strip() for line in f.readlines()]

layer_names = net.getLayerNames()

# Handling the return value of getUnconnectedOutLayers()
out_layers = net.getUnconnectedOutLayers()
if isinstance(out_layers[0], list):
    output_layers = [layer_names[i[0] - 1] for i in out_layers]
else:
    output_layers = [layer_names[i - 1] for i in out_layers]

# Generate random colors for each class
colors = np.random.uniform(0, 255, size=(len(classes), 3))


def detect_objects(frame):
    height, width, _ = frame.shape
    blob = cv2.dnn.blobFromImage(frame, 1 / 255.0, (416, 416), swapRB=True, crop=False)
    net.setInput(blob)

    layer_outputs = net.forward(output_layers)

    boxes = []
    confidences = []
    class_ids = []

    for output in layer_outputs:
        for detection in output:
            scores = detection[5:]
            class_id = np.argmax(scores)
            confidence = scores[class_id]
            if confidence > 0.3:
                center_x = int(detection[0] * width)
                center_y = int(detection[1] * height)
                w = int(detection[2] * width)
                h = int(detection[3] * height)

                x = int(center_x - w / 2)
                y = int(center_y - h / 2)

                boxes.append([x, y, w, h])
                confidences.append(float(confidence))
                class_ids.append(class_id)

    indexes = cv2.dnn.NMSBoxes(boxes, confidences, 0.3, 0.4)

    # Draw detections on the frame
    if len(indexes) > 0 and isinstance(indexes, np.ndarray):
        indexes = indexes.flatten()
        for i in indexes:
            x, y, w, h = boxes[i]
            label = str(classes[class_ids[i]])
            confidence = confidences[i]
            color = colors[class_ids[i]]
            print(f"Detected: {label} with confidence {confidence:.2f}")

            cv2.rectangle(frame, (x, y), (x + w, y + h), color, 2)
            cv2.putText(
                frame,
                f"{label} {confidence:.2f}",
                (x, y - 10),
                cv2.FONT_HERSHEY_SIMPLEX,
                0.5,
                color,
                2,
            )

    return frame


def main():
    cv2.namedWindow("Object Detection", cv2.WINDOW_AUTOSIZE)

    while True:
        try:
            img_resp = urllib.request.urlopen(url)
            imgnp = np.array(bytearray(img_resp.read()), dtype=np.uint8)
            frame = cv2.imdecode(imgnp, -1)
            frame = detect_objects(frame)

            cv2.imshow("Object Detection", frame)

            if cv2.waitKey(1) & 0xFF == ord("q"):
                break
        except Exception as e:
            print(f"Error occurred: {e}")
            break

    cv2.destroyAllWindows()


if __name__ == "__main__":
    main()

Der obige Code implementiert ein Objekterkennungssystem mit dem YOLO Modell und OpenCV. Er nimmt Bilder von einem Kamerafeed auf und erkennt Objekte in Echtzeit, wobei die Ergebnisse auf dem Bildschirm angezeigt werden.

Bibliotheken importieren

Wir beginnen mit dem Import der notwendigen Bibliotheken: cv2 für Computer-Vision-Aufgaben, numpy für numerische Operationen und urllib.request für das Handling von URL-Anfragen.

import cv2
import numpy as np
import urllib.request

Kamera-URL

Hier definieren wir die URL des Kamerafeeds, von dem wir Bilder aufnehmen. Du musst diese Konstante durch die URL ersetzen, die dein Webserver liefert:

url = "http://192.168.1.146/cam.jpg"

YOLO Modell-Dateien

Als nächstes geben wir die Pfade zu den YOLO Modell-Dateien an: die Gewichtsdatei, die Konfigurationsdatei und die Namen der Objekte, die das Modell erkennen kann.

weights_path = r"./YOLO/yolov3.weights"
config_path = r"./YOLO/yolov3.cfg"
names_path = r"./YOLO/coco.names"

YOLO Modell laden

Wir laden das YOLO Modell mit OpenCVs dnn Modul und lesen die Klassennamen aus der angegebenen Datei. Die Layer-Namen werden ebenfalls für die spätere Verwendung abgerufen.

net = cv2.dnn.readNet(weights_path, config_path)
with open(names_path, "r") as f:
    classes = [line.strip() for line in f.readlines()]

layer_names = net.getLayerNames()

Ausgabe-Layer

Wir bestimmen die Ausgabelayer des Netzwerks. Das ist wichtig, um zu wissen, welche Layer die finalen Erkennungen liefern.

out_layers = net.getUnconnectedOutLayers()
if isinstance(out_layers[0], list):
    output_layers = [layer_names[i[0] - 1] for i in out_layers]
else:
    output_layers = [layer_names[i - 1] for i in out_layers]

Farben für Klassen generieren

Um die erkannten Objekte zu visualisieren und zu unterscheiden, generieren wir zufällige Farben für die Begrenzungsrahmen.

colors = np.random.uniform(0, 255, size=(len(classes), 3))

Objekterkennungsfunktion

Die detect_objects() Funktion nimmt einen Bild-Frame als Eingabe, verarbeitet ihn und erkennt Objekte mit dem YOLO Modell. Sie gibt den Frame mit gezeichneten Begrenzungsrahmen und Labels zurück.

def detect_objects(frame):
    height, width, _ = frame.shape
    blob = cv2.dnn.blobFromImage(frame, 1 / 255.0, (416, 416), swapRB=True, crop=False)
    net.setInput(blob)

    layer_outputs = net.forward(output_layers)

    boxes = []
    confidences = []
    class_ids = []

In dieser Funktion erstellen wir zuerst einen Blob aus dem Eingabeframe, eine vorverarbeitete Version des Bildes, die für das Modell geeignet ist. Dann führen wir einen Forward-Pass durch, um die Ausgabe des Modells zu erhalten.

Verarbeitung der Erkennungen

Wir durchlaufen die Ausgaben, um Begrenzungsrahmen, Konfidenzwerte und Klassen-IDs der erkannten Objekte zu extrahieren. Nur Erkennungen mit einer Konfidenz größer als 0,3 werden als gültig betrachtet. Du kannst diesen Wert (0…1) anpassen, um weniger oder mehr sichere Erkennungen anzuzeigen.

for output in layer_outputs:
    for detection in output:
        scores = detection[5:]
        class_id = np.argmax(scores)
        confidence = scores[class_id]
        if confidence > 0.3:
            center_x = int(detection[0] * width)
            center_y = int(detection[1] * height)
            w = int(detection[2] * width)
            h = int(detection[3] * height)

            x = int(center_x - w / 2)
            y = int(center_y - h / 2)

            boxes.append([x, y, w, h])
            confidences.append(float(confidence))
            class_ids.append(class_id)

Non-Maximum Suppression

Um überlappende, redundante Rahmen zu eliminieren, wenden wir Non-Maximum Suppressio (NMS) an, um nur die besten Begrenzungsrahmen zu behalten.

indexes = cv2.dnn.NMSBoxes(boxes, confidences, 0.3, 0.4)

Zeichnen der Erkennungen

Wir zeichnen die Begrenzungsrahmen und Labels für jedes erkannte Objekt auf den Frame. Der erkannte Klassen-/Objektname und der Konfidenzwert werden angezeigt.

if len(indexes) > 0 and isinstance(indexes, np.ndarray):
    indexes = indexes.flatten()
    for i in indexes:
        x, y, w, h = boxes[i]
        label = str(classes[class_ids[i]])
        confidence = confidences[i]
        color = colors[class_ids[i]]
        print(f"Detected: {label} with confidence {confidence:.2f}")

        cv2.rectangle(frame, (x, y), (x + w, y + h), color, 2)
        cv2.putText(
            frame,
            f"{label} {confidence:.2f}",
            (x, y - 10),
            cv2.FONT_HERSHEY_SIMPLEX,
            0.5,
            color,
            2,
        )

Hauptfunktion

Die main() Funktion richtet ein Fenster zur Anzeige der Erkennungen ein und nimmt kontinuierlich Frames vom Kamerafeed auf. Sie verarbeitet jeden Frame mit der detect_objects() Funktion und zeigt das Ergebnis an.

def main():
    cv2.namedWindow("Object Detection", cv2.WINDOW_AUTOSIZE)

    while True:
        try:
            img_resp = urllib.request.urlopen(url)
            imgnp = np.array(bytearray(img_resp.read()), dtype=np.uint8)
            frame = cv2.imdecode(imgnp, -1)
            frame = detect_objects(frame)

            cv2.imshow("Object Detection", frame)

            if cv2.waitKey(1) & 0xFF == ord("q"):
                break
        except Exception as e:
            print(f"Error occurred: {e}")
            break

    cv2.destroyAllWindows()

Es öffnet sich ein Fenster, und wenn du „q“ drückst, während das Fenster im Vordergrund ist, wird die Anwendung beendet.

Ausführungseintrittspunkt

Zum Schluss prüfen wir, ob das Skript direkt ausgeführt wird, und rufen die main() Funktion auf, um das Programm zu starten.

if __name__ == "__main__":
    main()

Im nächsten Abschnitt fassen wir alles zusammen und starten unser Objekterkennungssystem.

Objekterkennung starten

Starte zuerst dein ESP32-CAM Modul mit dem Webserver-Code und stelle sicher, dass der ESP32-CAM Bilder aufnimmt und diese im Webbrowser unter der im seriellen Monitor angezeigten URL anzeigt. Achte auch darauf, dass diese URL in detect.py verwendet wird, z.B. in meinem Fall ist diese URL:

url = "http://192.168.1.146/cam.jpg"

Als nächstes startest du den YOLO Objektdetektor. Wechsle in deinen Projektordner („esp32-cam-object-detection“), aktiviere die virtuelle Umgebung und führe den Detektor-Code detect.py aus:

cd esp32-cam-object-detection
venv\Scripts\activate.bat  
python detect.py

Beachte, dass du die virtuelle Umgebung mit folgendem Befehl deaktivieren kannst:

venv\Scripts\deactivate.bat

Wenn der Code läuft, solltest du die Namen der erkannten Objekte mit den Konfidenzwerten in der Konsole sehen:

Detected: cup with confidence 0.76
Detected: laptop with confidence 0.39
Detected: cup with confidence 0.51
Detected: laptop with confidence 0.33
Detected: cup with confidence 0.44
Detected: cup with confidence 0.65
Detected: cup with confidence 0.63     

Außerdem öffnet sich ein Fenster namens „Object Detection“, das das aktuelle Bild der Kamera mit Begrenzungsrahmen um die erkannten Objekte zeigt. Unten ein Beispiel, bei dem das System korrekt eine Tasse, eine Fernbedienung und einen Laptop erkennt:

Window of Object Detection Application
Fenster der Objekterkennungsanwendung

Wenn du mehr Beispiele für die Erkennungsfähigkeiten des YOLO Modells sehen möchtest, besuche die folgende YOLO Demo Video.

Fazit

In diesem Tutorial hast du gelernt, wie man ein Objekterkennungssystem baut. Das ESP32-CAM Modul wurde verwendet, um Bilder aufzunehmen und einen Webserver für diese Bilder zu betreiben. Die Bilder wurden dann per Wi-Fi an einen PC gesendet, der eine Objekterkennungssoftware basierend auf dem YOLO Deep-Learning-Modell ausführt.

Das Kompilieren und Hochladen von Code auf den ESP32-CAM kann recht knifflig sein. Wenn du auf Probleme stößt, schau dir das Programming the ESP32-CAM Tutorial an, das detailliertere Anleitungen bietet.

Beachte, dass unser kleines Objekterkennungssystem auf 80 vordefinierte Objekte (oder Klassen) beschränkt ist. Du kannst das YOLO Modell jedoch mit eigenen Objekten trainieren. Das How to Train YOLOv3 to Detect Custom Objects? Tutorial könnte dir dabei helfen, wenn du das tun möchtest.

Wenn du weitere Fragen hast, kannst du sie gerne im Kommentarbereich stellen.

Viel Spaß beim Tüfteln ; )