Skip to Content

Deteção de Objetos com ESP32-CAM e YOLO

Deteção de Objetos com ESP32-CAM e YOLO

Neste tutorial, vais aprender a detetar e classificar objetos usando o módulo ESP32-CAM e o YOLO, um sistema de deep learning para deteção de objetos.

Vou guiar-te na construção de um projeto onde o ESP32-CAM captura imagens, funciona como servidor web e envia as imagens para um computador para análise. O computador usará o YOLO para detetar e classificar objetos. Vais aprender a montar o hardware, configurar a câmara e servir imagens JPEG via HTTP.

No final, terás uma interface web funcional para ver as fotos capturadas pelo teu ESP32-CAM e um sistema de deteção de objetos para 80 tipos diferentes de objetos.

Peças Necessárias

A seguir encontras os componentes necessários para construir o projeto. Em vez do Programador FTDI, também podes usar um Programming Shield para o ESP32-CAM, mas recomendo o primeiro.

ESP32-CAM

Adaptador FTDI USB-TTL

USB data cable

Cabo USB de Dados

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.

Arquitetura do Sistema

O sistema que vamos construir é composto por dois componentes principais: 1) um módulo ESP32-CAM que captura imagens e funciona como servidor web que envia as imagens via WiFi; 2) um PC onde o YOLO sistema de deteção de objetos está a correr. Ele analisa as imagens e anota os objetos detetados. O diagrama abaixo dá uma visão geral da arquitetura do sistema:

System Architecture Object Detection System
Arquitetura do Sistema de Deteção de Objetos

A imagem seguinte mostra um exemplo de deteção do sistema. Na minha secretária desarrumada, o YOLO conseguiu detetar uma chávena com 88% de confiança, um par de tesouras com 68% e um portátil com 59%. Podes ver as caixas delimitadoras em volta dos objetos com os seus nomes e a confiança da deteção anotados no canto superior esquerdo:

Objects detected with YOLO
Objetos detetados com YOLO

Nas próximas secções vais aprender a programar o servidor web no ESP32-CAM e a configurar o sistema de deteção YOLO num PC.

Placa de Desenvolvimento ESP32-CAM

A Placa de Desenvolvimento ESP32-CAM é um módulo compacto que combina um chip ESP32-S, uma câmara, um flash incorporado e um slot para cartão microSD. A placa tem Wi-Fi e Bluetooth integrados e suporta uma câmara OV2640 ou OV7670 com até 2 megapixels de resolução.

Front and Back of ESP32-CAM
Frente e Verso do ESP32-CAM

No tutorial vamos referir-nos ao AI-Thinker model original da placa ESP32-CAM, mas existem muitos clones com exatamente as mesmas especificações. São programados e usados da mesma forma – incluindo o que listámos em Peças Necessárias.

Ligação do programador FTDI

Podes programar o ESP32-CAM via um Programming Shield ou via um FTDI Programmer. Este último é mais fácil de usar e mais flexível. Converte sinais USB em sinais seriais e permite programar microcontroladores como o Arduino e o ESP32 via interface UART. A imagem seguinte mostra como ligar o Programador FTDI ao módulo ESP32-CAM.

Wiring of FTDI Programmer with ESP32-CAM
Ligação do Programador FTDI ao ESP32-CAM

As ligações são simples. Começa por ligar o GND do Programador ao GND do módulo ESP32-CAM (fio azul). Depois faz o mesmo com a alimentação de 5V (fio vermelho). Nota que alguns Programadores FTDI têm jumpers ou interruptores para mudar de 3.3V para 5V. Atenção a isso e usa 5V, se possível.

De seguida ligamos o pino U0T (U0TXD) do ESP32-CAM ao pino RXD do Programador (fio amarelo). De forma semelhante, o U0R liga-se ao TXD (fio verde). Com isso, a comunicação serial fica estabelecida.

