ESP CAM Image Buffer Delay Chain

Hi, I'm using an ESP Cam board, when I take a photo, I get one two shots old... If I take three photos then I get the latest shot!

Is this standard practice? Are there some intermediary hardware / software buffer chains? Is it possible to just take one shot and get that?

#include <Arduino.h>
#include <WiFi.h>
#include <AsyncTCP.h>
//#include <ESPAsyncWebServer.h>
#include <ESPAsyncWebSrv.h>

#include "esp_camera.h"

#include "soc/soc.h"           // Disable brownour problems
#include "soc/rtc_cntl_reg.h"  // Disable brownour problems

//#include "driver/rtc_io.h"
#include <EEPROM.h>            // read and write from flash memory

#include "FS.h"
#include "SD_MMC.h"            // SD Card ESP32

//#include "camdraw.h"

const char* ssid = "ssid";        //Write your own Wi-Fi name here
const char* password = "pass";    //Write your own password here

IPAddress local_IP(192, 168, 1, 200);
IPAddress gateway(192, 168, 1, 255);
IPAddress subnet(255, 255, 0, 0);
IPAddress primaryDNS(8, 8, 8, 8);
IPAddress secondaryDNS(8, 8, 4, 4);

AsyncWebServer server(80);         //object created on default port 80

// define the number of bytes you want to access
#define EEPROM_SIZE 1

// Pin definition for CAMERA_MODEL_AI_THINKER
#define PWDN_GPIO_NUM     32
#define RESET_GPIO_NUM    -1
#define XCLK_GPIO_NUM      0
#define SIOD_GPIO_NUM     26
#define SIOC_GPIO_NUM     27

#define Y9_GPIO_NUM       35
#define Y8_GPIO_NUM       34
#define Y7_GPIO_NUM       39
#define Y6_GPIO_NUM       36
#define Y5_GPIO_NUM       21
#define Y4_GPIO_NUM       19
#define Y3_GPIO_NUM       18
#define Y2_GPIO_NUM        5
#define VSYNC_GPIO_NUM    25
#define HREF_GPIO_NUM     23
#define PCLK_GPIO_NUM     22


char webpage[] PROGMEM = R"=====(
<!DOCTYPE html>
<html>
<head>
<title>CCam</title>
<style>
 body(background-color:rgba(128,128,128, 0.8)}
</style>
</head>
<body>
<div>
  <div style="width:90%">
    <h2>CCam</h2>
    <button onclick="photo()">Photo</button><br /><br />
  </div>
  
  <div>
    <div id="" style="float:left;padding:15px;">
      <b>Files</b><br />
      <div>
        <button onclick="fm_refresh()">Refresh</button>
      </div>
      <div id="div_dir" style="width:250px;height:300px;overflow:scroll;"></div>
    </div>
    
    <div id="" style="float:left;padding:15px;width:50%;">
      <img src='' id='img1' name='img1' style="width:50%;">
    </div>
  </div>

  <div style="width:90%;clear:left;">
    <h2>FUNCTIONS</h2>
    <button onclick="delete_all()">Delete All Images</button><br />
    <button onclick="reset_count()">Reset Count</button><br />
  </div>
  
</div>
<script>
function photo()
{
  var req = new XMLHttpRequest();
  req.onreadystatechange = function()
  {
    if(this.readyState == 4 && this.status == 200)
    {
      //document.getElementById("div_dir2").innerHTML = this.responseText;
    }
  };
  req.open("GET", "photo", true);
  req.send();
}

function img_set(fn) {
  var pic = document.getElementById("img1");
  //pic.src = "img?fn=picture4.jpg";
  pic.src = "img?fn=" + fn;
}

function fm_refresh()
{
  var req = new XMLHttpRequest();
  req.onreadystatechange = function()
  {
    if(this.readyState == 4 && this.status == 200)
    {
      var data = JSON.parse(this.responseText);
      var ss = "<ul>";
      var len =  data.length;
      for(var i=0;i<len;i++ ){
          ss += "<li onclick=\"img_set('" + data[i][1] + "')\">" + data[i][1] + "</li>";
      }
      ss += "</ul>";
      document.getElementById("div_dir").innerHTML = ss;
    }
  };
  req.open("GET", "ls", true);
  req.send();
}

