Skip to Content

Detección de objetos con ESP32-CAM y YOLO

Detección de objetos con ESP32-CAM y YOLO

En este tutorial aprenderás a detectar y clasificar objetos usando el módulo ESP32-CAM y YOLO, un sistema de deep learning para detección de objetos.

Te guiaré para construir un proyecto donde el ESP32-CAM captura imágenes, funciona como servidor web y envía las imágenes a un ordenador para su análisis. El ordenador usará YOLO para detectar y clasificar objetos. Aprenderás a montar el hardware, configurar la cámara y servir imágenes JPEG vía HTTP.

Al final, tendrás una interfaz web funcional para ver las instantáneas capturadas por tu ESP32-CAM y un sistema de detección de objetos para 80 tipos diferentes de objetos.

Piezas necesarias

A continuación encontrarás los componentes necesarios para construir el proyecto. En lugar del programador FTDI también podrías usar un Shield de programación para el ESP32-CAM, pero recomiendo el primero.

ESP32-CAM

Adaptador FTDI USB-TTL

USB data cable

Cable de datos 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.

Arquitectura del sistema

El sistema que vamos a construir está compuesto por dos componentes principales. 1) un módulo ESP32-CAM que captura imágenes y funciona como servidor web que envía las imágenes vía WiFi. 2) un PC donde el YOLO sistema de detección de objetos está en funcionamiento. Analiza las imágenes y anota los objetos detectados. El diagrama a continuación te da una visión general de la arquitectura del sistema:

System Architecture Object Detection System
Arquitectura del sistema de detección de objetos

La siguiente imagen muestra un ejemplo de detección del sistema. En mi escritorio desordenado, YOLO pudo detectar una taza con un 88% de confianza, unas tijeras con un 68% y un portátil con un 59%. Puedes ver los cuadros delimitadores alrededor de los objetos con sus nombres y la confianza de detección anotados en la esquina superior izquierda:

Objects detected with YOLO
Objetos detectados con YOLO

En las siguientes secciones aprenderás a programar el servidor web en el ESP32-CAM y cómo configurar el sistema de detección YOLO en un PC.

La placa de desarrollo ESP32-CAM

La placa de desarrollo ESP32-CAM es un módulo compacto que combina un chip ESP32-S, una cámara, un flash incorporado y una ranura para tarjeta microSD. La placa tiene Wi-Fi y Bluetooth integrados y soporta una cámara OV2640 o OV7670 con hasta 2 megapíxeles de resolución.

Front and Back of ESP32-CAM
Frontal y trasera del ESP32-CAM

En el tutorial nos referiremos a la placa original AI-Thinker model del ESP32-CAM pero existen muchos clones con exactamente las mismas especificaciones. Se programan y usan de la misma manera, incluyendo el que listamos en Piezas necesarias.

Conectando el programador FTDI

Puedes programar el ESP32-CAM vía un Programming Shield o vía un FTDI Programmer. Este último es más fácil de usar y más flexible. Convierte señales USB a señales seriales y permite programar microcontroladores como Arduino y ESP32 vía la interfaz UART. La siguiente imagen muestra cómo conectar el programador FTDI al módulo ESP32-CAM.

Wiring of FTDI Programmer with ESP32-CAM
Conexión del programador FTDI con ESP32-CAM

Las conexiones son simples. Comienza conectando GND del programador con GND del módulo ESP32-CAM (cable azul). Luego haz lo mismo con la alimentación de 5V (cable rojo). Ten en cuenta que algunos programadores FTDI tienen jumpers o interruptores para cambiar entre 3.3V y 5V. Asegúrate de usar 5V, si es posible.

Luego conectamos el pin U0T (U0TXD) del ESP32-CAM al pin RXD del programador (cable amarillo). De forma similar, U0R se conecta a TXD (cable verde). Con esto se establece la comunicación serial.

