terça-feira, maio 26, 2020

Tirando Fotos com o ESP32-CAM

O meu objetivo com o ESP32-CAM é fazer uma aplicação que tire fotos quando detectar movimento e as envie para algum lugar na "nuvem" (algo que já fiz com a Raspeberry usando uma webcam).

Para isso terei que aprender várias coisas, portanto vamos por "baby steps"... Neste primeiro passo vamos ver como tirar uma foto e conseguir analisá-la.

A ideia de um auto-retrato parecia boa, mas a execução deixou a desejar


As rotinas para interagir com a câmera estão em https://github.com/espressif/esp32-camera. Para começar a brincar precisamos:
  1. Preparar a configuração da câmera
  2. Iniciar a câmera através da função esp_camera_init()
  3. Solicitar a foto através da função esp_camera_fb_get()
  4. Processar a foto
  5. Devolver a área de memória recebida de  esp_camera_fb_get() através de esp_camera_fb_return()
O programa abaixo faz isso. A configuração foi feita mesclando o exemplo no link acima como um exemplo do Randon Nerd Tutorials. O tamanho da foto está como VGA (640x480), porque o tamanho UXVGA  (1600x1200) estourou a memória no próximo passo. O "processamento" é só mostrar o tempo para tirar a foto e o tamanho do arquivo.
  1. #include "esp_camera.h"  
  2.   
  3. // Conexões da ESP32-CAM  
  4. #define CAM_PIN_PWDN    32  
  5. #define CAM_PIN_RESET   -1 // usar software reset  
  6. #define CAM_PIN_XCLK    0  
  7. #define CAM_PIN_SIOD    26  
  8. #define CAM_PIN_SIOC    27  
  9.   
  10. #define CAM_PIN_D7      35  
  11. #define CAM_PIN_D6      34  
  12. #define CAM_PIN_D5      39  
  13. #define CAM_PIN_D4      36  
  14. #define CAM_PIN_D3      21  
  15. #define CAM_PIN_D2      19  
  16. #define CAM_PIN_D1      18  
  17. #define CAM_PIN_D0       5  
  18.   
  19. #define CAM_PIN_VSYNC   25  
  20. #define CAM_PIN_HREF    23  
  21. #define CAM_PIN_PCLK    22  
  22.   
  23. // Configuração da camera  
  24. static camera_config_t camera_config = {  
  25.     .pin_pwdn  = CAM_PIN_PWDN,  
  26.     .pin_reset = CAM_PIN_RESET,  
  27.     .pin_xclk = CAM_PIN_XCLK,  
  28.     .pin_sscb_sda = CAM_PIN_SIOD,  
  29.     .pin_sscb_scl = CAM_PIN_SIOC,  
  30.   
  31.     .pin_d7 = CAM_PIN_D7,  
  32.     .pin_d6 = CAM_PIN_D6,  
  33.     .pin_d5 = CAM_PIN_D5,  
  34.     .pin_d4 = CAM_PIN_D4,  
  35.     .pin_d3 = CAM_PIN_D3,  
  36.     .pin_d2 = CAM_PIN_D2,  
  37.     .pin_d1 = CAM_PIN_D1,  
  38.     .pin_d0 = CAM_PIN_D0,  
  39.     .pin_vsync = CAM_PIN_VSYNC,  
  40.     .pin_href = CAM_PIN_HREF,  
  41.     .pin_pclk = CAM_PIN_PCLK,  
  42.   
  43.     .xclk_freq_hz = 20000000,  
  44.     .ledc_timer = LEDC_TIMER_0,  
  45.     .ledc_channel = LEDC_CHANNEL_0,  
  46.   
  47.     .pixel_format = PIXFORMAT_JPEG,  
  48.     .frame_size = FRAMESIZE_VGA,  
  49.   
  50.     .jpeg_quality = 12, //0-63 lower number means higher quality  
  51.     .fb_count = 1 //if more than one, i2s runs in continuous mode. Use only with JPEG  
  52. };  
  53.   
  54.   
  55. // Iniciacao do programa  
  56. void setup() {  
  57.   
  58.   Serial.begin(115200);  
  59.   
  60.   // Iniciar a camera  
  61.   esp_err_t err = esp_camera_init(&camera_config);  
  62.   if (err != ESP_OK) {  
  63.     Serial.print("Erro ");  
  64.     Serial.print (err);  
  65.     Serial.println (" ao iniciar a camera!");  
  66.     // Não pode prosseguir  
  67.     for (;;) {  
  68.       delay (1000);  
  69.     }  
  70.   }  
  71. }  
  72.   
  73. // Laço principal  
  74. void loop() {  
  75.   // Tirar uma foto  
  76.   unsigned long inicio = millis();  
  77.   camera_fb_t * fb = esp_camera_fb_get();  
  78.   unsigned long fim = millis();  
  79.   if (fb) {  
  80.     Serial.print ("Obteve foto, tempo = ");  
  81.     Serial.println (fim-inicio);  
  82.     Serial.print ("Tamanho = ");  
  83.     Serial.println (fb->len);  
  84.     esp_camera_fb_return(fb);  
  85.   } else {  
  86.     Serial.println ("Erro ao tirar a foto!");  
  87.   }  
  88.   // Dá um tempo entre as fotos  
  89.   delay(3000);  
  90. }  