function delete_all()
{
  var req = new XMLHttpRequest();
  req.onreadystatechange = function()
  {
    if(this.readyState == 4 && this.status == 200)
    {
      //document.getElementById("div_dir2").innerHTML = this.responseText;
    }
  };
  req.open("GET", "delete_all", true);
  req.send();
}

function reset_count()
{
  var req = new XMLHttpRequest();
  req.onreadystatechange = function()
  {
    if(this.readyState == 4 && this.status == 200)
    {
      //document.getElementById("div_dir2").innerHTML = this.responseText;
    }
  };
  req.open("GET", "reset_count", true);
  req.send();
}
</script>
</body>
</html>)=====";

void init_wifi()
{ 
  // Configures static IP address
  if (!WiFi.config(local_IP, gateway, subnet, primaryDNS, secondaryDNS)) 
  {
    Serial.println("Static IP Fail");
  }
  
  WiFi.mode(WIFI_STA);
  WiFi.begin(ssid, password);
  Serial.println("Connecting WiFi");
  while (WiFi.status() != WL_CONNECTED) {
    Serial.print('.');
    delay(1000);
  }
  Serial.println(WiFi.localIP());
}

void init_sd()
{ 
  //if(!SD_MMC.begin())
  if(!SD_MMC.begin("/sdcard", true))  //  disables the flash!!! yeah!
  {
    Serial.println("Initialization Failed");
    return;
  }
  uint8_t cardType = SD_MMC.cardType();

  if(cardType == CARD_NONE){
    Serial.println("SD card is not present!");
    return;
  }

  Serial.print("SD Card Type: ");
  if(cardType == CARD_MMC){
    Serial.println("MMC");
  } else if(cardType == CARD_SD){
    Serial.println("SDSC");
  } else if(cardType == CARD_SDHC){
    Serial.println("SDHC");
  } else {
    Serial.println("UNKNOWN");
  }
  uint64_t cardSize = SD_MMC.cardSize() / (1024 * 1024);
  Serial.printf("SD Card Size: %lluMB\n", cardSize);

  uint64_t card_free = SD_MMC.usedBytes();  // / (1024 * 1024);
  //Serial.printf("SD Card Used: %lluMB\n", card_free);
  Serial.printf("SD Card Used: %lluB\n", card_free);
}

int fs_file_exist(fs::FS &fs, const char * path){
  File file = fs.open(path);
  if(!file){
    return 0; //  file not exist
  }
  return 1; //  file exists
}

String dir_list(fs::FS &fs, const char * dirname)
{
    String s = "";
    File root = fs.open(dirname);
    if(!root){
        Serial.println("Failed to open directory");
        return s;
    }
    if(!root.isDirectory()){
        Serial.println("Not a directory");
        return s;
    }

    s += "[";
    File file = root.openNextFile();
    while(file){
        if(!file.isDirectory())
        {
          String fn = file.name();
          if(fn.endsWith(".jpg"))
          {
            s += "[\"f\",\"" + fn + "\"],";
          }
        }
        file = root.openNextFile();
    }
    s = s.substring(0, s.length()-1);
    s += "]";
    return s;
}

void cam_setup()
{
  camera_config_t config;
  config.ledc_channel = LEDC_CHANNEL_0;
  config.ledc_timer = LEDC_TIMER_0;
  config.pin_d0 = Y2_GPIO_NUM;
  config.pin_d1 = Y3_GPIO_NUM;
  config.pin_d2 = Y4_GPIO_NUM;
  config.pin_d3 = Y5_GPIO_NUM;
  config.pin_d4 = Y6_GPIO_NUM;
  config.pin_d5 = Y7_GPIO_NUM;
  config.pin_d6 = Y8_GPIO_NUM;
  config.pin_d7 = Y9_GPIO_NUM;
  config.pin_xclk = XCLK_GPIO_NUM;
  config.pin_pclk = PCLK_GPIO_NUM;
  config.pin_vsync = VSYNC_GPIO_NUM;
  config.pin_href = HREF_GPIO_NUM;
  config.pin_sscb_sda = SIOD_GPIO_NUM;
  config.pin_sscb_scl = SIOC_GPIO_NUM;
  config.pin_pwdn = PWDN_GPIO_NUM;
  config.pin_reset = RESET_GPIO_NUM;
  config.xclk_freq_hz = 20000000;
  config.pixel_format = PIXFORMAT_JPEG; 
  
  if(psramFound()){
    config.frame_size = FRAMESIZE_UXGA; // FRAMESIZE_ + QVGA|CIF|VGA|SVGA|XGA|SXGA|UXGA
    config.jpeg_quality = 10;
    config.fb_count = 2;
  } else {
    config.frame_size = FRAMESIZE_SVGA;
    config.jpeg_quality = 12;
    config.fb_count = 1;
  }
  
  // Init Camera
  esp_err_t err = esp_camera_init(&config);
  if (err != ESP_OK) {
    Serial.printf("Camera init failed with error 0x%x", err);
    return;
  }

  // initialize EEPROM with predefined size
  EEPROM.begin(EEPROM_SIZE);
  //pictureNumber = EEPROM.read(0) + 1;
  
  //cam_pic();
}