Para poner el ESP32-CAM en modo programación, el pin IO0 debe conectarse a tierra (GND). Pero si quieres ejecutar el programa, el pin IO0 debe quedar desconectado. Por eso añadí un interruptor entre IO0 y GND (cable morado) que me permite cambiar entre modo programación y modo ejecución. La foto abajo muestra mi conexión del ESP32-CAM con el interruptor y el programador FTDI:

Switch to enable programming mode of ESP32-CAM
Interruptor para habilitar modo programación del ESP32-CAM

Instalando el Core ESP32

Si es tu primer proyecto con cualquier placa de la serie ESP32, primero debes instalar la placa. Si ya tienes las placas ESP32 instaladas en tu Arduino IDE, puedes saltarte esta sección.

Comienza abriendo el diálogo de Preferencias seleccionando “Preferences…” en el menú “File”. Esto abrirá el diálogo de Preferencias mostrado abajo.

En la pestaña Settings encontrarás un cuadro de edición al final del diálogo etiquetado como “Additional boards manager URLs”:

En este campo copia la siguiente URL: «https://espressif.github.io/arduino-esp32/package_esp32_dev_index.json«

Esto permitirá que el Arduino IDE sepa dónde encontrar las librerías core del ESP32. A continuación instalaremos las librerías core del ESP32 usando el Boards Manager.

Abre el Boards Manager vía «Tools -> Boards -> Board Manager». Verás el Boards Manager en la barra lateral izquierda. Escribe «ESP32» en el campo de búsqueda arriba y deberías ver dos tipos de placas ESP32; las «Arduino ESP32 Boards» y las placas «esp32 by Espressif». Queremos las librerías esp32 de Espressif. Haz clic en el botón INSTALL y espera a que la descarga e instalación terminen.

Install ESP32 Core libraries
Instalar librerías Core ESP32

Seleccionando la placa ESP32-CAM

Haz clic en el menú desplegable y luego en «Select other board and port…»:

Drop-down Menu for Board Selection
Menú desplegable para selección de placa

Esto abrirá un diálogo donde debes escribir «ESP32-CAM» en la barra de búsqueda. Verás la placa «AI Thinker ESP32-CAM» bajo Boards. Haz clic en ella y selecciona el puerto COM para activarla, luego haz clic en OK:

Board Selection Dialog with AI Thinker ESP32-CAM
Diálogo de selección de placa con AI Thinker ESP32-CAM

Si no puedes seleccionar un puerto a pesar de tener el ESP32-CAM conectado a un puerto USB vía el programador FTDI, entonces falta el driver CP210X. Ve a SILICON LABS Software Downloads y descarga el driver CP210x para tu sistema operativo, por ejemplo para Windows es «CP210x VCP Windows»:

Download CP210X Driver
Descargar driver CP210X

Esto descargará un archivo ZIP. Descomprímelo y ejecuta el instalador. Después de eso tu ESP32-CAM debería aparecer conectado a un puerto USB. Si aún tienes problemas, puede que también necesites instalar un FTDI Driver.

Instalando la librería ESP32-CAM

Para nuestro servidor web vamos a usar la esp32cam library. Ve a github repo, haz clic en el botón verde CODE y luego en «Download ZIP» para descargar la librería:

Download esp32cam library
Descargar librería esp32cam

Luego haz clic en «Sketch->Include Library->Add .Zip Library»:

y selecciona la ruta al archivo ZIP que acabas de descargar para instalar la librería. En la siguiente sección escribiremos y explicaremos el código para ejecutar un servidor web en el ESP32-CAM.

Código para servidor web ESP32-CAM

El siguiente código configura un módulo ESP32-CAM para enviar imágenes a través de una red Wi-Fi. Captura imágenes y las sirve como archivos JPEG a los clientes que las soliciten. El servidor corre en el puerto 80, que es el puerto HTTP por defecto.

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

Vamos a desglosar el código en sus componentes para entender cómo funciona.

