Image resize for ESP32-CAM

Hello,
I am currently using a ESP32-CAM to recognize digits on a water meter.
I would like to crop the picture tooked by the cam in order to isolate the digits. In order to check the picture cropped I send it on a webServer (temporary solution for testing) but when I crop an image, it doesn't display on the websever


I printed in the console the picture values and they exist (picture has pixels with different values). I just need help to check the cropped image. Someone has an idea why I have this problem ?

Currently, here is my code :

#include "esp_camera.h"
#include <WiFi.h>
#include "esp_timer.h"
#include "img_converters.h"
#include "Arduino.h"
#include "fb_gfx.h"
#include "soc/soc.h"
#include "soc/rtc_cntl_reg.h"
#include "esp_http_server.h"
#include <HTTPClient.h>
#include "gsc_model_fixed.h"

// WiFi Credentials
const char* ssid = "Khu S";
const char* password = "khu@s2022";

// HTTP server handles
httpd_handle_t stream_httpd = NULL;
httpd_handle_t camera_httpd = NULL;

// LED control parameters
#define LED_CHANNEL     0
#define LED_RESOLUTION  8
#define LED_GPIO_NUM    4
const int defaultFlashIntensity = 20;

// Region of Interest (ROI) for cropping
#define ROI_X_MIN 0
#define ROI_Y_MIN 0
#define ROI_X_MAX 800 
#define ROI_Y_MAX 600 

// Interval for capturing images
const unsigned long captureInterval = 100000;
unsigned long lastCaptureTime = 0;

// Camera model configuration
#define CAMERA_MODEL_AI_THINKER
#if defined(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
#else
  #error "Camera model not selected"
#endif

// Function Prototypes
void setupFlashPWM();
void setFlashIntensity(int intensity);
camera_fb_t* crop_image(camera_fb_t* src_fb, int x_min, int y_min, int x_max, int y_max);
static esp_err_t capture_handler(httpd_req_t *req);
void startCameraServer();
void setup();
void loop();

// Flash LED setup
void setupFlashPWM() {
    ledcAttachPin(LED_GPIO_NUM, LED_CHANNEL);
    ledcSetup(LED_CHANNEL, 5000, LED_RESOLUTION);
}

uint8_t* resizeImageBilinear(uint8_t* inputImage, int inputWidth, int inputHeight, int outputWidth, int outputHeight) {
    uint8_t* outputImage = (uint8_t*)malloc(outputWidth * outputHeight); // Allouer de la mémoire pour l'image de sortie

    float scaleX = (float)inputWidth / (float)outputWidth;
    float scaleY = (float)inputHeight / (float)outputHeight;
    
    for (int y = 0; y < outputHeight; y++) {
        for (int x = 0; x < outputWidth; x++) {
            float sourceX = x * scaleX;
            float sourceY = y * scaleY;

            int sourceX0 = (int)sourceX;
            int sourceY0 = (int)sourceY;
            int sourceX1 = min(sourceX0 + 1, inputWidth - 1);
            int sourceY1 = min(sourceY0 + 1, inputHeight - 1);

            float xFraction = sourceX - sourceX0;
            float yFraction = sourceY - sourceY0;

            uint8_t topLeft = inputImage[sourceY0 * inputWidth + sourceX0];
            uint8_t topRight = inputImage[sourceY0 * inputWidth + sourceX1];
            uint8_t bottomLeft = inputImage[sourceY1 * inputWidth + sourceX0];
            uint8_t bottomRight = inputImage[sourceY1 * inputWidth + sourceX1];

            // Interpoler les valeurs des pixels
            float topInterpolated = topLeft * (1 - xFraction) + topRight * xFraction;
            float bottomInterpolated = bottomLeft * (1 - xFraction) + bottomRight * xFraction;

            float interpolatedValue = topInterpolated * (1 - yFraction) + bottomInterpolated * yFraction;

            outputImage[y * outputWidth + x] = (uint8_t)interpolatedValue;
        }
    }

    return outputImage;
}

// Set the intensity of the flash LED
void setFlashIntensity(int intensity) {
    ledcWrite(LED_CHANNEL, intensity);
}

