Note to self: Testar em locais mais iluminados! |
A minha ideia básica de detecção de movimento foi:
- Tirar uma foto
- Convertê-la para tons de cinza
- Comparar com a anterior
- Considerar que houve movimento se tiver diferença significativa
cinza = 0.2989*R + 0.5870*G + 0.1140*B
Para a minha aplicação (que é comprar fotos), provavelmente eu não precisaria usar estes pesos. Acabei usando, mas de forma aproximada.
O primeiro problema foi a alocação das áreas de memória para guardar as duas imagens em tons de cinza. Declarando como variáveis estáticas com tamanho VGA acusou falta de memória.
Pensando um pouco, decidi que não precisava da imagem com resolução VGA (640x480) para detectar movimento. Decidi usar QVGA (320x240). Mesmo assim, deu erro ao tentar alocar memória dinamicamente. Fui dar uma olhada nos fontes da rotina de conversão para BMP e vi que ela usava uma função diferente para alocar memória:
heap_caps_malloc(tamanho, MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT);
Resumindo uma história complicada, alguns modelos do ESP32 (como o usado na ESP32-CAM) possuem 4M de Ram externa adicional. Apesar desta memória estar ligada via SPI, ela é mapeada no endereçamento do processador e pode ser acessada via ponteiros. Os detalhes estão aqui.
Conseguindo guardar as duas imagens, a próxima questão é como comparar. Fiz algo relativamente simples: comparei pixel a pixel, ignorei diferenças pequenas e no final examino a porcentagem de pixels diferentes. A ideia de ignorar diferenças pequenas é para ficar menos sensíveis a diferenças de luminosidade.
Para completar este teste, resolvi tirar uma foto com resolução UXGA quando movimento é detectado. Mudar a configuração exigiu finalizar a câmera primeiro, ainda dá um erro quando reativa, mas parece ser inócuo.
O código ficou assim:
#include "esp_camera.h" #include "FS.h" #include "SD_MMC.h" // Cabeçalho de arquivo bmp (incluindo a assinatura) typedef struct { uint16_t signature; uint32_t filesize; uint32_t reserved; uint32_t fileoffset_to_pixelarray; uint32_t dibheadersize; int32_t width; int32_t height; uint16_t planes; uint16_t bitsperpixel; uint32_t compression; uint32_t imagesize; uint32_t ypixelpermeter; uint32_t xpixelpermeter; uint32_t numcolorspallette; uint32_t mostimpcolor; } bmp_header_t; // Conexões da ESP32-CAM #define CAM_PIN_PWDN 32 #define CAM_PIN_RESET -1 // usar software reset #define CAM_PIN_XCLK 0 #define CAM_PIN_SIOD 26 #define CAM_PIN_SIOC 27 #define CAM_PIN_D7 35 #define CAM_PIN_D6 34 #define CAM_PIN_D5 39 #define CAM_PIN_D4 36 #define CAM_PIN_D3 21 #define CAM_PIN_D2 19 #define CAM_PIN_D1 18 #define CAM_PIN_D0 5 #define CAM_PIN_VSYNC 25 #define CAM_PIN_HREF 23 #define CAM_PIN_PCLK 22 // Configuração da camera static camera_config_t camera_config = { .pin_pwdn = CAM_PIN_PWDN, .pin_reset = CAM_PIN_RESET, .pin_xclk = CAM_PIN_XCLK, .pin_sscb_sda = CAM_PIN_SIOD, .pin_sscb_scl = CAM_PIN_SIOC, .pin_d7 = CAM_PIN_D7, .pin_d6 = CAM_PIN_D6, .pin_d5 = CAM_PIN_D5, .pin_d4 = CAM_PIN_D4, .pin_d3 = CAM_PIN_D3, .pin_d2 = CAM_PIN_D2, .pin_d1 = CAM_PIN_D1, .pin_d0 = CAM_PIN_D0, .pin_vsync = CAM_PIN_VSYNC, .pin_href = CAM_PIN_HREF, .pin_pclk = CAM_PIN_PCLK, .xclk_freq_hz = 20000000, .ledc_timer = LEDC_TIMER_0, .ledc_channel = LEDC_CHANNEL_0, .pixel_format = PIXFORMAT_JPEG, .frame_size = FRAMESIZE_QVGA, .jpeg_quality = 12, //0-63 lower number means higher quality .fb_count = 1 //if more than one, i2s runs in continuous mode. Use only with JPEG }; const int LARGURA = 320; const int ALTURA = 240; const int DIF_MIN = 10; const int PERC_MOV = 5; // Buffers para a imagem em b&p (tons de cinza) byte *imgbp; int imgAtual = 0; bool primeira = true; void setup() { Serial.begin(115200); // Iniciar a camera esp_err_t err = esp_camera_init(&camera_config); if (err != ESP_OK) { Serial.print("Erro "); Serial.print (err); Serial.println (" ao iniciar a camera!"); // Não pode prosseguir for (;;) { delay (1000); } } // Iniciar o cartão SD if(!SD_MMC.begin() || (SD_MMC.cardType() == CARD_NONE)){ Serial.println("Falha no cartão SD"); // Não pode prosseguir for (;;) { delay (1000); } } fs::FS &fs = SD_MMC; fs.mkdir("/fotos"); imgbp = (byte *) heap_caps_malloc(2*ALTURA*LARGURA, MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT); if (imgbp == NULL) { Serial.println ("Faltou memória!"); // Não pode prosseguir for (;;) { delay (1000); } } Serial.println ("Pronto"); } void loop() { delay(1000); Serial.print ("Tirando foto..."); camera_fb_t * fb = esp_camera_fb_get(); convBP(fb); esp_camera_fb_return(fb); if (primeira) { Serial.println (" Primeira"); primeira = false; } else { Serial.print (" Processando..."); if (diferente()) { Serial.println ("Detectou movimento!"); tiraFotoHi(); delay(30000); primeira = true; } } } // Tira foto em maior resolução e salva void tiraFotoHi() { esp_camera_deinit(); delay(500); camera_config.frame_size = FRAMESIZE_UXGA; esp_err_t err = esp_camera_init(&camera_config); if (err != ESP_OK) { Serial.println("Erro ao configurar camera para foto"); } else { camera_fb_t * fb = esp_camera_fb_get(); if (fb != NULL) { save (fb->buf, fb->len, ".jpg"); esp_camera_fb_return(fb); } esp_camera_deinit(); delay(500); camera_config.frame_size = FRAMESIZE_QVGA; esp_err_t err = esp_camera_init(&camera_config); if (err != ESP_OK) { Serial.println("Erro ao voltar camera para deteccao"); } } } // Converte foto para tons de cinza void convBP(camera_fb_t * fb) { if (fb == NULL) { return; } uint8_t *buf = NULL; size_t buf_len = 0; if (frame2bmp(fb, &buf, &buf_len)) { byte *pixels = buf + sizeof(bmp_header_t); byte *cinza = imgbp; if (imgAtual == 1) { cinza += ALTURA*LARGURA; } for (int i = 0; i < ALTURA*LARGURA; i++) { // cinza = 0.2989*R + 0.5870*G + 0.1140*B // vamos aproximar *cinza++ = (3*pixels[0] + 6*pixels[1] + 1*pixels[2])/10; pixels += 3; } free(buf); imgAtual ^= 1; } } // Compara as imagens b&p bool diferente() { int cont = 0; int dif; byte *img0 = imgbp; byte *img1 = imgbp + ALTURA*LARGURA; for (int i = 0; i < ALTURA*LARGURA; i++) { if (*img0 > *img1) { dif = *img0 - *img1; } else { dif = *img1 - *img0; } if (dif > DIF_MIN) { cont ++; } img0++; img1++; } Serial.print (" Diferenca: "); Serial.println (cont); return cont > ((ALTURA*LARGURA*PERC_MOV)/100); } // Salva uma foto void save (byte *data, int len, char *type) { static int foto = 1; // Nome da foto no cartão String nome = "/fotos/foto" + String(foto) + type; fs::FS &fs = SD_MMC; Serial.printf("Salvando %s\n", nome.c_str()); File file = fs.open(nome.c_str(), FILE_WRITE); if(!file){ Serial.println("Erro ao criar o arquivo"); } else { file.write(data, len); Serial.println("Arquivo salvo"); foto++; } file.close(); }Uma observação: a ESP32-CAM tem um LED forte (para uso como Flash) que está conectado a um dos pinos usados para comunicação com o cartão SD, O resultado é que o LED pisca quando é feito uma acesso ao SD e fica aceso fraco quando o SD não está sendo acessado.Preciso estudar mais para ver se é possível se livrar deste efeito.
No próximo post eu conto o resultado dos meus testes.
9 comentários:
Bom dia.
Estou tentando implementar o código, porém quando é rodado o esp_camera_deinit() e o esp_camera_init() o programa começa a retornar dois erros.
E (17009) i2c: i2c driver install error
E (18136) gpio: gpio_install_isr_service(410): GPIO isr service already installed
Já atualizei as libs e o problema persiste.
Utilizo um esp32cam com a câmera CAMERA_MODEL_AI_THINKER
Enfrentou esse problema?
Teria alguma ideia de como resolver?
Tales, este é o erro que eu mencionei no texto, aparentemente não afeta o desempenho.
Olá. estou tendo problemas com minhas fotos. quando uso uma resolução ou qualidade mais alta, elas aparecem com uma tarja cinza em baixo. Pode ser o tamanho da foto?
não sei como resolver isso.
É difícil dizer o que está causando a tarja cinza, porque isso não me aconteceu, mas pode ser sim um problema de tamanho x memória disponível. Eu acabei encostando essa placa porque me pareceu bastante instável e o resultado deixou a desejar.
e se usar dois cartos sd e madar comparar com a outra
salva nosi cartoes edepois manda comparar
ALEKS, a placa só suporta um cartão. Daria para salvar em dois arquivos e depois comparar, mas a ideia era fazer na memória para ser mais rápido.
Olá,
sou aluno de Eng. controle e Automação.
achei interessante seu artigo ‘’ ESP32-CAM: tirando foto ao Detectar Movimento – primeiras experiências.’’
Estou trabalhando de uma forma de vez de manda as fotos para o Cartão SD, mandar para nuvem, FIREBASE.
Poderia me ajudar nisso ?
ficarei grato se puder fazer um artigo sobre.
Bruno, por enquanto eu dei uma encostada na ESP32-CAM e não tenho experiência com o Firebase, portanto acho que um artigo sobre isso não vai rolar nos próximos meses. Mas, em uma busca rápida, eu achei esta biblioteca: https://github.com/mobizt/Firebase-ESP-Client, um dos exemplos é upload de arquivo, o que poderia ser usado para armazenar fotos. Boa sorte no seu projeto!
Postar um comentário