Librerías y constantes

Al inicio del código, incluimos las librerías necesarias para el servidor web, funcionalidad Wi-Fi y control de la cámara.

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

También definimos constantes para las credenciales Wi-Fi y el endpoint URL para acceder a la imagen de la cámara.

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

Obviamente, tendrás que reemplazar las credenciales con el SSID y la contraseña de tu red Wi-Fi.

Resolución de la cámara

Establecemos la resolución deseada para la cámara. En este caso, buscamos una resolución de 800×600 píxeles.

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

Inicialización del servidor web

Luego creamos una instancia del servidor web que escucha en el puerto 80.

WebServer server(80);

Función para servir JPEG

La función serveJpg()captura una imagen de la cámara y la envía al cliente como un archivo JPEG. Si la captura falla, envía una respuesta «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);
}

Aquí, primero intentamos capturar un frame. Si tiene éxito, registramos las dimensiones y tamaño de la imagen, establecemos la longitud del contenido y enviamos la imagen al cliente.

Función para manejar JPEG

La función handleJpg() cambia la resolución de la cámara y llama a serveJpg() para servir la imagen.

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

Esta función asegura que la cámara esté configurada a la resolución deseada antes de servir la imagen.

Inicialización de la cámara

La función initCamera() configura los ajustes de la cámara, incluyendo asignación de pines, resolución, número de buffers y calidad 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");
  }
}

Creamos un objeto de configuración, establecemos los parámetros necesarios e inicializamos la cámara. Se imprime un mensaje en el monitor serial indicando si la inicialización fue exitosa.

Inicialización Wi-Fi

La función initWifi() conecta el ESP32 a la red Wi-Fi especificada.

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

Desactivamos conexiones Wi-Fi persistentes, establecemos el modo estación y tratamos de conectar a la red Wi-Fi. Una vez conectado, imprimimos la URL para acceder a la imagen de la cámara.

Inicialización del servidor

La función initServer() configura el servidor para manejar solicitudes de la imagen de la cámara.

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

Definimos el endpoint URL y lo asociamos con la función handleJpg(), luego iniciamos el servidor.

Función setup

La función setup() inicializa la comunicación serial, Wi-Fi, cámara y servidor.

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

Función loop

Finalmente, la función loop() maneja continuamente las solicitudes entrantes de los clientes.

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

Esta función asegura que el servidor responda a las solicitudes de los clientes, permitiendo que estos recuperen imágenes capturadas por la cámara.

Prueba del servidor web ESP32-CAM

Ahora, probemos el servidor web. Compila y sube el código anterior. Para subir código al ESP32-CAM, cambia la placa a modo programación activando el interruptor, luego presiona brevemente el botón Reset en la placa y después haz clic en el botón Upload en el Arduino IDE.

Si necesitas más ayuda para subir código al ESP32-CAM, echa un vistazo al Programming the ESP32-CAM tutorial, que ofrece más detalles.

Después de una subida exitosa verás la URL para las imágenes de la cámara impresa en el Monitor Serial y también deberías ver el texto «CAMERA OK»:

http://192.168.1.146/cam.jpg
CAMERA OK

Copia esta URL en la barra de direcciones de tu navegador web y deberías ver la imagen que la cámara ha tomado:

Address Bar with URL
Barra de direcciones con URL

Cada vez que presionas el botón de recargar en tu navegador, el servidor web recibe esta solicitud, pide al ESP32-CAM que tome una nueva foto y envía esta nueva imagen a tu navegador. Abajo una foto de mi escritorio tomada de esta manera:

Picture taken by ESP32-CAM and served via Web Server in Browser
Foto tomada por ESP32-CAM y servida vía servidor web en navegador

En la siguiente sección enviaremos las imágenes al modelo de detección de objetos YOLO para reconocer los objetos en la escena.

Detección de objetos con YOLO