// Crop the captured image
camera_fb_t* crop_image(camera_fb_t* src_fb, int x_min, int y_min, int x_max, int y_max) {
    int src_width = src_fb->width;
    int src_height = src_fb->height;

    if (x_min < 0) x_min = 0;
    if (y_min < 0) y_min = 0;
    if (x_max > src_width) x_max = src_width;
    if (y_max > src_height) y_max = src_height;

    int crop_width = x_max - x_min;
    int crop_height = y_max - y_min;

    camera_fb_t* cropped_fb = esp_camera_fb_get();
    if (!cropped_fb) {
        Serial.println("Failed to allocate memory for cropped image");
        return NULL;
    }

    cropped_fb->format = PIXFORMAT_GRAYSCALE;
    cropped_fb->width = crop_width;
    cropped_fb->height = crop_height;
    cropped_fb->len = crop_width * crop_height;

    uint8_t* src_data = src_fb->buf;
    uint8_t* cropped_data = cropped_fb->buf;
    for (int y = y_min; y < y_max; y++) {
        for (int x = x_min; x < x_max; x++) {
            int src_index = y * src_width + x;
            int cropped_index = (y - y_min) * crop_width + (x - x_min);
            cropped_data[cropped_index] = src_data[src_index];
        }
    }

    return cropped_fb;
}

// Capture handler for HTTP requests
// Capture handler for HTTP requests
// Capture handler for HTTP requests
static esp_err_t capture_handler(httpd_req_t *req) {
    setFlashIntensity(defaultFlashIntensity);
    delay(50);

    camera_fb_t * fb = esp_camera_fb_get();
    if (!fb) {
        Serial.println("Camera capture failed");
        httpd_resp_send_500(req);
        setFlashIntensity(0);
        return ESP_FAIL;
    }

    camera_fb_t* cropped_fb = crop_image(fb, ROI_X_MIN, ROI_Y_MIN, ROI_X_MAX, ROI_Y_MAX);
    if (!cropped_fb) {
        Serial.println("Failed to crop image");
        httpd_resp_send_500(req);
        esp_camera_fb_return(fb);
        setFlashIntensity(0);
        return ESP_FAIL;
    }

    // Redimensionner l'image à la taille souhaitée (par exemple, 28x28 pixels)
    int desiredWidth = 28;
    int desiredHeight = 28;
    uint8_t* resizedImage = resizeImageBilinear(cropped_fb->buf, cropped_fb->width, cropped_fb->height, desiredWidth, desiredHeight);

    // Envoyer l'image redimensionnée en tant que réponse HTTP
    httpd_resp_set_type(req, "image/jpeg");
    httpd_resp_set_hdr(req, "Content-Disposition", "inline; filename=captured.jpg");
    httpd_resp_set_hdr(req, "Access-Control-Allow-Origin", "*");

    esp_err_t res = httpd_resp_send(req, (const char *)resizedImage, desiredWidth * desiredHeight);

    // Libérer la mémoire allouée pour les images
    esp_camera_fb_return(fb);
    esp_camera_fb_return(cropped_fb);
    free(resizedImage); // Libérer la mémoire de l'image redimensionnée
    delay(5);
    setFlashIntensity(0);

    return res;
}



// Start the camera server
void startCameraServer() {
    httpd_config_t config = HTTPD_DEFAULT_CONFIG();
    config.server_port = 80;

    if (httpd_start(&stream_httpd, &config) == ESP_OK) {
        httpd_uri_t capture_uri = {
            .uri       = "/capture",
            .method    = HTTP_GET,
            .handler   = capture_handler,
            .user_ctx  = NULL
        };
        httpd_register_uri_handler(stream_httpd, &capture_uri);
    } else {
        Serial.println("Error starting stream server");
    }
}

// Setup function
void setup() {
    WRITE_PERI_REG(RTC_CNTL_BROWN_OUT_REG, 0);
    Serial.begin(115200);
    Serial.setDebugOutput(false);
    pinMode(LED_GPIO_NUM, OUTPUT);
    setupFlashPWM();
    setFlashIntensity(defaultFlashIntensity);

    // Camera configuration
    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_SVGA;
        config.jpeg_quality = 10;
        config.fb_count = 2;
    } else {
        config.frame_size = FRAMESIZE_SVGA;
        config.jpeg_quality = 12;
        config.fb_count = 1;
    }

    esp_err_t err = esp_camera_init(&config);
    if (err != ESP_OK) {
        Serial.printf("Camera init failed with error 0x%x", err);
        return;
    }

    WiFi.begin(ssid, password);
    while (WiFi.status() != WL_CONNECTED) {
        delay(50);
        Serial.print(".");
    }
    Serial.println();
    Serial.println("WiFi connected");
    Serial.print("Camera Stream Ready! Go to: http://");
    Serial.print(WiFi.localIP());

    startCameraServer();
    lastCaptureTime = millis();
}