void cam_pic()
{
  camera_fb_t * fb = NULL;
  
  // Take Picture with Camera
  fb = esp_camera_fb_get();
  esp_camera_fb_return(fb);
  
  // Take Picture with Camera
  fb = esp_camera_fb_get();
  esp_camera_fb_return(fb);

  // Take Picture with Camera
  fb = esp_camera_fb_get();
  if(!fb) {
    Serial.println("Camera capture failed");
    return;
  }
  
  int pictureNumber = EEPROM.read(0) + 1;

  // Path where new picture will be saved in SD Card
  String path = "/picture" + String(pictureNumber) +".jpg";

  fs::FS &fs = SD_MMC; 
  Serial.printf("Picture file name: %s\n", path.c_str());
  
  File file = fs.open(path.c_str(), FILE_WRITE);
  if(!file){
    Serial.println("Failed to open file in writing mode");
  } 
  else {
    file.write(fb->buf, fb->len); // payload (image), payload length
    //file.write(rfb.data, sizeof(rfb.data)/sizeof(uint8_t)); // payload (image), payload length
    Serial.printf("Saved file to path: %s\n", path.c_str());
    EEPROM.write(0, pictureNumber);
    EEPROM.commit();
  }
  file.close();
  esp_camera_fb_return(fb);
  
}
/*
void cam_pic2()
{

  
  //  https://stackoverflow.com/questions/56384828/text-overlay-on-video-esp32
  
  //  1- get the image from the camera:
  camera_fb_t *fb = esp_camera_fb_get();
  
  //  2- get the image matrix:
  dl_matrix3du_t *image_matrix = dl_matrix3du_alloc(1, fb->width, fb->height, 3);
  
  //  3- Change the format of the image stored in the image_matrix:
  fmt2rgb888(fb->buf, fb->len, fb->format, image_matrix->item);
  
  //  4- print the text of your choice over that matrix (FF is red color, you can use any uint32_t. Also, take a look at the rgb_print function posted in @sashok1337 response; you can modify it to send the position of the string):
  rgb_print(image_matrix, 0x000000FF, "test");
  
  //  4- Define a buffer and convert the martrix to jpg(you can play with the settings here):
  size_t _jpg_buf_len = 0;
  uint8_t * _jpg_buf = NULL;
  bool jpeg_converted = fmt2jpg(image_matrix->item, fb->width*fb->height*3, fb->width, fb->height, PIXFORMAT_RGB888, 90, &_jpg_buf, &_jpg_buf_len);
  
  //  Once you finished working with the image_matrix, remember to free the memory used by the dl_matrix3du_alloc in the second point above (thank you, @fuenfundachtzig):
  dl_matrix3du_free(image_matrix); 
  
  //  The "frame" with the text over it will be stored in the _jpg_buf array
  

  
  esp_camera_fb_return(fb);
  
}

void cam_pic3()
{
  camera_fb_t * fb = NULL;
  
  // Take Picture with Camera
  fb = esp_camera_fb_get();
  esp_camera_fb_return(fb);
  
  // Take Picture with Camera
  fb = esp_camera_fb_get();
  esp_camera_fb_return(fb);

  // Take Picture with Camera
  //fb = esp_camera_fb_get();
  //if(!fb) {
  //  Serial.println("Camera capture failed");
  //  return;
  //}

  size_t out_len, out_width, out_height;
  uint8_t *out_buf;
  bool s;

  out_len = fb->width * fb->height * 3;
  out_width = fb->width;
  out_height = fb->height;
  out_buf = (uint8_t*)malloc(out_len);
  if (!out_buf) {
      Serial.println("out_buf malloc failed");
  }

  s = fmt2rgb888(fb->buf, fb->len, fb->format, out_buf);

  esp_camera_fb_return(fb);
  if (!s) {
      free(out_buf);
      Serial.println("Camera capture failed: to rgb888 failed");
  }

  fb_data_t rfb;
  rfb.width = fb->width;
  rfb.height = fb->height;
  rfb.data = fb->buf;
  //rfb.bytes_per_pixel = 2;
  //rfb.format = FB_RGB565;   //  2
  rfb.bytes_per_pixel = 3;
  rfb.format = FB_BGR888;

  
  //rgb_print(fb_data_t *fb, uint32_t color, const char *str)
  rgb_print(&rfb, FACE_COLOR_RED, "HELLO");

  //s = fmt2jpg_cb(out_buf, out_len, out_width, out_height, PIXFORMAT_RGB888, 90, jpg_encode_stream, &jchunk);
  free(out_buf);

  
  

  int pictureNumber = EEPROM.read(0) + 1;

  // Path where new picture will be saved in SD Card
  String path = "/picture" + String(pictureNumber) +".jpg";

  fs::FS &fs = SD_MMC; 
  Serial.printf("Picture file name: %s\n", path.c_str());
  
  File file = fs.open(path.c_str(), FILE_WRITE);
  if(!file){
    Serial.println("Failed to open file in writing mode");
  } 
  else {
    //file.write(fb->buf, fb->len); // payload (image), payload length
    file.write(rfb.data, sizeof(rfb.data)/sizeof(uint8_t)); // payload (image), payload length
    Serial.printf("Saved file to path: %s\n", path.c_str());
    EEPROM.write(0, pictureNumber);
    EEPROM.commit();
  }
  file.close();
  esp_camera_fb_return(fb);
  
}
*/

