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.
#include "esp_camera.h"

// 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_VGA,

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


// Iniciacao do programa
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);
    }
  }
}

// Laço principal
void loop() {
  // Tirar uma foto
  unsigned long inicio = millis();
  camera_fb_t * fb = esp_camera_fb_get();
  unsigned long fim = millis();
  if (fb) {
    Serial.print ("Obteve foto, tempo = ");
    Serial.println (fim-inicio);
    Serial.print ("Tamanho = ");
    Serial.println (fb->len);
    esp_camera_fb_return(fb);
  } else {
    Serial.println ("Erro ao tirar a foto!");
  }
  // Dá um tempo entre as fotos
  delay(3000);
}
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.
// Estrutura do cabeçalho de arquivo BMP
typedef struct {
    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;

// Laço principal com a conversão
void loop() {
  // Dá um tempo entre as fotos
  delay(10000);

  // Tirar uma foto
  unsigned long inicio = millis();
  camera_fb_t * fb = esp_camera_fb_get();
  unsigned long fim = millis();
  if (fb) {
    Serial.print ("Obteve foto, tempo = ");
    Serial.println (fim-inicio);
    Serial.print ("Tamanho = ");
    Serial.println (fb->len);
    uint8_t * buf = NULL;
    size_t buf_len = 0;
    inicio = millis();
    bool converteu = frame2bmp(fb, &buf, &buf_len);    
    fim = millis();
    if (converteu) {
      Serial.print ("Converteu foto, tempo = ");
      Serial.println (fim-inicio);
      bmp_header_t * bitmap  = (bmp_header_t*)&buf[2];
      Serial.print ("Largura = ");
      Serial.println (bitmap->width);
      Serial.print ("Altura = ");
      Serial.println (-bitmap->height);
      free(buf);
    } else {
      Serial.println ("Erro na conversão!");
    }
    esp_camera_fb_return(fb);
  } else {
    Serial.println ("Erro ao tirar a foto!");
  }
}
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?)