Assistentes de IA de Bancada — Visão: ESP32-S3 CAM como sensor do assistente (captura, detecção leve e o que roda on-device)

Ouvir

Carregando voz…

Assistentes de IA de Bancada — Visão: ESP32-S3 CAM como sensor do assistente (captura, detecção leve e o que roda on-dev

(a) Conceito e Arquitetura

Câmera em um assistente de bancada não é “tirar foto”: é medir presença, atenção e movimento. Este post trata o módulo ESP32-S3 CAM como um sensor do sistema — quem entra no campo de visão, se há uma pessoa olhando para o dispositivo, se algo mudou no ambiente — e não como um stream de vídeo para a nuvem. Todo o processamento fica no próprio chip.

O pipeline é curto e vale conhecê-lo em detalhe: o sensor (OV2640 ou OV5640) entrega pixels crus via barramento DVP; o periférico de câmera do ESP32-S3 despeja os dados em DMA direto na PSRAM; o encoder JPEG do sensor comprime o quadro (tipicamente 10 a 40 KB em VGA); o resultado é um buffer que o firmware pode enviar pela rede, gravar no cartão SD ou analisar localmente com a biblioteca ESP-DL. Duas operações nunca acontecem no mesmo buffer ao mesmo tempo, e é por isso que a escolha de fb_count e do modo de captura define o comportamento do sistema.

Há dois modos de operação que se excluem na prática. O modo streaming maximiza quadros por segundo: buffers duplos, JPEG, prioridade para entregar imagem. O modo inferência na borda troca taxa de quadros por decisão: um modelo quantizado em int8 roda sobre o quadro e devolve uma caixa, um contador ou um booleano. O truque usado em quase todos os projetos que funcionam é desacoplar os dois — capturar a 15–25 fps e rodar detecção a cada N quadros, mantendo o número de quadros capturados estável enquanto a inferência roda a 3–8 fps.

Por que ESP32-S3 e não o ESP32 clássico: o S3 tem vetorização de instruções (PIE) que acelera modelos convolucionais, suporta PSRAM octal a 80 MHz e tem mais largura de banda de DMA. É a diferença entre detecção facial a 10–20 fps em QVGA e detecção engasgada em 3 fps.

(b) BOM — Lista de Materiais

Item Qtd Especificação Observação
MCU 1 ESP32-S3-WROOM-1 N16R8 16 MB flash + 8 MB PSRAM octal — obrigatório para VGA em diante
Módulo câmera 1 OV2640 (2 MP) com FPC de 24 pinos Barato, JPEG on-chip, bom para borda
Alternativa 1 OV5640 (5 MP, autofoco) Mais nitidez, mais consumo e mais calor
Lente 1 66° padrão ou 120° grande angular 120° cobre a bancada toda com distorção de borda
Iluminação 1 LED IR 850 nm + filtro IR-CUT removível Visão noturna; sem IR-CUT a imagem fica roxa de dia
Gatilho 1 Sensor PIR HC-SR501 Acorda a câmera só quando há movimento
Armazenamento 1 microSD SPI Snapshots e datasets de calibração
Gimbal 1 Pescoço pan/tilt do post anterior Fecha o loop de rastreamento
Alimentação 1 Fonte 5 V / 2 A + LDO 3,3 V de 500 mA CRÍTICO: picos do Wi-Fi + câmera causam brownout

(c) Wiring & Pinagem

A câmera consome 15 pinos de dados e controle, mais dois de SCCB (o I2C do sensor). O mapeamento varia por placa: use sempre a tabela do fabricante. O exemplo abaixo é o padrão de fato em módulos ESP32-S3 CAM de 24 pinos.

Sinal da câmera GPIO típico (S3 CAM) Observação
XCLK GPIO15 Clock gerado pelo LEDC; 20 MHz é o ponto de estabilidade para JPEG
SIOD / SIOC GPIO4 / GPIO5 SCCB do sensor (endereço 7 bits 0x30). Pode dividir o barramento com o PCA9685 (0x40)
VSYNC GPIO6 Sincronismo vertical
HREF GPIO7 Sincronismo horizontal
PCLK GPIO13 Pixel clock do DVP
D0–D7 GPIO11, 9, 8, 10, 12, 18, 17, 16 Barramento de 8 bits. Ordem invertida em várias placas — confira
RESET / PWDN −1 / −1 Quando o módulo não expõe esses pinos, deixe −1 na config
3V3 / GND 3V3 e GND Somente 3,3 V: 5 V no DVP queima o sensor e o GPIO

Dois cuidados evitam metade dos problemas. Corrente: o módulo de câmera puxa picos de ~200 mA além do consumo do S3 e do rádio; a linha de 3,3 V precisa de capacitor de 100 µF + 100 nF junto ao módulo, e o LDO deve ter folga. FPC: o cabo flat de 24 pinos é frágil e sensível a dobra; fixe-o com fita e evite movimentá-lo com o sistema ligado — mau contato no FPC se manifesta como erro de inicialização, não como imagem ruim.

(d) Código — captura, medição de FPS e inferência desacoplada

A API é a própria do componente esp32-camera da Espressif. A configuração abaixo é para VGA/JPEG com dois buffers na PSRAM.

#include "esp_camera.h"
#include "esp_timer.h"

static camera_config_t cfg = {
  .pin_pwdn = -1, .pin_reset = -1,
  .pin_xclk = 15,
  .pin_sccb_sda = 4, .pin_sccb_scl = 5,
  .pin_d7 = 16, .pin_d6 = 17, .pin_d5 = 18, .pin_d4 = 12,
  .pin_d3 = 10, .pin_d2 = 8,  .pin_d1 = 9,  .pin_d0 = 11,
  .pin_vsync = 6, .pin_href = 7, .pin_pclk = 13,
  .xclk_freq_hz = 20000000,           // 20 MHz
  .ledc_timer   = LEDC_TIMER_0,
  .ledc_channel = LEDC_CHANNEL_0,
  .pixel_format = PIXFORMAT_JPEG,
  .frame_size   = FRAMESIZE_VGA,      // 640x480
  .jpeg_quality = 12,                 // 0..63 — menor = melhor
  .fb_count     = 2,                  // dois buffers na PSRAM
  .fb_location  = CAMERA_FB_IN_PSRAM,
  .grab_mode    = CAMERA_GRAB_LATEST, // sempre o quadro mais novo
};

void setup() {
  Serial.begin(115200);
  if (esp_camera_init(&cfg) != ESP_OK) {
    Serial.println("Falha ao iniciar a camera");
    return;
  }
}

void loop() {
  static uint32_t frames = 0;
  static int64_t t0 = esp_timer_get_time();

  camera_fb_t *fb = esp_camera_fb_get();   // bloqueia ate haver buffer
  if (!fb) return;

  // aqui vai a inferencia: rode a cada N frames, nao a cada frame
  // ex.: if ((frames % 4) == 0) detecta_pessoa(fb);

  frames++;
  int64_t agora = esp_timer_get_time();
  if (agora - t0 > 2000000) {              // janela de 2 s
    Serial.printf("VGA/JPEG %.1f fps  %u bytes/frame\n",
                  frames * 1e6 / (double)(agora - t0), fb->len);
    frames = 0; t0 = agora;
  }
  esp_camera_fb_return(fb);                // devolve o buffer ao pool
}

Três parâmetros governam tudo. fb_count: com 1 buffer, esp_camera_fb_get() só retorna quando o quadro está completo e você perde captura durante o processamento; com 2, o driver mantém um quadro em escrita e outro pronto, e CAMERA_GRAB_LATEST garante baixa latência em vez de fila. jpeg_quality: a escala é invertida — 12 é qualidade alta e arquivo maior, 30 é qualidade baixa e mais quadros por segundo. frame_size: cada passo de resolução custa banda de PSRAM e tempo de encode.

Taxas realistas em S3 a 240 MHz, PSRAM octal, JPEG, fb_count=2 (valores de ordem de grandeza; variam com clock, barramento e qualidade):

Resolução JPEG típico Captura Com detecção facial (ESP-DL)
QVGA 320×240 8–15 KB 25–40 fps 10–20 fps
VGA 640×480 20–40 KB 15–25 fps 4–8 fps
SVGA 800×600 40–60 KB 10–15 fps 2–4 fps
XGA 1024×768 60–100 KB 6–10 fps 1–2 fps

Para presença e contagem, QVGA é o ponto ótimo: o modelo roda em int8 com entrada 320×240, a imagem inteira já é JPEG pequeno e o S3 sobra para o Wi-Fi. Para leitura de display ou medidor, suba a resolução e aceite 2–5 fps — ninguém precisa de um medidor a 30 fps.

(e) Troubleshooting

Sintoma Causa provável Correção
esp_camera_init retorna 0x105 / NOT_FOUND Pinagem errada, FPC mal encaixado ou placa sem PSRAM Confira o mapeamento do fabricante e reseat o cabo FPC
Inicia, mas sem imagem XCLK alto demais ou polaridade de PCLK invertida Baixe para 10–20 MHz e teste pixel_format JPEG/GRAYSCALE
PSRAM não detectada Modo QSPI em vez de OPI/octal Habilite PSRAM octa na menuconfig e use placa R8
Reinicia ao enviar pela rede Brownout: pico do Wi-Fi somado à câmera LDO com folga, 100 µF + 100 nF no rail de 3,3 V, fonte 5 V/2 A
Imagem rasgada ou “de outro momento” Buffer devolvido cedo ou inferência sobre quadro antigo fb_count=2 + CAMERA_GRAB_LATEST; processe dentro do escopo do framebuffer
Artefatos em cena com movimento do gimbal Rolling shutter do sensor Reduza velocidade do servo ou faça captura estática antes de inferir
Superaquecimento Wi-Fi + câmera contínuos, sem duty cycle Capture por janelas, use PIR como gatilho, ligue Wi-Fi só para enviar

(f) Aplicações reais — o que roda on-device

Com ESP-DL, tudo abaixo roda sem nuvem: detecção facial (caixa e landmarks), detecção de pessoa, classificação de presença e comparação de similaridade simples entre os embeddings faciais (útil para “é o mesmo usuário que chegou”). O que não roda bem é reconhecimento de texto genérico, detecção de dezenas de classes e qualquer modelo acima de poucos MB — isso fica para um host na rede.

As aplicações que amadureceram em produção são prosaicas: contador de pessoas em porta ou bancada, gatilho de presença para acordar o assistente sem manter o microfone sempre ativo, monitoramento de bancada com snapshot por evento, leitura de medidores e displays analógicos (o projeto AI-on-the-edge-device faz exatamente isso em um ESP32 com um modelo convivendo em flash e PSRAM) e o rastreamento do gimbal — o centroide da caixa facial virando comando de pan/tilt, fechando o loop com o hardware do post anterior.

Vale o registro de engenharia: privacidade aqui não é marketing, é consequência da arquitetura. Se a imagem nunca sai do chip, não há stream para vazar. Publique apenas eventos — “1 pessoa detectada às 14:32” — em vez de quadros, e o assistente de bancada passa a ser um sensor de ambiente, não uma câmera de vigilância.

Não some depois da matéria

Radar do hub: Núcleo Hits, Além do Oculto, Bobinho. Sem spray, sem lista comprada.

Radar do hub

Um e-mail quando sair matéria que importa — sem spray de newsletter genérica.

Comentários

Deixe um comentário