void eeprom_reset_count()
{
  EEPROM.write(0, 0);   //  addr, val
}

void sd_delete_file(fs::FS &fs, const char * path){
    Serial.printf("Deleting file: %s\n", path);
    if(fs.remove(path)){
        Serial.println("File deleted");
    } else {
        Serial.println("Delete failed");
    }
}

void sd_delete_all_images(fs::FS &fs, const char * dirname)
{
    File root = fs.open(dirname);
    if(!root){
        Serial.println("Failed to open directory");
        return;
    }
    if(!root.isDirectory()){
        Serial.println("Not a directory");
        return;
    }

    File file = root.openNextFile();
    while(file){
        if(!file.isDirectory())
        {
          String fn = file.name();
          if(fn.endsWith(".jpg"))
          {
            fn = "/" + fn;
            sd_delete_file(fs, fn.c_str());
          }
        }
        file = root.openNextFile();
    }
}

void setup() {

  WRITE_PERI_REG(RTC_CNTL_BROWN_OUT_REG, 0); //disable brownout detector

  Serial.begin(115200);
  init_sd();
  init_wifi();
  cam_setup();
  
  server.on("/", HTTP_GET, [](AsyncWebServerRequest *request)
  {
    //  http://192.168.1.200/
    request->send(200, "text/html", webpage);
  });

  server.on("/img", HTTP_GET, [](AsyncWebServerRequest *request)
  {
    //  http://192.168.1.200/img?fn=picture4.jpg
    bool flag = true;
    if(request->hasParam("fn", false))    //  for GET
    {
        String fn = request->getParam("fn", false)->value();    //  for GET
        fn = "/" + fn;
        if(fs_file_exist(SD_MMC, fn.c_str())){
          flag = false;
          //  RETURN REQUESTED IMAGE
          request->send(SD_MMC, fn.c_str(), "image/jpeg");
        }
    }
    
    if(flag)
    {
        //  RETURN DEFAULT IMAGE (e.g. latest, first)
        request->send(SD_MMC, "/picture3.jpg", "image/jpeg");
    }
  });
  
  server.on("/ls", HTTP_GET, [](AsyncWebServerRequest *request)
  {
    //  http://192.168.1.200/ls
    //String msg = "[[\"f\", \"picture4.jpg\"],[\"f\",\"picture3.jpg\"]]";
    String msg = dir_list(SD_MMC, "/");
    Serial.println(msg);
    request->send(200, "application/json", msg);
  });
  

  server.on("/photo", HTTP_GET, [](AsyncWebServerRequest *request)
  {
    //  http://192.168.1.200/photo
    cam_pic();
    request->send(200, "text/html", "ok");
  });

  server.on("/delete_all", HTTP_GET, [](AsyncWebServerRequest *request)
  {
    //  http://192.168.1.200/delete_all
    //cam_pic();
    sd_delete_all_images(SD_MMC, "/");
    request->send(200, "text/html", "ok");
  });

  server.on("/reset_count", HTTP_GET, [](AsyncWebServerRequest *request)
  {
    //  http://192.168.1.200/delete_all
    //cam_pic();
    eeprom_reset_count();
    request->send(200, "text/html", "ok");
  });
  
  server.on("/favicon.ico", HTTP_GET, [](AsyncWebServerRequest *request)
  {
    //  http://192.168.1.200/favicon.ico
    //String ico = "<svg height=\"210\" width=\"500\"><polygon points=\"100,10 40,198 190,78 10,78 160,198\" style=\"fill:lime;stroke:purple;stroke-width:5;fill-rule:evenodd;\"/></svg>";
    String ico = "<svg height=\"100\" width=\"100\" xmlns=\"http://www.w3.org/2000/svg\"><ellipse cx=\"50\" cy=\"50\" rx=\"45\" ry=\"45\" style=\"fill:yellow;stroke:purple;stroke-width:2\" /><text x=\"50%\" y=\"50%\" dominant-baseline=\"middle\" text-anchor=\"middle\" fill=\"red\">CCam</text></svg>";
    request->send(200, "image/svg+xml", ico);
  });
  
  server.begin();
}

