Skip to Content

Rilevamento oggetti con ESP32-CAM e YOLO

Rilevamento oggetti con ESP32-CAM e YOLO

In questo tutorial imparerai come rilevare e classificare oggetti usando il modulo ESP32-CAM e YOLO, un sistema di deep learning per il rilevamento degli oggetti.

Ti guiderò nella realizzazione di un progetto in cui l’ESP32-CAM cattura immagini, funziona come server web e invia le immagini a un computer per l’analisi. Il computer utilizzerà YOLO per rilevare e classificare gli oggetti. Imparerai a montare l’hardware, configurare la fotocamera e servire immagini JPEG via HTTP.

Alla fine avrai un’interfaccia web funzionante per visualizzare gli scatti catturati dal tuo ESP32-CAM e un sistema di rilevamento oggetti per 80 diversi tipi di oggetti.

Componenti necessari

Di seguito trovi i componenti necessari per costruire il progetto. Invece del Programmatore FTDI potresti anche usare uno Shield di programmazione per ESP32-CAM, ma ti consiglio il primo.

ESP32-CAM

Adattatore FTDI USB-TTL

USB data cable

Cavo dati USB

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.

Architettura del sistema

Il sistema che andremo a costruire è composto da due componenti principali: 1) un modulo ESP32-CAM che cattura immagini e funziona come server web inviando le immagini via WiFi; 2) un PC dove il YOLO sistema di rilevamento oggetti è in esecuzione. Analizza le immagini e annota gli oggetti rilevati. Il diagramma seguente offre una panoramica dell’architettura del sistema:

System Architecture Object Detection System
Architettura del sistema di rilevamento oggetti

L’immagine seguente mostra un esempio di rilevamento del sistema. Sulla mia scrivania disordinata, YOLO ha rilevato una tazza con l’88% di confidenza, un paio di forbici con il 68% e un laptop con il 59%. Puoi vedere le bounding box intorno agli oggetti con i loro nomi e la confidenza di rilevamento annotati nell’angolo in alto a sinistra:

Objects detected with YOLO
Oggetti rilevati con YOLO

Nelle sezioni successive imparerai come programmare il server web sull’ESP32-CAM e come configurare il sistema di rilevamento YOLO su un PC.

La scheda di sviluppo ESP32-CAM

La scheda di sviluppo ESP32-CAM è un modulo compatto che combina un chip ESP32-S, una fotocamera, un flash integrato e uno slot per schede microSD. La scheda ha Wi-Fi e Bluetooth integrati e supporta una fotocamera OV2640 o OV7670 con risoluzione fino a 2 megapixel.

Front and Back of ESP32-CAM
Fronte e retro dell’ESP32-CAM

Nel tutorial faremo riferimento alla scheda originale AI-Thinker model dell’ESP32-CAM, ma esistono molti cloni con esattamente le stesse specifiche. Sono programmati e usati allo stesso modo, incluso quello elencato tra i componenti necessari.

Collegamento del programmatore FTDI

Puoi programmare l’ESP32-CAM tramite un Programming Shield o tramite un FTDI Programmer. Quest’ultimo è più facile da usare e più flessibile. Converte i segnali USB in segnali seriali e permette di programmare microcontrollori come Arduino e ESP32 tramite l’interfaccia UART. L’immagine seguente mostra come collegare il programmatore FTDI al modulo ESP32-CAM.

Wiring of FTDI Programmer with ESP32-CAM
Collegamenti del programmatore FTDI con ESP32-CAM

I collegamenti sono semplici. Inizia collegando il GND del programmatore al GND del modulo ESP32-CAM (filo blu). Poi fai lo stesso con l’alimentazione a 5V (filo rosso). Nota che alcuni programmatori FTDI hanno jumper o interruttori per passare da 3.3V a 5V. Fai attenzione e usa 5V, se possibile.

Successivamente colleghiamo il pin U0T (U0TXD) dell’ESP32-CAM al pin RXD del programmatore (filo giallo). Analogamente, U0R si collega a TXD (filo verde). Così si stabilisce la comunicazione seriale.