// Loop function
void loop() {
    unsigned long currentTime = millis();
    if (currentTime - lastCaptureTime < captureInterval) {
        delay(10);
        return;
    }

    String captureUrl = "http://" + WiFi.localIP().toString() + "/capture";

    HTTPClient http;
    http.begin(captureUrl);
    int httpCode = http.GET();

    if (httpCode == HTTP_CODE_OK) {
        Serial.println("Capture d'image réussie");
    } else {
        Serial.printf("Échec de la capture d'image, code HTTP: %d\n", httpCode);
    }

    http.end();
    lastCaptureTime = currentTime;
    delay(10);
}

you send raw binary data of the cropped region without a matching BMP header.

Hello,
Thank you for you answer.
I tried something but I still have the problem...

// Define BMP header structure
typedef struct {
    uint16_t bfType;
    uint32_t bfSize;
    uint16_t bfReserved1;
    uint16_t bfReserved2;
    uint32_t bfOffBits;
} __attribute__((packed)) bmp_header_t;

// Send cropped image with BMP header
static esp_err_t capture_handler(httpd_req_t *req) {
    // Toggle the LED to indicate photo capture
    setFlashIntensity(defaultFlashIntensity);
    delay(50);

    camera_fb_t *fb = NULL;
    esp_err_t res = ESP_OK;

    // Capture a photo
    fb = esp_camera_fb_get();
    if (!fb) {
        Serial.println("Camera capture failed");
        httpd_resp_send_500(req);
        digitalWrite(LED_GPIO_NUM, LOW); // Turn off the LED if capture failed
        return ESP_FAIL;
    }

    // Crop the captured image
    camera_fb_t* cropped_fb = crop_image(fb, ROI_X_MIN, ROI_Y_MIN, ROI_X_MAX, ROI_Y_MAX);
    if (!cropped_fb) {
        Serial.println("Failed to crop image");
        httpd_resp_send_500(req);
        esp_camera_fb_return(fb);
        digitalWrite(LED_GPIO_NUM, LOW); // Turn off the LED if cropping failed
        return ESP_FAIL;
    }

    // Allocate memory for BMP header and image
    bmp_header_t bmp_header;
    size_t bmp_header_size = sizeof(bmp_header);
    size_t image_size = cropped_fb->len;
    size_t response_size = bmp_header_size + image_size;
    uint8_t *response_buffer = (uint8_t *)malloc(response_size);

    if (!response_buffer) {
        Serial.println("Failed to allocate memory for response buffer");
        httpd_resp_send_500(req);
        esp_camera_fb_return(fb);
        esp_camera_fb_return(cropped_fb);
        digitalWrite(LED_GPIO_NUM, LOW);
        return ESP_FAIL;
    }

    // Fill BMP header with necessary information
    bmp_header.bfType = 0x4D42; // BMP magic number
    bmp_header.bfSize = response_size;
    bmp_header.bfReserved1 = 0;
    bmp_header.bfReserved2 = 0;
    bmp_header.bfOffBits = bmp_header_size;

    // Copy BMP header to response buffer
    memcpy(response_buffer, &bmp_header, bmp_header_size);

    // Copy image data to response buffer
    memcpy(response_buffer + bmp_header_size, cropped_fb->buf, image_size);

    // Set content type and headers
    httpd_resp_set_type(req, "image/bmp");
    httpd_resp_set_hdr(req, "Content-Disposition", "inline; filename=captured.bmp");
    httpd_resp_set_hdr(req, "Access-Control-Allow-Origin", "*");

    // Send HTTP response with BMP header and image data
    res = httpd_resp_send(req, (const char *)response_buffer, response_size);

    // Free allocated memory and return
    free(response_buffer);
    esp_camera_fb_return(fb);
    esp_camera_fb_return(cropped_fb);
    digitalWrite(LED_GPIO_NUM, LOW); // Turn off the LED after photo capture process is complete
    return res;
}

The interristing thing is that when I crop the image in 800x600 (which at the begining is a 1600x1200 image) Something is displayed but it is really weird


(btw it only works without the bmp header idk why)
I am bit lost for the moment ..

You need to build a valid image from your raw data. Probably your BMP header does not match the raw data. You could try to upload an image as you get it from the ESP32 so we know what you have.

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