Como recomendado na biblioteca, o formato solicitado é JPG. Este formato implica em uma compressão, a perda de qualidade não é problema mas precisamos descompactar para acessarmos os pixels. Para isso existe a rotina jpg2bmp(). A notar que tanto esta rotina como esp_camera_fb_get() montam em memória exatamente uma imagem do arquivo, incluindo os respectivos cabeçalhos. Novamente a área retornada precisa ser liberada (nesta caso através de free).

Abaixo um outra versão da rotina loop() que gera o bmp e informa a resolução da foto.
  1. // Estrutura do cabeçalho de arquivo BMP  
  2. typedef struct {  
  3.     uint32_t filesize;  
  4.     uint32_t reserved;  
  5.     uint32_t fileoffset_to_pixelarray;  
  6.     uint32_t dibheadersize;  
  7.     int32_t width;  
  8.     int32_t height;  
  9.     uint16_t planes;  
  10.     uint16_t bitsperpixel;  
  11.     uint32_t compression;  
  12.     uint32_t imagesize;  
  13.     uint32_t ypixelpermeter;  
  14.     uint32_t xpixelpermeter;  
  15.     uint32_t numcolorspallette;  
  16.     uint32_t mostimpcolor;  
  17. } bmp_header_t;  
  18.   
  19. // Laço principal com a conversão  
  20. void loop() {  
  21.   // Dá um tempo entre as fotos  
  22.   delay(10000);  
  23.   
  24.   // Tirar uma foto  
  25.   unsigned long inicio = millis();  
  26.   camera_fb_t * fb = esp_camera_fb_get();  
  27.   unsigned long fim = millis();  
  28.   if (fb) {  
  29.     Serial.print ("Obteve foto, tempo = ");  
  30.     Serial.println (fim-inicio);  
  31.     Serial.print ("Tamanho = ");  
  32.     Serial.println (fb->len);  
  33.     uint8_t * buf = NULL;  
  34.     size_t buf_len = 0;  
  35.     inicio = millis();  
  36.     bool converteu = frame2bmp(fb, &buf, &buf_len);      
  37.     fim = millis();  
  38.     if (converteu) {  
  39.       Serial.print ("Converteu foto, tempo = ");  
  40.       Serial.println (fim-inicio);  
  41.       bmp_header_t * bitmap  = (bmp_header_t*)&buf[2];  
  42.       Serial.print ("Largura = ");  
  43.       Serial.println (bitmap->width);  
  44.       Serial.print ("Altura = ");  
  45.       Serial.println (-bitmap->height);  
  46.       free(buf);  
  47.     } else {  
  48.       Serial.println ("Erro na conversão!");  
  49.     }  
  50.     esp_camera_fb_return(fb);  
  51.   } else {  
  52.     Serial.println ("Erro ao tirar a foto!");  
  53.   }  
  54. }  
O próximo passo será comparar duas fotos para detectar movimento.

Um comentário:

Gustavo Vanin disse...

Olá.
O meu deixa a primeira foto esverdeada e depois fica uma foto em defasagem... Se eu fizer a captura e pegar a segunda imagem fica perfeita, com uma qualidade top.
Outra dificuldade é quando está exposta ao sol, a minha gera nulos que não são codificados corretamente. (Já testou a sua na claridade do sol do meio dia?)