Per mettere l’ESP32-CAM in modalità programmazione il pin IO0 deve essere collegato a massa (GND). Ma per eseguire il programma, il pin IO0 deve rimanere scollegato. Ho quindi aggiunto un interruttore tra IO0 e GND (filo viola) che mi permette di passare dalla modalità programmazione a quella esecuzione e viceversa. La foto sotto mostra il mio cablaggio dell’ESP32-CAM con l’interruttore e il programmatore FTDI:

Switch to enable programming mode of ESP32-CAM
Interruttore per abilitare la modalità programmazione dell’ESP32-CAM

Installazione del Core ESP32

Se è il tuo primo progetto con una scheda della serie ESP32, devi prima installare il core della scheda. Se le schede ESP32 sono già installate nel tuo Arduino IDE, puoi saltare questa sezione.

Inizia aprendo la finestra Preferenze selezionando “Preferences…” dal menu “File”. Si aprirà la finestra Preferenze mostrata sotto.

Nella scheda Settings troverai una casella di testo in fondo alla finestra etichettata “Additional boards manager URLs”:

In questo campo incolla il seguente URL: “https://espressif.github.io/arduino-esp32/package_esp32_dev_index.json

Questo permetterà all’Arduino IDE di sapere dove trovare le librerie core ESP32. Successivamente installeremo effettivamente le librerie core ESP32 usando il Boards Manager.

Apri il Boards Manager tramite “Tools -> Boards -> Board Manager”. Vedrai il Boards Manager apparire nella barra laterale sinistra. Inserisci “ESP32” nel campo di ricerca in alto e dovresti vedere due tipi di schede ESP32: “Arduino ESP32 Boards” e “esp32 by Espressif”. Vogliamo le librerie esp32 di Espressif. Clicca sul pulsante INSTALL e attendi che il download e l’installazione siano completati.

Install ESP32 Core libraries
Installa le librerie Core ESP32

Selezione della scheda ESP32-CAM

Clicca sul menu a tendina e poi su “Select other board and port…”:

Drop-down Menu for Board Selection
Menu a tendina per la selezione della scheda

Si aprirà una finestra dove inserire “ESP32-CAM” nella barra di ricerca. Vedrai la scheda “AI Thinker ESP32-CAM” sotto Boards. Cliccaci sopra, seleziona la porta COM per attivarla e poi clicca OK:

Board Selection Dialog with AI Thinker ESP32-CAM
Finestra di selezione scheda con AI Thinker ESP32-CAM

Se non riesci a selezionare una PORTA nonostante l’ESP32-CAM sia collegato a una porta USB tramite il programmatore FTDI, allora manca il driver CP210X. Vai a SILICON LABS Software Downloads e scarica il driver CP210x per il tuo sistema operativo, ad esempio per Windows è “CP210x VCP Windows”:

Download CP210X Driver
Scarica il driver CP210X

Questo scaricherà un file ZIP. Estrailo ed esegui l’installer. Dopo di che il tuo ESP32-CAM dovrebbe apparire come connesso a una porta USB. Se hai ancora problemi, potrebbe essere necessario installare anche un FTDI Driver.

Installazione della libreria ESP32-CAM

Per il nostro Web Server useremo la esp32cam library. Vai al github repo, clicca sul pulsante verde CODE e poi su “Download ZIP” per scaricare la libreria:

Download esp32cam library
Scarica la libreria esp32cam

Poi clicca su “Sketch->Include Library->Add .Zip Library”:

e seleziona il percorso del file ZIP appena scaricato per installare la libreria. Nella sezione successiva scriveremo e spiegheremo il codice per far funzionare un Web Server sull’ESP32-CAM.

Codice per il Web Server ESP32-CAM

Il codice seguente configura un modulo ESP32-CAM per inviare immagini su una rete Wi-Fi. Cattura immagini e le serve come file JPEG ai client che le richiedono. Il server gira sulla porta 80, che è la porta HTTP predefinita.

#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();
}

Analizziamo il codice nelle sue componenti per capire come funziona.

Librerie e costanti