YOLO(You Only Look Once) es un modelo de deep learning para detección de objetos conocido por su velocidad y precisión. Fue presentado por primera vez por Joseph Redmon et al. in 2016. Desde entonces ha habido muchas versiones mejoradas, siendo YOLO11 by Ultralytics la más reciente (a febrero de 2025).

Sin embargo, usaremos un modelo más antiguo YOLOv3v, ya que es más pequeño y fácil de usar, aunque su precisión no es tan alta como la de los modelos más recientes.

Arquitectura del modelo YOLO (source)

El modelo YOLO es una red convolucional profunda que toma una imagen RGB de dimensiones 448x448x3 como entrada y produce los cuadros delimitadores y las puntuaciones de confianza para los objetos detectados en un tensor 7×7×30. Usaremos una versión del modelo entrenada para detectar 80 different objects, tales como:

  • persona
  • bicicleta
  • coche
  • moto
  • tijeras
  • oso de peluche
  • secador de pelo
  • cepillo de dientes

No entraremos en detalles del modelo aquí, pero si quieres aprender más, aquí tienes enlaces a la publicación original de YOLO, una descripción de las mejoras en la versión 3 de YOLO y un artículo de aplicación con información útil:

Estructura de carpetas del proyecto

Para ejecutar el sistema de detección YOLO en un PC necesitamos crear una carpeta de proyecto, digamos «esp32-cam-yolo-object-detection». Dentro de esta carpeta crea una subcarpeta llamada «YOLO» y un archivo python llamado «detect.py». La estructura de carpetas debería ser la siguiente:

Estructura de carpetas del proyecto

Descargar archivos YOLO

Luego debes descargar los archivos necesarios de YOLO (pesos, configuración de arquitectura, nombres de clases) y colocarlos en la carpeta «YOLO» del proyecto. Aquí están los enlaces a estos archivos:

El contenido de tu carpeta «YOLO» debería verse así:

YOLO Folder
Carpeta YOLO

Creando entorno virtual

También tenemos que instalar algunas librerías de Python y las instalaremos en un entorno virtual usando venv. Abre una consola y ejecuta los siguientes comandos:

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

El comandocdnos mueve a la carpeta del proyecto. El comando ven crea el entorno virtual y dentro de este instalamos las librerías necesarias vía pip install. Esto creará una carpeta «venv» en la carpeta del proyecto que contiene las librerías:

venv folder within Project Folder
Carpeta venv dentro de la carpeta del proyecto

Código de detección de objetos

Finalmente, como último paso copia el siguiente código en el archivo detect.pyde tu carpeta de proyecto.

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

El código anterior implementa un sistema de detección de objetos usando el modelo YOLO con OpenCV. Captura imágenes de una fuente de cámara y detecta objetos en tiempo real, mostrando los resultados en pantalla.

Importando librerías

Comenzamos importando las librerías necesarias:cv2para tareas de visión por computadora,numpypara operaciones numéricas, yurllib.requestpara manejar solicitudes URL.

import cv2
import numpy as np
import urllib.request

URL de la cámara

Aquí definimos la URL del feed de la cámara desde donde capturaremos imágenes. Tendrás que reemplazar esta constante con la URL que tu servidor web entrega imágenes:

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

Archivos del modelo YOLO

Luego especificamos las rutas a los archivos del modelo YOLO: el archivo de pesos, el archivo de configuración y los nombres de los objetos que el modelo puede detectar.

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

Cargando el modelo YOLO

Cargamos el modelo YOLO usando el módulo dnn de OpenCV y leemos los nombres de las clases desde el archivo especificado. También obtenemos los nombres de las capas para uso posterior.

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

Capas de salida

Determinamos las capas de salida de la red. Esto es crucial para saber qué capas proporcionan las detecciones finales.

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]

Generando colores para las clases

Para visualizar y distinguir los objetos detectados, generamos colores aleatorios para los cuadros delimitadores.

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