Para colocar o ESP32-CAM em modo de programação, o pino IO0 precisa de ser ligado ao terra (GND). Mas para executar o programa, o pino IO0 deve ficar desconectado. Por isso, adicionei um interruptor entre IO0 e GND (fio roxo) que me permite alternar entre modo de programação e modo de execução. A foto abaixo mostra a minha ligação do ESP32-CAM com o interruptor e o Programador FTDI:

Switch to enable programming mode of ESP32-CAM
Interruptor para ativar o modo de programação do ESP32-CAM

Instalar o Core ESP32

Se este é o teu primeiro projeto com qualquer placa da série ESP32, precisas primeiro de instalar a placa. Se já tens as placas ESP32 instaladas no Arduino IDE, podes saltar esta secção.

Começa por abrir o diálogo de Preferências selecionando “Preferences…” no menu “File”. Isto abrirá o diálogo de Preferências mostrado abaixo.

Na aba Settings encontrarás uma caixa de edição na parte inferior do diálogo rotulada “Additional boards manager URLs”:

Neste campo de entrada copia a seguinte URL: “https://espressif.github.io/arduino-esp32/package_esp32_dev_index.json

Isto permite ao Arduino IDE saber onde encontrar as bibliotecas core do ESP32. A seguir vamos instalar as bibliotecas core do ESP32 usando o Boards Manager.

Abre o Boards Manager via “Tools -> Boards -> Board Manager”. Vais ver o Boards Manager aparecer na barra lateral esquerda. Escreve “ESP32” no campo de pesquisa no topo e deverás ver dois tipos de placas ESP32; as “Arduino ESP32 Boards” e as placas “esp32 by Espressif”. Queremos as bibliotecas esp32 da Espressif. Clica no botão INSTALL e espera até o download e instalação terminarem.

Install ESP32 Core libraries
Instalar bibliotecas Core ESP32

Selecionar a placa ESP32-CAM

Clica no menu drop-down e depois em “Select other board and port…”:

Drop-down Menu for Board Selection
Menu drop-down para seleção da placa

Isto abrirá um diálogo onde escreves “ESP32-CAM” na barra de pesquisa. Vais ver a placa “AI Thinker ESP32-CAM” em Boards. Clica nela, seleciona a porta COM para ativá-la e depois clica OK:

Board Selection Dialog with AI Thinker ESP32-CAM
Diálogo de seleção da placa com AI Thinker ESP32-CAM

Se não conseguires selecionar uma PORTA apesar do ESP32-CAM estar ligado a uma porta USB via programador FTDI, então falta o driver CP210X. Vai a SILICON LABS Software Downloads e descarrega o driver CP210x para o teu sistema operativo, por exemplo, para Windows é “CP210x VCP Windows”:

Download CP210X Driver
Descarregar driver CP210X

Isto vai descarregar um ficheiro ZIP. Descompacta-o e executa o instalador. Depois disso, o teu ESP32-CAM deverá aparecer como ligado a uma porta USB. Se ainda tiveres problemas, pode ser necessário instalar também um FTDI Driver.

Instalar a biblioteca ESP32-CAM

Para o nosso Servidor Web vamos usar a esp32cam library. Vai ao github repo, clica no botão verde CODE e depois em “Download ZIP” para descarregar a biblioteca:

Download esp32cam library
Descarregar biblioteca esp32cam

Depois clica em “Sketch->Include Library->Add .Zip Library”:

e seleciona o caminho para o ficheiro ZIP que acabaste de descarregar para instalar a biblioteca. Na próxima secção vamos escrever e explicar o código para correr um Servidor Web no ESP32-CAM.

Código para Servidor Web ESP32-CAM

O código seguinte configura um módulo ESP32-CAM para enviar imagens através de uma rede Wi-Fi. Ele captura imagens e serve-as como ficheiros JPEG aos clientes que as solicitam. O servidor corre na porta 80, que é a porta HTTP padrão.