All’inizio del codice includiamo le librerie necessarie per il server web, la funzionalità Wi-Fi e il controllo della fotocamera.

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

Definiamo anche le costanti per le credenziali Wi-Fi e l’endpoint URL per accedere all’immagine della fotocamera.

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

Ovviamente dovrai sostituire le credenziali con SSID e password della tua rete Wi-Fi.

Risoluzione della fotocamera

Impostiamo la risoluzione desiderata per la fotocamera. In questo caso puntiamo a una risoluzione di 800×600 pixel.

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

Inizializzazione del Web Server

Creiamo un’istanza del server web che ascolta sulla porta 80.

WebServer server(80);

Funzione Serve JPEG

La funzione serveJpg() cattura un’immagine dalla fotocamera e la invia al client come file JPEG. Se la cattura fallisce, invia una risposta “503 Service Unavailable”.

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);
}

Qui tentiamo prima di catturare un frame. Se ha successo, registriamo dimensioni e dimensione dell’immagine, impostiamo la lunghezza del contenuto e inviamo l’immagine al client.

Funzione Handle JPEG

La funzione handleJpg() cambia la risoluzione della fotocamera e chiamaserveJpg() per servire l’immagine.

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

Questa funzione assicura che la fotocamera sia impostata alla risoluzione desiderata prima di servire l’immagine.

Inizializzazione della fotocamera

La funzione initCamera() configura le impostazioni della fotocamera, inclusi i pin, la risoluzione, il numero di buffer e la qualità JPEG.

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");
  }
}

Creiamo un oggetto di configurazione, impostiamo i parametri necessari e inizializziamo la fotocamera. Un messaggio viene stampato sul monitor seriale per indicare se l’inizializzazione è andata a buon fine.

Inizializzazione Wi-Fi

La funzione initWifi() connette l’ESP32 alla rete Wi-Fi specificata.

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);
}

Disabilitiamo le connessioni Wi-Fi persistenti, impostiamo la modalità station e tentiamo di connetterci alla rete Wi-Fi. Una volta connessi, stampiamo l’URL per accedere all’immagine della fotocamera.

Inizializzazione del server

La funzione initServer() configura il server per gestire le richieste dell’immagine della fotocamera.

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

Definiamo l’endpoint URL e lo associamo alla funzionehandleJpg(), poi avviamo il server.

Funzione Setup

La funzione setup() inizializza la comunicazione seriale, il Wi-Fi, la fotocamera e il server.

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

Funzione Loop

Infine, la funzione loop() gestisce continuamente le richieste dei client.

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

Questa funzione assicura che il server risponda alle richieste dei client, permettendo loro di recuperare le immagini catturate dalla fotocamera.

Test del Web Server ESP32-CAM

Ora testiamo il Web Server. Compila e carica il codice sopra. Per caricare il codice sull’ESP32-CAM, metti la scheda in modalità programmazione azionando l’interruttore, poi premi brevemente il pulsante Reset sulla scheda e infine clicca su Upload nell’Arduino IDE.

Se hai bisogno di aiuto per caricare il codice sull’ESP32-CAM, dai un’occhiata al tutorial Programming the ESP32-CAM che fornisce maggiori dettagli.

Dopo un caricamento riuscito vedrai l’URL per le immagini della fotocamera stampato sul Monitor Seriale e dovresti anche vedere il testo “CAMERA OK”:

http://192.168.1.146/cam.jpg
CAMERA OK

Copia questo URL nella barra degli indirizzi del browser e dovresti vedere l’immagine scattata dalla fotocamera:

Address Bar with URL
Barra degli indirizzi con URL

Ogni volta che premi il pulsante di ricarica nel browser, il Web Server riceve la richiesta, chiede all’ESP32-CAM di scattare una nuova foto e invia questa nuova immagine al browser. Qui sotto una foto della mia scrivania, scattata in questo modo:

Picture taken by ESP32-CAM and served via Web Server in Browser
Immagine scattata dall’ESP32-CAM e servita via Web Server nel browser