Función de detección de objetos

La función detect_objects() toma un frame de imagen como entrada, lo procesa y detecta objetos usando el modelo YOLO. Devuelve el frame con cuadros delimitadores y etiquetas dibujadas.

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 = []

En esta función, primero creamos un blob a partir del frame de entrada, que es una versión preprocesada de la imagen adecuada para el modelo. Luego realizamos un pase hacia adelante para obtener la salida del modelo.

Procesando detecciones

Recorremos las salidas para extraer cuadros delimitadores, puntuaciones de confianza e IDs de clase para los objetos detectados. Solo se consideran válidas las detecciones con confianza mayor a 0.3. Puedes cambiar este parámetro (0…1) para mostrar detecciones menos o más confiables.

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)

Supresión de no máximos

Para eliminar cuadros superpuestos redundantes, aplicamos Non-Maximum Suppressio (NMS) para mantener solo los mejores cuadros delimitadores.

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

Dibujando detecciones

Dibujamos los cuadros delimitadores y etiquetas en el frame para cada objeto detectado. Se muestra el nombre de la clase/objeto detectado y la puntuación de confianza.

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

Función principal

La función main() configura una ventana para mostrar las detecciones y captura continuamente frames del feed de la cámara. Procesa cada frame con la función detect_objects() y muestra el resultado.

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

Se abrirá una ventana y si presionas «q» mientras la ventana está activa, la aplicación terminará.

Punto de entrada de ejecución

Finalmente, verificamos si el script se está ejecutando directamente y llamamos a la función main() para iniciar el programa.

if __name__ == "__main__":
    main()

En la siguiente sección juntamos todo y ejecutamos nuestro sistema de detección de objetos.

Ejecutando el detector de objetos

Primero, enciende tu módulo ESP32-CAM con el código del servidor web y asegúrate de que el ESP32-CAM capture imágenes y las muestre en un navegador web bajo la URL impresa en el Monitor Serial. También asegúrate de que esta URL se use en detect.py, por ejemplo, en mi caso esta URL es:

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

Luego iniciamos el detector de objetos YOLO. Ve a tu carpeta de proyecto («esp32-cam-object-detection»), activa el entorno virtual y ejecuta el código del detector detect.py:

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

Ten en cuenta que puedes desactivar el entorno virtual llamando a:

venv\Scripts\deactivate.bat

Si el código está corriendo deberías ver los nombres de los objetos detectados con la puntuación de confianza impresa en la consola:

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     

También se abrirá una ventana llamada «Object Detection» que muestra la imagen actual que la cámara ve con cuadros delimitadores alrededor de los objetos que el sistema pudo detectar. Abajo un ejemplo donde el sistema detecta correctamente una taza, un mando a distancia y un portátil:

Window of Object Detection Application
Ventana de la aplicación de detección de objetos

Si quieres ver más ejemplos de las capacidades de detección del modelo YOLO visita el siguiente YOLO Demo Video.

Conclusiones

En este tutorial aprendiste a construir un sistema de detección de objetos. El módulo ESP32-CAM se usó para capturar imágenes y ejecutar un servidor web para esas imágenes. Luego las imágenes se enviaron vía Wi-Fi a un PC que ejecuta un software de detección de objetos basado en el modelo de deep learning YOLO.

Compilar y subir código al ESP32-CAM puede ser bastante complicado. Si tienes problemas, echa un vistazo al Programming the ESP32-CAM tutorial, que ofrece instrucciones más detalladas.

Ten en cuenta que nuestro pequeño sistema de detección de objetos está limitado a 80 objetos predefinidos (o clases). Sin embargo, puedes entrenar el modelo YOLO con tus propios objetos. El How to Train YOLOv3 to Detect Custom Objects?tutorial puede ayudarte si quieres hacer esto.

Si tienes más preguntas, no dudes en dejarlas en la sección de comentarios.

¡Feliz bricolaje ; )