#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 decompor o código em componentes para entender como funciona.

Bibliotecas e Constantes

No início do código, incluímos as bibliotecas necessárias para o servidor web, funcionalidade Wi-Fi e controlo da câmara.

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

Também definimos constantes para as credenciais Wi-Fi e o endpoint URL para aceder à imagem da câmara.

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

Obviamente, terás de substituir as credenciais pelo SSID e password da tua rede Wi-Fi.

Resolução da Câmara

Definimos a resolução desejada para a câmara. Neste caso, procuramos uma resolução de 800×600 pixels.

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

Inicialização do Servidor Web

De seguida, criamos uma instância do servidor web que escuta na porta 80.

WebServer server(80);

Função Serve JPEG

A função serveJpg() captura uma imagem da câmara e envia-a ao cliente como ficheiro JPEG. Se a captura falhar, envia uma resposta “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);
}

Aqui, tentamos primeiro capturar um frame. Se for bem-sucedido, registamos as dimensões e o tamanho da imagem, definimos o comprimento do conteúdo e enviamos a imagem ao cliente.

Função Handle JPEG

A função handleJpg() altera a resolução da câmara e chama serveJpg() para servir a imagem.

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

Esta função garante que a câmara está configurada para a resolução desejada antes de servir a imagem.

Inicialização da Câmara

A função initCamera() configura as definições da câmara, incluindo atribuição de pinos, resolução, número de buffers e qualidade 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");
  }
}

Criamos um objeto de configuração, definimos os parâmetros necessários e inicializamos a câmara. Uma mensagem é impressa no monitor serial indicando se a inicialização da câmara foi bem-sucedida.

Inicialização do Wi-Fi

A função initWifi() liga o ESP32 à rede 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);
}

Desativamos conexões Wi-Fi persistentes, definimos o modo para estação e tentamos conectar à rede Wi-Fi. Uma vez conectado, imprimimos a URL para aceder à imagem da câmara.

Inicialização do Servidor

A função initServer() configura o servidor para tratar pedidos da imagem da câmara.

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

Definimos o endpoint URL e associamo-lo à função handleJpg(), depois iniciamos o servidor.

Função Setup

A função setup() inicializa a comunicação serial, Wi-Fi, câmara e servidor.

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

Função Loop

Finalmente, a função loop() trata continuamente os pedidos dos clientes.

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

Esta função garante que o servidor responde aos pedidos dos clientes, permitindo-lhes obter imagens capturadas pela câmara.

Testar o Servidor Web ESP32-CAM

Agora, vamos testar o Servidor Web. Compila e carrega o código acima. Para carregar código no ESP32-CAM, coloca a placa em modo de programação acionando o interruptor, depois pressiona brevemente o botão Reset na placa e clica no botão Upload no Arduino IDE.

Se precisares de mais ajuda para carregar código no ESP32-CAM, consulta o tutorial Programming the ESP32-CAM que fornece mais detalhes.

Após um upload bem-sucedido, verás a URL para as imagens da câmara impressa no Monitor Serial e também deverás ver o texto “CAMERA OK”:

http://192.168.1.146/cam.jpg
CAMERA OK

Copia esta URL para a barra de endereços do navegador e deverás ver a imagem capturada pela câmara:

Address Bar with URL
Barra de Endereços com URL

Cada vez que clicas no botão de recarregar no teu navegador, o Servidor Web recebe o pedido, pede ao ESP32-CAM para tirar uma nova foto e envia essa nova foto para o navegador. Abaixo uma foto da minha secretária, tirada desta forma:

Picture taken by ESP32-CAM and served via Web Server in Browser
Foto tirada pelo ESP32-CAM e servida via Servidor Web no Navegador

Na próxima secção, vamos enviar as fotos para o Modelo de Deteção de Objetos YOLO para reconhecer os objetos na cena.

Deteção de Objetos YOLO