Nella sezione successiva invieremo le immagini al modello di rilevamento oggetti YOLO per riconoscere gli oggetti nella scena.

Rilevamento oggetti con YOLO

YOLO (You Only Look Once) è un modello di deep learning per il rilevamento oggetti noto per la sua velocità e precisione. È stato introdotto per la prima volta da Joseph Redmon et al. in 2016. Da allora ci sono state molte versioni migliorate con YOLO11 by Ultralytics essendo l’ultima (a febbraio 2025).

Tuttavia, useremo un modello più vecchio YOLOv3v, poiché è più piccolo e più facile da usare, anche se la sua precisione non è alta come i modelli più recenti.

Architettura del modello YOLO (source)

Il modello YOLO è una rete convoluzionale profonda che prende in input un’immagine RGB di dimensioni 448x448x3 e produce in output le bounding box e i punteggi di confidenza per gli oggetti rilevati in un tensore 7×7×30. Useremo una versione del modello addestrata a rilevare 80 different objects, come:

  • persona
  • bicicletta
  • auto
  • motocicletta
  • forbici
  • orsacchiotto
  • asciugacapelli
  • spazzolino da denti

Non entreremo nei dettagli del modello qui, ma se vuoi saperne di più ecco i link alla pubblicazione originale di YOLO, a una descrizione dei miglioramenti della Versione 3 di YOLO e a un articolo applicativo con informazioni utili:

Struttura della cartella del progetto

Per eseguire il sistema di rilevamento oggetti YOLO su un PC dobbiamo creare una cartella di progetto, ad esempio “esp32-cam-yolo-object-detection”. All’interno crea una sottocartella chiamata “YOLO” e un file python chiamato “detect.py”. La struttura della cartella dovrebbe essere la seguente:

Struttura della cartella del progetto

Scarica i file YOLO

Successivamente devi scaricare i file YOLO richiesti (pesi, configurazione architettura, nomi delle classi) e metterli nella cartella “YOLO” del progetto. Ecco i link a questi file:

Il contenuto della tua cartella “YOLO” dovrebbe quindi essere così:

YOLO Folder
Cartella YOLO

Creazione dell’ambiente virtuale

Dobbiamo anche installare alcune librerie Python e le installeremo in un ambiente virtuale usando venv. Apri un terminale e esegui i seguenti comandi:

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

Il comando cd ci sposta nella cartella del progetto. Il comando ven crea l’ambiente virtuale e al suo interno installiamo le librerie richieste tramite pip install. Questo creerà una cartella “venv” nella cartella del progetto che contiene le librerie:

venv folder within Project Folder
Cartella venv all’interno della cartella del progetto

Codice per il rilevamento oggetti

Infine, come ultimo passo copia il seguente codice nel file detect.pynel tuo progetto.

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()

Il codice sopra implementa un sistema di rilevamento oggetti usando il modello YOLO con OpenCV. Cattura immagini da un feed della fotocamera e rileva oggetti in tempo reale, mostrando i risultati sullo schermo.

Importazione delle librerie

Iniziamo importando le librerie necessarie: cv2 per compiti di computer vision, numpy per operazioni numeriche, e urllib.request per gestire richieste URL.

import cv2
import numpy as np
import urllib.request

URL della fotocamera

Qui definiamo l’URL del feed della fotocamera da cui cattureremo le immagini. Dovrai sostituire questa costante con l’URL che il tuo Web Server fornisce:

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

File del modello YOLO

Successivamente specifichiamo i percorsi ai file del modello YOLO: il file dei pesi, il file di configurazione e i nomi degli oggetti che il modello può rilevare.

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

Caricamento del modello YOLO

Carichiamo il modello YOLO usando il modulo dnn di OpenCV e leggiamo i nomi delle classi dal file specificato. Recuperiamo anche i nomi degli strati per un uso successivo.

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()

Strati di output

Determiniamo gli strati di output della rete. Questo è cruciale per capire quali strati forniscono le rilevazioni finali.

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]

Generazione dei colori per le classi

Per visualizzare e distinguere gli oggetti rilevati, generiamo colori casuali per le bounding box.

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