void loop() {
  // put your main code here, to run repeatedly:

}

On a side note...
Can anyone finish the cam_pic3() function? It is attempting to draw text and lines on an image and then save it. You'll need the extra src for this task if you decide to accept it :smiley:

#include "esp_camera.h"

#include "img_converters.h"
#include "fb_gfx.h"

#define FACE_COLOR_WHITE 0x00FFFFFF
#define FACE_COLOR_BLACK 0x00000000
#define FACE_COLOR_RED 0x000000FF
#define FACE_COLOR_GREEN 0x0000FF00
#define FACE_COLOR_BLUE 0x00FF0000
#define FACE_COLOR_YELLOW (FACE_COLOR_RED | FACE_COLOR_GREEN)
#define FACE_COLOR_CYAN (FACE_COLOR_BLUE | FACE_COLOR_GREEN)
#define FACE_COLOR_PURPLE (FACE_COLOR_BLUE | FACE_COLOR_RED)

static void rgb_print(fb_data_t *fb, uint32_t color, const char *str)
{
    fb_gfx_print(fb, (fb->width - (strlen(str) * 14)) / 2, 10, color, str);
}

static int rgb_printf(fb_data_t *fb, uint32_t color, const char *format, ...)
{
    char loc_buf[64];
    char *temp = loc_buf;
    int len;
    va_list arg;
    va_list copy;
    va_start(arg, format);
    va_copy(copy, arg);
    len = vsnprintf(loc_buf, sizeof(loc_buf), format, arg);
    va_end(copy);
    if (len >= sizeof(loc_buf))
    {
        temp = (char *)malloc(len + 1);
        if (temp == NULL)
        {
            return 0;
        }
    }
    vsnprintf(temp, len + 1, format, arg);
    va_end(arg);
    rgb_print(fb, color, temp);
    if (len > 64)
    {
        free(temp);
    }
    return len;
}

Thanks

REFS

ESP Project: CameraWebServer

Standard practice.

Helps a lot to improve the picture quality and exposure of the final image.

1 Like

try

config.fb_count = 1;

You also might try playing with the value of 'config.grab_mode'. It takes values:

/**
 * @brief Configuration structure for camera initialization
 */
typedef enum {
    CAMERA_GRAB_WHEN_EMPTY,         /*!< Fills buffers when they are empty. Less resources but first 'fb_count' frames might be old */
    CAMERA_GRAB_LATEST              /*!< Except when 1 frame buffer is used, queue will always contain the last 'fb_count' frames */
} camera_grab_mode_t;

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.