YOLO(You Only Look Once) é um modelo de deep learning para deteção de objetos conhecido pela sua rapidez e precisão. Foi introduzido pela primeira vez por Joseph Redmon et al. in 2016. Desde então, houve muitas versões melhoradas, sendo YOLO11 by Ultralytics a mais recente (em fevereiro de 2025).

No entanto, vamos usar um modelo mais antigo YOLOv3v, pois é mais pequeno e fácil de usar, embora a sua precisão não seja tão alta como os modelos mais recentes.

Arquitetura do Modelo YOLO (source)

O Modelo YOLO é uma rede convolucional profunda que recebe uma imagem RGB com dimensões 448x448x3 como entrada e produz as caixas delimitadoras e as pontuações de confiança para os objetos detetados num tensor 7×7×30. Vamos usar uma versão do modelo treinada para detetar 80 different objects, tais como:

  • pessoa
  • bicicleta
  • carro
  • mota
  • tesouras
  • urso de peluche
  • secador de cabelo
  • escova de dentes

Não vamos entrar em detalhes do modelo aqui, mas se quiseres aprender mais, aqui estão os links para a publicação original do YOLO, uma descrição das melhorias na Versão 3 do YOLO e um artigo de aplicação com informações úteis:

Estrutura da Pasta do Projeto

Para correr o sistema de deteção de objetos YOLO num PC, precisamos criar uma pasta de projeto, digamos “esp32-cam-yolo-object-detection”. Dentro desta pasta, cria uma subpasta chamada “YOLO” e um ficheiro python chamado “detect.py”. A estrutura da tua pasta deve ficar assim:

Estrutura da Pasta do Projeto

Descarregar ficheiros YOLO

De seguida, tens de descarregar os ficheiros necessários do YOLO (pesos, configuração da arquitetura, nomes das classes) e colocá-los na pasta “YOLO” do projeto. Aqui estão os links para esses ficheiros:

O conteúdo da tua pasta “YOLO” deverá ficar assim:

YOLO Folder
Pasta YOLO

Criar Ambiente Virtual

Também temos de instalar algumas bibliotecas Python e vamos instalá-las num Ambiente Virtual usando venv. Abre uma shell de comandos e executa os seguintes comandos:

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

O comando cdleva-nos para a pasta do projeto. O comando venv cria o ambiente virtual e dentro dele instalamos as bibliotecas necessárias via pip install. Isto vai criar uma pasta “venv” na pasta do projeto que contém as bibliotecas:

venv folder within Project Folder
Pasta venv dentro da Pasta do Projeto

Código de Deteção de Objetos

Finalmente, como último passo, copia o seguinte código para o ficheiro detect.py na pasta do teu projeto.

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

O código acima implementa um sistema de deteção de objetos usando o modelo YOLO com OpenCV. Captura imagens de um feed de câmara e deteta objetos em tempo real, mostrando os resultados no ecrã.

Importar Bibliotecas

Começamos por importar as bibliotecas necessárias: cv2 para tarefas de visão computacional, numpy para operações numéricas, e urllib.request para lidar com pedidos URL.

import cv2
import numpy as np
import urllib.request

URL da Câmara

Aqui definimos a URL do feed da câmara de onde vamos capturar imagens. Terás de substituir esta constante pela URL que o teu Servidor Web está a fornecer:

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

Ficheiros do Modelo YOLO

De seguida, especificamos os caminhos para os ficheiros do modelo YOLO: o ficheiro de pesos, o ficheiro de configuração e os nomes dos objetos que o modelo pode detetar.

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

Carregar o Modelo YOLO

Carregamos o modelo YOLO usando o módulo dnn do OpenCV e lemos os nomes das classes do ficheiro especificado. Os nomes das camadas também são obtidos 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()

Camadas de Saída

Determinamos as camadas de saída da rede. Isto é crucial para saber quais camadas fornecem as deteções finais.

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]

Gerar Cores para as Classes