Funzione di rilevamento oggetti

La funzione detect_objects() prende in input un frame immagine, lo elabora e rileva oggetti usando il modello YOLO. Restituisce il frame con le bounding box e le etichette disegnate.

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 questa funzione creiamo prima un blob dal frame di input, una versione preprocessata dell’immagine adatta al modello. Poi eseguiamo una forward pass per ottenere l’output dal modello.

Elaborazione delle rilevazioni

Scorriamo gli output per estrarre bounding box, punteggi di confidenza e ID delle classi per gli oggetti rilevati. Consideriamo valide solo le rilevazioni con confidenza superiore a 0.3. Puoi modificare questo parametro (0…1) per mostrare rilevazioni meno o più sicure.

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)

Soppressione non massima

Per eliminare le bounding box sovrapposte ridondanti, applichiamo la Non-Maximum Suppressio (NMS) per mantenere solo le migliori.

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

Disegno delle rilevazioni

Disegniamo le bounding box e le etichette sul frame per ogni oggetto rilevato. Vengono mostrati il nome della classe/oggetto rilevato e il punteggio di confidenza.

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,
        )

Funzione principale

La funzione main() apre una finestra per mostrare le rilevazioni e cattura continuamente frame dal feed della fotocamera. Elabora ogni frame tramite la funzione detect_objects() e mostra il risultato.

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()

Si aprirà una finestra e se premi “q”, mentre la finestra è attiva, l’applicazione terminerà.

Punto di ingresso dell’esecuzione

Infine, verifichiamo se lo script è eseguito direttamente e chiamiamo la funzione main() per avviare il programma.

if __name__ == "__main__":
    main()

Nella sezione successiva metteremo tutto insieme e faremo girare il nostro sistema di rilevamento oggetti.

Esecuzione del rilevatore oggetti

Per prima cosa avvia il modulo ESP32-CAM con il codice del Web Server e assicurati che l’ESP32-CAM catturi immagini e le mostri in un browser web all’URL stampato sul Monitor Seriale. Assicurati anche che questo URL sia usato in detect.py, ad esempio nel mio caso questo URL è:

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

Poi avvia il rilevatore oggetti YOLO. Vai nella cartella del progetto (“esp32-cam-object-detection”), attiva l’ambiente virtuale e esegui il codice del rilevatore detect.py:

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

Nota che puoi disattivare l’ambiente virtuale chiamando:

venv\Scripts\deactivate.bat

Se il codice è in esecuzione dovresti vedere i nomi degli oggetti rilevati con il punteggio di confidenza stampati sulla console:

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     

Si aprirà anche una finestra chiamata “Object Detection” che mostra l’immagine corrente vista dalla fotocamera con le bounding box intorno agli oggetti che il sistema ha rilevato. Qui sotto un esempio in cui il sistema rileva correttamente una tazza, un telecomando e un laptop:

Window of Object Detection Application
Finestra dell’applicazione di rilevamento oggetti

Se vuoi vedere altri esempi delle capacità di rilevamento del modello YOLO visita il seguente YOLO Demo Video.

Conclusioni

In questo tutorial hai imparato come costruire un sistema di rilevamento oggetti. Il modulo ESP32-CAM è stato usato per catturare immagini e far girare un Web Server per queste immagini. Le immagini sono state poi inviate via Wi-Fi a un PC che esegue un software di rilevamento oggetti basato sul modello di deep learning YOLO.

Compilare e caricare il codice sull’ESP32-CAM può essere piuttosto complicato. Se incontri problemi dai un’occhiata al tutorial Programming the ESP32-CAM che fornisce istruzioni più dettagliate.

Nota che il nostro piccolo sistema di rilevamento oggetti è limitato a 80 oggetti (o classi) predefiniti. Tuttavia, puoi addestrare il modello YOLO con i tuoi oggetti. Il tutorial How to Train YOLOv3 to Detect Custom Objects? potrebbe aiutarti se vuoi farlo.

Se hai altre domande, sentiti libero di lasciarle nella sezione commenti.

Buon divertimento con il tinkering ; )