Para visualizar e distinguir os objetos detetados, geramos cores aleatórias para as caixas delimitadoras.

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

Função de Deteção de Objetos

A função detect_objects() recebe um frame de imagem como entrada, processa-o e deteta objetos usando o modelo YOLO. Retorna o frame com caixas delimitadoras e etiquetas desenhadas.

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

Nesta função, primeiro criamos um blob a partir do frame de entrada, que é uma versão pré-processada da imagem adequada para o modelo. Depois fazemos uma passagem forward para obter a saída do modelo.

Processar Deteções

Percorremos as saídas para extrair caixas delimitadoras, pontuações de confiança e IDs de classe para os objetos detetados. Apenas deteções com confiança superior a 0.3 são consideradas válidas. Sente-te à vontade para alterar este parâmetro (0…1) para mostrar deteções menos ou mais confiantes.

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)

Supressão de Não-Máximos

Para eliminar caixas sobrepostas redundantes, aplicamos Non-Maximum Suppressio (NMS) para manter apenas as melhores caixas delimitadoras.

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

Desenhar Deteções

Desenhamos as caixas delimitadoras e etiquetas no frame para cada objeto detetado. O nome da classe/objeto detetado e a pontuação de confiança são exibidos.

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

Função Principal

A função main() configura uma janela para mostrar as deteções e captura continuamente frames do feed da câmara. Processa cada frame através da função detect_objects() e mostra o 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()

Abrirá uma janela e, se pressionares “q” enquanto a janela estiver ativa, a aplicação terminará.

Ponto de Entrada da Execução

Finalmente, verificamos se o script está a ser executado diretamente e chamamos a função main() para iniciar o programa.

if __name__ == "__main__":
    main()

Na próxima secção juntamos tudo e corremos o nosso sistema de deteção de objetos.

Executar o Deteção de Objetos

Primeiro, liga o teu módulo ESP32-CAM com o código do Servidor Web e certifica-te que o ESP32-CAM captura imagens e as mostra num navegador web na URL impressa no Monitor Serial. Também certifica-te que esta URL é usada no detect.py, por exemplo, no meu caso esta URL é:

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

De seguida, inicia o detector de objetos YOLO. Vai para a pasta do projeto (“esp32-cam-object-detection”), ativa o ambiente virtual e executa o código do detector detect.py:

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

Nota que podes desativar o ambiente virtual chamando:

venv\Scripts\deactivate.bat

Se o código estiver a correr, deverás ver os nomes dos objetos detetados com a pontuação de confiança impressa no 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     

Também abrirá uma janela chamada “Object Detection” que mostra a imagem atual que a câmara vê com caixas delimitadoras em volta dos objetos que o sistema conseguiu detetar. Abaixo um exemplo onde o sistema detetou corretamente uma chávena, um comando e um portátil:

Window of Object Detection Application
Janela da Aplicação de Deteção de Objetos

Se quiseres ver mais exemplos das capacidades de deteção do modelo YOLO, vai ao seguinte YOLO Demo Video.

Conclusões

Neste tutorial aprendeste a construir um sistema de deteção de objetos. O módulo ESP32-CAM foi usado para capturar imagens e correr um Servidor Web para essas imagens. As imagens foram depois enviadas via Wi-Fi para um PC que corre um software de deteção de objetos baseado no modelo de deep learning YOLO.

Compilar e carregar código para o ESP32-CAM pode ser complicado. Se encontrares problemas, consulta o tutorial Programming the ESP32-CAM que fornece instruções mais detalhadas.

Note que o nosso pequeno sistema de deteção de objetos está limitado a 80 objetos (ou classes) predefinidos. No entanto, podes treinar o modelo YOLO com os teus próprios objetos. O tutorial How to Train YOLOv3 to Detect Custom Objects? pode ajudar se quiseres fazer isso.

Se tiveres mais perguntas, não hesites em deixá-las na secção de comentários.

Boas construções ; )