Grayscale and binary cannot send uxga resolution images

grayscale and binary cannot send uxga resolution images. Error Capture Failed UXGA

// Function to clamp values between 0 and 255
inline uint8_t clamp(int value) {
    if (value < 0) return 0;
    if (value > 255) return 255;
    return (uint8_t)value;
}

// Function to convert YUV422 to RGB for high resolution (UXGA)
void yuv422_to_rgb(uint8_t* yuv_image, uint8_t* rgb_image, int width, int height) {
    int num_pixels = width * height;
    for (int i = 0; i < num_pixels; i += 2) {
        int base = i * 2;
        uint8_t y0 = yuv_image[base];
        uint8_t u  = yuv_image[base + 1];
        uint8_t y1 = yuv_image[base + 2];
        uint8_t v  = yuv_image[base + 3];

        // Konversi piksel pertama
        int c = y0 - 16;
        int d = u - 128;
        int e = v - 128;

        int r = (298 * c + 409 * e + 128) >> 8;
        int g = (298 * c - 100 * d - 208 * e + 128) >> 8;
        int b = (298 * c + 516 * d + 128) >> 8;

        rgb_image[i * 3]     = clamp(r);
        rgb_image[i * 3 + 1] = clamp(g);
        rgb_image[i * 3 + 2] = clamp(b);

        // Konversi piksel kedua
        c = y1 - 16;
        // d dan e tetap sama untuk kedua piksel
        r = (298 * c + 409 * e + 128) >> 8;
        g = (298 * c - 100 * d - 208 * e + 128) >> 8;
        b = (298 * c + 516 * d + 128) >> 8;

        rgb_image[(i + 1) * 3]     = clamp(r);
        rgb_image[(i + 1) * 3 + 1] = clamp(g);
        rgb_image[(i + 1) * 3 + 2] = clamp(b);
    }
}

// Function to convert RGB to Grayscale using integer arithmetic
void convertToGrayscale(uint8_t* rgb_image, uint8_t* gray_image, int width, int height) {
    int num_pixels = width * height;
    for (int i = 0; i < num_pixels; i++) {
        uint8_t r = rgb_image[i * 3];
        uint8_t g = rgb_image[i * 3 + 1];
        uint8_t b = rgb_image[i * 3 + 2];
        // Using integer approximation: 0.299*R + 0.587*G + 0.114*B ≈ (77*R + 150*G + 29*B) >> 8
        gray_image[i] = (77 * r + 150 * g + 29 * b) >> 8;
    }
}

// Function to perform PCA on grayscale image
int16_t* pca_grayscale(uint8_t* gray_image, int width, int height) {
    int num_pixels = width * height;
    int16_t* pca_result = new int16_t[num_pixels];
    
    // Calculate mean
    long sum = 0;
    for (int i = 0; i < num_pixels; i++) {
        sum += gray_image[i];
    }
    float mean = (float)sum / num_pixels;
    
    // Center the data
    for (int i = 0; i < num_pixels; i++) {
        pca_result[i] = gray_image[i] - (int16_t)mean;
    }
    
    // Note: This is still a simplified PCA implementation.
    // A full PCA would require computing eigenvectors, etc.
    
    return pca_result;
}

// Function to perform Otsu's thresholding
uint8_t* otsu_threshold(uint8_t* gray_image, int width, int height) {
    uint8_t* binary_image = new uint8_t[width * height];
    
    // Calculate histogram
    int histogram[256] = {0};
    for (int i = 0; i < width * height; i++) {
        histogram[gray_image[i]]++;
    }
    
    // Calculate total number of pixels
    int total = width * height;
    
    float sum = 0;
    for (int i = 0; i < 256; i++) {
        sum += i * histogram[i];
    }
    
    float sumB = 0;
    int wB = 0;
    int wF = 0;
    
    float varMax = 0;
    int threshold = 0;
    
    for (int i = 0; i < 256; i++) {
        wB += histogram[i];
        if (wB == 0) continue;
        wF = total - wB;
        if (wF == 0) break;
        
        sumB += i * histogram[i];
        float mB = sumB / wB;
        float mF = (sum - sumB) / wF;
        
        float varBetween = wB * wF * (mB - mF) * (mB - mF);
        
        if (varBetween > varMax) {
            varMax = varBetween;
            threshold = i;
        }
    }
    
    // Apply threshold
    for (int i = 0; i < width * height; i++) {
        binary_image[i] = (gray_image[i] > threshold) ? 255 : 0;
    }
    
    return binary_image;
}

bool jpeg_to_bmp(uint8_t *jpeg_buffer, size_t jpeg_len, int width, int height, pixformat_t format, uint8_t **bmp_buffer, size_t *bmp_len) {
    // Alokasikan buffer untuk gambar RGB
    uint8_t *rgb_image = (uint8_t *)malloc(width * height * 3); // 3 bytes per pixel for RGB

    if (!rgb_image) {
        Serial.println("Failed to allocate memory for RGB image");
        return false;
    }

    // Dekompresi JPEG menjadi RGB tanpa argumen width dan height, hanya kirimkan buffer hasil
    if (!fmt2rgb888(jpeg_buffer, jpeg_len, format, rgb_image)) {
        Serial.println("JPEG decompression failed");
        free(rgb_image); // Jangan lupa untuk membersihkan memori yang dialokasikan
        return false;
    }

    // BMP file header (14 bytes)
    uint8_t bmp_file_header[14] = {
        'B', 'M',                           // Signature
        0, 0, 0, 0,                         // File size (akan diisi nanti)
        0, 0, 0, 0,                         // Reserved
        54, 0, 0, 0                         // Offset ke data gambar
    };

    // BMP info header (40 bytes)
    uint8_t bmp_info_header[40] = {
        40, 0, 0, 0,                        // Header size
        0, 0, 0, 0,                         // Lebar gambar (akan diisi nanti)
        0, 0, 0, 0,                         // Tinggi gambar (akan diisi nanti)
        1, 0,                               // Jumlah planes warna
        24, 0,                              // Bits per pixel (24 untuk RGB)
        0, 0, 0, 0,                         // Tidak ada kompresi
        0, 0, 0, 0,                         // Ukuran gambar (bisa 0 untuk tanpa kompresi)
        0x13, 0x0B, 0, 0,                   // Resolusi horizontal (2835 pixels/meter)
        0x13, 0x0B, 0, 0,                   // Resolusi vertikal (2835 pixels/meter)
        0, 0, 0, 0,                         // Jumlah warna dalam palet (0 = default)
        0, 0, 0, 0                          // Warna penting (0 = semua)
    };

    // Isi width dan height ke header BMP
    bmp_info_header[4] = (uint8_t)(width);
    bmp_info_header[5] = (uint8_t)(width >> 8);
    bmp_info_header[6] = (uint8_t)(width >> 16);
    bmp_info_header[7] = (uint8_t)(width >> 24);

    bmp_info_header[8] = (uint8_t)(height);
    bmp_info_header[9] = (uint8_t)(height >> 8);
    bmp_info_header[10] = (uint8_t)(height >> 16);
    bmp_info_header[11] = (uint8_t)(height >> 24);

    // Ukuran total file BMP (header + data gambar)
    int row_size = (width * 3 + 3) & (~3);  // Lebar baris harus kelipatan 4 byte
    int image_size = row_size * height;
    int file_size = 54 + image_size;

    // Isi ukuran file di file header BMP
    bmp_file_header[2] = (uint8_t)(file_size);
    bmp_file_header[3] = (uint8_t)(file_size >> 8);
    bmp_file_header[4] = (uint8_t)(file_size >> 16);
    bmp_file_header[5] = (uint8_t)(file_size >> 24);

    // Alokasikan buffer BMP
    *bmp_len = file_size;
    *bmp_buffer = (uint8_t *)malloc(file_size);
    if (*bmp_buffer == NULL) {
        Serial.println("Failed to allocate memory for BMP");
        free(rgb_image);
        return false;
    }

    // Salin header file BMP ke buffer
    memcpy(*bmp_buffer, bmp_file_header, 14);
    memcpy(*bmp_buffer + 14, bmp_info_header, 40);

    // Salin data gambar dengan membalikkan secara vertikal (karena format BMP)
    for (int y = 0; y < height; y++) {
        for (int x = 0; x < width; x++) {
            int src_index = ((height - 1 - y) * width + x) * 3; // Urutan terbalik
            int dst_index = 54 + y * row_size + x * 3;
            (*bmp_buffer)[dst_index] = rgb_image[src_index + 2];   // Biru
            (*bmp_buffer)[dst_index + 1] = rgb_image[src_index + 1]; // Hijau
            (*bmp_buffer)[dst_index + 2] = rgb_image[src_index];   // Merah
        }
    }

    // Bersihkan memori sementara
    free(rgb_image);
    return true;
}

esp_err_t pca_grayscale_handler() {
    // Inisialisasi kamera ke mode YUV422
    init_camera(FRAMESIZE_SVGA, PIXFORMAT_YUV422);

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

    int width = fb->width;
    int height = fb->height;
    int num_pixels = width * height;

    // Alokasikan memori untuk gambar RGB dan grayscale
    uint8_t* rgb_image = new uint8_t[num_pixels * 3];
    uint8_t* gray_image = new uint8_t[num_pixels];

    // Konversi YUV422 ke RGB
    yuv422_to_rgb(fb->buf, rgb_image, width, height);

    // Konversi RGB ke Grayscale
    convertToGrayscale(rgb_image, gray_image, width, height);

    // Lakukan PCA pada gambar grayscale
    int16_t* pca_result = pca_grayscale(gray_image, width, height);

    // Cari nilai min dan max dari hasil PCA untuk normalisasi
    int16_t min_val = pca_result[0];
    int16_t max_val = pca_result[0];
    for (int i = 1; i < num_pixels; i++) {
        if (pca_result[i] < min_val) min_val = pca_result[i];
        if (pca_result[i] > max_val) max_val = pca_result[i];
    }

    // Normalisasi nilai PCA ke rentang 0-255
    uint8_t* normalized_pca = new uint8_t[num_pixels];
    float range = max_val - min_val;
    if (range == 0) range = 1; // Hindari pembagian dengan nol
    for (int i = 0; i < num_pixels; i++) {
        normalized_pca[i] = (uint8_t)(((pca_result[i] - min_val) / range) * 255);
    }

    // Konversi buffer JPEG ke BMP
    uint8_t *bmp_buffer = NULL;
    size_t bmp_len = 0;
    bool bmp_success = jpeg_to_bmp(normalized_pca, num_pixels, width, height, PIXFORMAT_GRAYSCALE, &bmp_buffer, &bmp_len);

    if (!bmp_success) {
        server.send(500, "text/plain", "BMP conversion failed");
    } else {
        // Set header konten BMP
        server.sendHeader("Content-Type", "image/bmp");
        server.sendHeader("Content-Disposition", "inline; filename=pca_grayscale.bmp");

        // Kirim gambar BMP
        server.sendContent((const char *)bmp_buffer, bmp_len);
    }

    // Bersihkan memori yang dialokasikan
    delete[] rgb_image;
    delete[] gray_image;
    delete[] pca_result;
    delete[] normalized_pca;
    free(bmp_buffer);
    esp_camera_fb_return(fb);

    // Kembalikan kamera ke mode JPEG untuk streaming
    init_camera(FRAME_SIZE_STREAM, PIXFORMAT_JPEG);

    return ESP_OK;
}

// Handler for binary image
esp_err_t binary_handler() {
    // Inisialisasi kamera ke mode YUV422
    init_camera(FRAMESIZE_SVGA, PIXFORMAT_YUV422);

    camera_fb_t *fb = esp_camera_fb_get();
    if (!fb) {
        Serial.println("Camera capture failed");
        return ESP_FAIL;
    }
    
    int width = fb->width;
    int height = fb->height;
    int num_pixels = width * height;
    
    uint8_t* rgb_image = new uint8_t[num_pixels * 3];
    uint8_t* gray_image = new uint8_t[num_pixels];
    
    yuv422_to_rgb(fb->buf, rgb_image, width, height);
    convertToGrayscale(rgb_image, gray_image, width, height);
    
    uint8_t* binary_image = otsu_threshold(gray_image, width, height);
    
    // Konversi buffer JPEG ke BMP
    uint8_t *bmp_buffer = NULL;
    size_t bmp_len = 0;
    bool bmp_success = jpeg_to_bmp(binary_image, num_pixels, width, height, PIXFORMAT_GRAYSCALE, &bmp_buffer, &bmp_len);
    
    if (!bmp_success) {
        server.send(500, "text/plain", "BMP conversion failed");
    } else {
        // Set header konten BMP
        server.sendHeader("Content-Type", "image/bmp");
        server.sendHeader("Content-Disposition", "inline; filename=pca_grayscale.bmp");

        // Kirim gambar BMP
        server.sendContent((const char *)bmp_buffer, bmp_len);
    }
    
    // Free allocated memory
    delete[] rgb_image;
    delete[] gray_image;
    delete[] binary_image;
    free(bmp_buffer);
    esp_camera_fb_return(fb);
    
    // Revert camera back to JPEG for streaming
    init_camera(FRAME_SIZE_STREAM, PIXFORMAT_JPEG);
    
    return ESP_OK;
}

Please post all the code, and the complete error message, also using code tags.

This is the complete code. There was only 1 error message, when I changed the resolution to UXGA in binary and pca grayscale the image couldn't be recorded

pca_grayscale_handler and binary_handler
init_camera(FRAMESIZE_SVGA, PIXFORMAT_YUV422); working
init_camera(FRAMESIZE_UXGA, PIXFORMAT_YUV422); no working

#include "Arduino.h"
#include "esp_camera.h"
#include "WiFi.h"
#include "WebServer.h"
#include "esp_timer.h"
#include "esp_system.h"
#include "algorithm"
#include "numeric"

// Camera pins and configurations
#define CAMERA_MODEL_ESP32S3_EYE // Has PSRAM
#include "camera_pins.h"

// WiFi credentials
const char* ap_ssid = "ESP32_Object_Counter";
const char* ap_password = "12345678";  // Password must be at least 8 characters

// Web server
WebServer server(80);

// Define the RGB structure
struct RGB {
    uint8_t r, g, b;
};

// Define the boundary for the multipart streaming
#define PART_BOUNDARY "123456789000000000000987654321"
#define _STREAM_CONTENT_TYPE "multipart/x-mixed-replace;boundary=" PART_BOUNDARY
#define _STREAM_BOUNDARY "\r\n--" PART_BOUNDARY "\r\n"
#define _STREAM_PART "Content-Type: image/jpeg\r\nContent-Length: %u\r\n\r\n"

#define FRAME_SIZE_STREAM FRAMESIZE_SVGA
#define FRAME_SIZE_CAPTURE FRAMESIZE_UXGA

// Function prototypes
void startCameraServer();
void handleRoot();
void handleCapture();

// Initialize the camera
esp_err_t init_camera(framesize_t frame_size, pixformat_t pixel_format) {
    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;

    // Parameter input untuk resolusi dan format
    config.grab_mode = CAMERA_GRAB_WHEN_EMPTY;
    config.fb_location = CAMERA_FB_IN_PSRAM;
    config.pixel_format = pixel_format;
    config.frame_size = frame_size;
    config.jpeg_quality = 10; // Kualitas JPEG (semakin tinggi angka semakin rendah kualitas)
    config.fb_count = 2;      // Jumlah frame buffer

    // Deinitialize camera first
    esp_camera_deinit();

    // Initialize the camera with new settings
    return esp_camera_init(&config);
}

// Stream video function
void streamVideo() {
    Serial.println("Stream video function called");
    camera_fb_t * fb = NULL;
    esp_err_t res = ESP_OK;
    size_t _jpg_buf_len = 0;
    uint8_t * _jpg_buf = NULL;
    char part_buf[64];

    // Set up the streaming headers
    Serial.println("Setting content type for stream");
    server.setContentLength(CONTENT_LENGTH_UNKNOWN);
    server.send(200, _STREAM_CONTENT_TYPE, "");

    static int64_t last_frame = 0;
    if (!last_frame) {
        last_frame = esp_timer_get_time();
    }

    // Begin streaming loop
    while (server.client().connected()) {
        fb = esp_camera_fb_get();  // Get a frame from the camera
        if (!fb) {
            Serial.println("Camera capture failed");
            res = ESP_FAIL;
        } else {
            if (fb->format != PIXFORMAT_JPEG) {
                bool jpeg_converted = frame2jpg(fb, 80, &_jpg_buf, &_jpg_buf_len);
                esp_camera_fb_return(fb);
                fb = NULL;
                if (!jpeg_converted) {
                    Serial.println("JPEG compression failed");
                    res = ESP_FAIL;
                }
            } else {
                _jpg_buf_len = fb->len;
                _jpg_buf = fb->buf;
            }
        }

        if (res == ESP_OK) {
            // Send the multipart headers and image
            size_t hlen = snprintf((char *)part_buf, 64, _STREAM_PART, _jpg_buf_len);
            server.sendContent((const char *)part_buf, hlen);   // Send header for this part
            server.sendContent((const char *)_jpg_buf, _jpg_buf_len); // Send image
            server.sendContent(_STREAM_BOUNDARY);               // Boundary between images
        }

        if (fb) {
            esp_camera_fb_return(fb); // Return frame buffer back to the camera
            fb = NULL;
            _jpg_buf = NULL;
        } else if (_jpg_buf) {
            free(_jpg_buf);
            _jpg_buf = NULL;
        }

        if (res != ESP_OK) {
            break;  // Exit the streaming loop if there's an error
        }

        int64_t fr_end = esp_timer_get_time();
        int64_t frame_time = fr_end - last_frame;
        last_frame = fr_end;
        frame_time /= 1000;

        Serial.printf("MJPG: %uB %ums (%.1ffps)\n", (uint32_t)(_jpg_buf_len),
                      (uint32_t)frame_time, 1000.0 / (uint32_t)frame_time);
    }

    // Close the stream when done
    last_frame = 0;
    Serial.println("Stream closed");
}

// Handle root route ("/") to serve the HTML page
void handleRoot() {
  const char* htmlContent = R"rawliteral(
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>ESP32 Camera</title>
  <style>
      body {
          font-family: Arial, sans-serif;
          display: flex;
          justify-content: center;
          align-items: center;
          height: 100vh;
          margin: 0;
          background-color: #f0f0f0;
      }
  
      .container {
          text-align: center;
          background-color: white;
          padding: 30px;
          border-radius: 15px;
          box-shadow: 0 0 20px rgba(0,0,0,0.1);
          width: 90%;
          max-width: 600px;
      }
  
      h1 {
          color: #333;
          margin-bottom: 30px;
      }
  
      .camera-container {
          position: relative;
          width: 100%;
          max-width: 400px;
          aspect-ratio: 1 / 1;
          overflow: hidden;
          margin: 0 auto 30px;
          border-radius: 50%;
          border: 5px solid #333;
          box-shadow: 0 5px 15px rgba(0,0,0,0.2);
      }
  
      #cameraFeed {
          width: 100%;
          height: 100%;
          object-fit: cover;
          border-radius: 50%;
      }
  
      button {
          background-color: #4CAF50;
          border: none;
          color: white;
          padding: 15px 32px;
          text-align: center;
          text-decoration: none;
          display: inline-block;
          font-size: 16px;
          margin: 4px 2px;
          cursor: pointer;
          border-radius: 8px;
          transition: background-color 0.3s;
          box-shadow: 0 2px 5px rgba(0,0,0,0.2);
      }

      button:hover {
          background-color: #45a049;
      }
  </style>
</head>
<body>
    <div class="container">
        <h1>ESP32 Camera</h1>
        <div class="camera-container">
            <img id="cameraFeed" src="/stream" alt="Camera Stream">
        </div>
        <button onclick="saveImage()">Save Image</button>
    </div>
    <script>
        function saveImage() {
            const video = document.getElementById('cameraFeed');
            const canvas = document.createElement('canvas');
            canvas.width = 1600;
            canvas.height = 1600;
            const context = canvas.getContext('2d');
            
            // Draw white background
            context.fillStyle = 'white';
            context.fillRect(0, 0, canvas.width, canvas.height);
            
            // Calculate new position for the circular camera feed
            const centerY = 800;
            const radius = 600;
            const videoSize = radius * 2;
            const videoY = centerY - radius;
            
            // Draw circular camera feed
            context.save();
            context.beginPath();
            context.arc(800, centerY, radius, 0, Math.PI * 2);
            context.clip();
            context.drawImage(video, 200, videoY, videoSize, videoSize);
            context.restore();
            
            // Draw border
            context.strokeStyle = '#333';
            context.lineWidth = 20;
            context.beginPath();
            context.arc(800, centerY, radius, 0, Math.PI * 2);
            context.stroke();
            
            canvas.toBlob(function(blob) {
                const link = document.createElement('a');
                link.href = URL.createObjectURL(blob);
                link.download = 'esp32_camera.jpg';
                link.click();
            }, 'image/jpeg');
        }
    </script>
</body>
</html>
)rawliteral";
  server.send(200, "text/html", htmlContent);
}

// Handle the capture request
void handleCapture() {
    // Inisialisasi kamera untuk capture
    init_camera(FRAME_SIZE_CAPTURE, PIXFORMAT_JPEG);

    // Ambil gambar
    camera_fb_t *fb = esp_camera_fb_get();
    if (!fb) {
        Serial.println("Camera capture failed");
        return;
    }

    // Kirim gambar melalui server
    server.send(200, "image/jpeg", String((const char *)fb->buf, fb->len)); // Mengonversi buffer ke String

    // Lepaskan frame buffer
    esp_camera_fb_return(fb);

    // Inisialisasi ulang kamera untuk streaming
    init_camera(FRAME_SIZE_STREAM, PIXFORMAT_JPEG);
}

// Function to clamp values between 0 and 255
inline uint8_t clamp(int value) {
    if (value < 0) return 0;
    if (value > 255) return 255;
    return (uint8_t)value;
}

// Function to convert YUV422 to RGB for high resolution (UXGA)
void yuv422_to_rgb(uint8_t* yuv_image, uint8_t* rgb_image, int width, int height) {
    int num_pixels = width * height;
    for (int i = 0; i < num_pixels; i += 2) {
        int base = i * 2;
        uint8_t y0 = yuv_image[base];
        uint8_t u  = yuv_image[base + 1];
        uint8_t y1 = yuv_image[base + 2];
        uint8_t v  = yuv_image[base + 3];

        // Konversi piksel pertama
        int c = y0 - 16;
        int d = u - 128;
        int e = v - 128;

        int r = (298 * c + 409 * e + 128) >> 8;
        int g = (298 * c - 100 * d - 208 * e + 128) >> 8;
        int b = (298 * c + 516 * d + 128) >> 8;

        rgb_image[i * 3]     = clamp(r);
        rgb_image[i * 3 + 1] = clamp(g);
        rgb_image[i * 3 + 2] = clamp(b);

        // Konversi piksel kedua
        c = y1 - 16;
        // d dan e tetap sama untuk kedua piksel
        r = (298 * c + 409 * e + 128) >> 8;
        g = (298 * c - 100 * d - 208 * e + 128) >> 8;
        b = (298 * c + 516 * d + 128) >> 8;

        rgb_image[(i + 1) * 3]     = clamp(r);
        rgb_image[(i + 1) * 3 + 1] = clamp(g);
        rgb_image[(i + 1) * 3 + 2] = clamp(b);
    }
}

// Function to convert RGB to Grayscale using integer arithmetic
void convertToGrayscale(uint8_t* rgb_image, uint8_t* gray_image, int width, int height) {
    int num_pixels = width * height;
    for (int i = 0; i < num_pixels; i++) {
        uint8_t r = rgb_image[i * 3];
        uint8_t g = rgb_image[i * 3 + 1];
        uint8_t b = rgb_image[i * 3 + 2];
        // Using integer approximation: 0.299*R + 0.587*G + 0.114*B ≈ (77*R + 150*G + 29*B) >> 8
        gray_image[i] = (77 * r + 150 * g + 29 * b) >> 8;
    }
}

// Function to perform PCA on grayscale image
int16_t* pca_grayscale(uint8_t* gray_image, int width, int height) {
    int num_pixels = width * height;
    int16_t* pca_result = new int16_t[num_pixels];
    
    // Calculate mean
    long sum = 0;
    for (int i = 0; i < num_pixels; i++) {
        sum += gray_image[i];
    }
    float mean = (float)sum / num_pixels;
    
    // Center the data
    for (int i = 0; i < num_pixels; i++) {
        pca_result[i] = gray_image[i] - (int16_t)mean;
    }
    
    // Note: This is still a simplified PCA implementation.
    // A full PCA would require computing eigenvectors, etc.
    
    return pca_result;
}

// Function to perform Otsu's thresholding
uint8_t* otsu_threshold(uint8_t* gray_image, int width, int height) {
    uint8_t* binary_image = new uint8_t[width * height];
    
    // Calculate histogram
    int histogram[256] = {0};
    for (int i = 0; i < width * height; i++) {
        histogram[gray_image[i]]++;
    }
    
    // Calculate total number of pixels
    int total = width * height;
    
    float sum = 0;
    for (int i = 0; i < 256; i++) {
        sum += i * histogram[i];
    }
    
    float sumB = 0;
    int wB = 0;
    int wF = 0;
    
    float varMax = 0;
    int threshold = 0;
    
    for (int i = 0; i < 256; i++) {
        wB += histogram[i];
        if (wB == 0) continue;
        wF = total - wB;
        if (wF == 0) break;
        
        sumB += i * histogram[i];
        float mB = sumB / wB;
        float mF = (sum - sumB) / wF;
        
        float varBetween = wB * wF * (mB - mF) * (mB - mF);
        
        if (varBetween > varMax) {
            varMax = varBetween;
            threshold = i;
        }
    }
    
    // Apply threshold
    for (int i = 0; i < width * height; i++) {
        binary_image[i] = (gray_image[i] > threshold) ? 255 : 0;
    }
    
    return binary_image;
}

bool jpeg_to_bmp(uint8_t *jpeg_buffer, size_t jpeg_len, int width, int height, pixformat_t format, uint8_t **bmp_buffer, size_t *bmp_len) {
    // Alokasikan buffer untuk gambar RGB
    uint8_t *rgb_image = (uint8_t *)malloc(width * height * 3); // 3 bytes per pixel for RGB

    if (!rgb_image) {
        Serial.println("Failed to allocate memory for RGB image");
        return false;
    }

    // Dekompresi JPEG menjadi RGB tanpa argumen width dan height, hanya kirimkan buffer hasil
    if (!fmt2rgb888(jpeg_buffer, jpeg_len, format, rgb_image)) {
        Serial.println("JPEG decompression failed");
        free(rgb_image); // Jangan lupa untuk membersihkan memori yang dialokasikan
        return false;
    }

    // BMP file header (14 bytes)
    uint8_t bmp_file_header[14] = {
        'B', 'M',                           // Signature
        0, 0, 0, 0,                         // File size (akan diisi nanti)
        0, 0, 0, 0,                         // Reserved
        54, 0, 0, 0                         // Offset ke data gambar
    };

    // BMP info header (40 bytes)
    uint8_t bmp_info_header[40] = {
        40, 0, 0, 0,                        // Header size
        0, 0, 0, 0,                         // Lebar gambar (akan diisi nanti)
        0, 0, 0, 0,                         // Tinggi gambar (akan diisi nanti)
        1, 0,                               // Jumlah planes warna
        24, 0,                              // Bits per pixel (24 untuk RGB)
        0, 0, 0, 0,                         // Tidak ada kompresi
        0, 0, 0, 0,                         // Ukuran gambar (bisa 0 untuk tanpa kompresi)
        0x13, 0x0B, 0, 0,                   // Resolusi horizontal (2835 pixels/meter)
        0x13, 0x0B, 0, 0,                   // Resolusi vertikal (2835 pixels/meter)
        0, 0, 0, 0,                         // Jumlah warna dalam palet (0 = default)
        0, 0, 0, 0                          // Warna penting (0 = semua)
    };

    // Isi width dan height ke header BMP
    bmp_info_header[4] = (uint8_t)(width);
    bmp_info_header[5] = (uint8_t)(width >> 8);
    bmp_info_header[6] = (uint8_t)(width >> 16);
    bmp_info_header[7] = (uint8_t)(width >> 24);

    bmp_info_header[8] = (uint8_t)(height);
    bmp_info_header[9] = (uint8_t)(height >> 8);
    bmp_info_header[10] = (uint8_t)(height >> 16);
    bmp_info_header[11] = (uint8_t)(height >> 24);

    // Ukuran total file BMP (header + data gambar)
    int row_size = (width * 3 + 3) & (~3);  // Lebar baris harus kelipatan 4 byte
    int image_size = row_size * height;
    int file_size = 54 + image_size;

    // Isi ukuran file di file header BMP
    bmp_file_header[2] = (uint8_t)(file_size);
    bmp_file_header[3] = (uint8_t)(file_size >> 8);
    bmp_file_header[4] = (uint8_t)(file_size >> 16);
    bmp_file_header[5] = (uint8_t)(file_size >> 24);

    // Alokasikan buffer BMP
    *bmp_len = file_size;
    *bmp_buffer = (uint8_t *)malloc(file_size);
    if (*bmp_buffer == NULL) {
        Serial.println("Failed to allocate memory for BMP");
        free(rgb_image);
        return false;
    }

    // Salin header file BMP ke buffer
    memcpy(*bmp_buffer, bmp_file_header, 14);
    memcpy(*bmp_buffer + 14, bmp_info_header, 40);

    // Salin data gambar dengan membalikkan secara vertikal (karena format BMP)
    for (int y = 0; y < height; y++) {
        for (int x = 0; x < width; x++) {
            int src_index = ((height - 1 - y) * width + x) * 3; // Urutan terbalik
            int dst_index = 54 + y * row_size + x * 3;
            (*bmp_buffer)[dst_index] = rgb_image[src_index + 2];   // Biru
            (*bmp_buffer)[dst_index + 1] = rgb_image[src_index + 1]; // Hijau
            (*bmp_buffer)[dst_index + 2] = rgb_image[src_index];   // Merah
        }
    }

    // Bersihkan memori sementara
    free(rgb_image);
    return true;
}

esp_err_t pca_grayscale_handler() {
    // Inisialisasi kamera ke mode YUV422
    init_camera(FRAMESIZE_SVGA, PIXFORMAT_YUV422);

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

    int width = fb->width;
    int height = fb->height;
    int num_pixels = width * height;

    // Alokasikan memori untuk gambar RGB dan grayscale
    uint8_t* rgb_image = new uint8_t[num_pixels * 3];
    uint8_t* gray_image = new uint8_t[num_pixels];

    // Konversi YUV422 ke RGB
    yuv422_to_rgb(fb->buf, rgb_image, width, height);

    // Konversi RGB ke Grayscale
    convertToGrayscale(rgb_image, gray_image, width, height);

    // Lakukan PCA pada gambar grayscale
    int16_t* pca_result = pca_grayscale(gray_image, width, height);

    // Cari nilai min dan max dari hasil PCA untuk normalisasi
    int16_t min_val = pca_result[0];
    int16_t max_val = pca_result[0];
    for (int i = 1; i < num_pixels; i++) {
        if (pca_result[i] < min_val) min_val = pca_result[i];
        if (pca_result[i] > max_val) max_val = pca_result[i];
    }

    // Normalisasi nilai PCA ke rentang 0-255
    uint8_t* normalized_pca = new uint8_t[num_pixels];
    float range = max_val - min_val;
    if (range == 0) range = 1; // Hindari pembagian dengan nol
    for (int i = 0; i < num_pixels; i++) {
        normalized_pca[i] = (uint8_t)(((pca_result[i] - min_val) / range) * 255);
    }

    // Konversi buffer JPEG ke BMP
    uint8_t *bmp_buffer = NULL;
    size_t bmp_len = 0;
    bool bmp_success = jpeg_to_bmp(normalized_pca, num_pixels, width, height, PIXFORMAT_GRAYSCALE, &bmp_buffer, &bmp_len);

    if (!bmp_success) {
        server.send(500, "text/plain", "BMP conversion failed");
    } else {
        // Set header konten BMP
        server.sendHeader("Content-Type", "image/bmp");
        server.sendHeader("Content-Disposition", "inline; filename=pca_grayscale.bmp");

        // Kirim gambar BMP
        server.sendContent((const char *)bmp_buffer, bmp_len);
    }

    // Bersihkan memori yang dialokasikan
    delete[] rgb_image;
    delete[] gray_image;
    delete[] pca_result;
    delete[] normalized_pca;
    free(bmp_buffer);
    esp_camera_fb_return(fb);

    // Kembalikan kamera ke mode JPEG untuk streaming
    init_camera(FRAME_SIZE_STREAM, PIXFORMAT_JPEG);

    return ESP_OK;
}

// Handler for binary image
esp_err_t binary_handler() {
    // Inisialisasi kamera ke mode YUV422
    init_camera(FRAMESIZE_SVGA, PIXFORMAT_YUV422);

    camera_fb_t *fb = esp_camera_fb_get();
    if (!fb) {
        Serial.println("Camera capture failed");
        return ESP_FAIL;
    }
    
    int width = fb->width;
    int height = fb->height;
    int num_pixels = width * height;
    
    uint8_t* rgb_image = new uint8_t[num_pixels * 3];
    uint8_t* gray_image = new uint8_t[num_pixels];
    
    yuv422_to_rgb(fb->buf, rgb_image, width, height);
    convertToGrayscale(rgb_image, gray_image, width, height);
    
    uint8_t* binary_image = otsu_threshold(gray_image, width, height);
    
    // Konversi buffer JPEG ke BMP
    uint8_t *bmp_buffer = NULL;
    size_t bmp_len = 0;
    bool bmp_success = jpeg_to_bmp(binary_image, num_pixels, width, height, PIXFORMAT_GRAYSCALE, &bmp_buffer, &bmp_len);
    
    if (!bmp_success) {
        server.send(500, "text/plain", "BMP conversion failed");
    } else {
        // Set header konten BMP
        server.sendHeader("Content-Type", "image/bmp");
        server.sendHeader("Content-Disposition", "inline; filename=pca_grayscale.bmp");

        // Kirim gambar BMP
        server.sendContent((const char *)bmp_buffer, bmp_len);
    }
    
    // Free allocated memory
    delete[] rgb_image;
    delete[] gray_image;
    delete[] binary_image;
    free(bmp_buffer);
    esp_camera_fb_return(fb);
    
    // Revert camera back to JPEG for streaming
    init_camera(FRAME_SIZE_STREAM, PIXFORMAT_JPEG);
    
    return ESP_OK;
}

// Start the web server
void startCameraServer() {
    server.on("/", HTTP_GET, []() {
        Serial.println("Root route accessed");
        handleRoot();
    });
    server.on("/stream", HTTP_GET, streamVideo);
    server.on("/capture", HTTP_GET, handleCapture);
    server.on("/pca_grayscale", HTTP_GET, pca_grayscale_handler);
    server.on("/binary", HTTP_GET, binary_handler);
    
    server.begin();
    Serial.println("HTTP server started");
}

// Setup function
void setup() {
    Serial.begin(115200);
    Serial.println("Starting setup...");

    // Setup WiFi AP mode
    WiFi.softAP(ap_ssid, ap_password);

    IPAddress IP = WiFi.softAPIP();
    Serial.print("Access Point IP: ");
    Serial.println(IP);

    #ifdef RGB_BUILTIN
      digitalWrite(RGB_BUILTIN, HIGH);   // Turn the RGB LED white
      delay(1000);
      digitalWrite(RGB_BUILTIN, LOW);    // Turn the RGB LED off
      delay(1000);
 
      neopixelWrite(RGB_BUILTIN,RGB_BRIGHTNESS,0,0); // Red
      delay(1000);
      neopixelWrite(RGB_BUILTIN,0,RGB_BRIGHTNESS,0); // Green
      delay(1000);
      neopixelWrite(RGB_BUILTIN,0,0,RGB_BRIGHTNESS); // Blue
      delay(1000);
      neopixelWrite(RGB_BUILTIN,0,0,0); // Off / black
      delay(1000);
    #endif

    // Initialize camera with low resolution for streaming
    Serial.println("Initializing camera...");
    esp_err_t err = init_camera(FRAMESIZE_SVGA, PIXFORMAT_JPEG);
    if (err != ESP_OK) {
        Serial.printf("Camera init failed with error 0x%x\n", err);
        return;
    }
    Serial.println("Camera initialized successfully");

    Serial.println("Starting camera server...");
    startCameraServer();
    Serial.println("Camera server started");

    Serial.println("Setup complete");
}

// Main loop
void loop() {
    server.handleClient();
}

It's not a complete code, it doesn't contain setup() and loop()

You also not shown a error message.

Post #3 does :wink: Might be due a slight language barrier; I also had to read twice what OP said / meant and check that code.

My code is almost close to UXGA which is SXGA in this part "esp_err_t err = init_camera(FRAMESIZE_SXGA, PIXFORMAT_YUV422);". So when I change the resolution to UXGA the web image doesn't appear even though the serial monitor shows the process is successful, what should I change so that UXGA can appear on the web?

#include "Arduino.h"
#include "esp_camera.h"
#include "WiFi.h"
#include "WebServer.h"
#include "esp_timer.h"
#include "esp_system.h"
#include "algorithm"
#include "numeric"

// Camera pins and configurations
#define CAMERA_MODEL_ESP32S3_EYE // Has PSRAM
#include "camera_pins.h"

// WiFi credentials
const char* ap_ssid = "ESP32_Object_Counter";
const char* ap_password = "12345678";  // Password must be at least 8 characters

// Web server
WebServer server(80);

// Define the RGB structure
struct RGB {
    uint8_t r, g, b;
};

// Define the boundary for the multipart streaming
#define PART_BOUNDARY "123456789000000000000987654321"
#define _STREAM_CONTENT_TYPE "multipart/x-mixed-replace;boundary=" PART_BOUNDARY
#define _STREAM_BOUNDARY "\r\n--" PART_BOUNDARY "\r\n"
#define _STREAM_PART "Content-Type: image/jpeg\r\nContent-Length: %u\r\n\r\n"

#define FRAME_SIZE_STREAM FRAMESIZE_SVGA
#define FRAME_SIZE_CAPTURE FRAMESIZE_UXGA

// Function prototypes
void startCameraServer();
void handleRoot();
void handleCapture();

// Initialize the camera
esp_err_t init_camera(framesize_t frame_size, pixformat_t pixel_format) {
    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;

    // Parameter input untuk resolusi dan format
    config.grab_mode = CAMERA_GRAB_WHEN_EMPTY;
    config.fb_location = CAMERA_FB_IN_PSRAM;
    config.pixel_format = pixel_format;
    config.frame_size = frame_size;
    config.jpeg_quality = 10; // Kualitas JPEG (semakin tinggi angka semakin rendah kualitas)
    config.fb_count = 2;      // Jumlah frame buffer

    // Deinitialize camera first
    esp_camera_deinit();

    // Initialize the camera with new settings
    return esp_camera_init(&config);
}

// Stream video function
void streamVideo() {
    Serial.println("Stream video function called");
    camera_fb_t * fb = NULL;
    esp_err_t res = ESP_OK;
    size_t _jpg_buf_len = 0;
    uint8_t * _jpg_buf = NULL;
    char part_buf[64];

    // Set up the streaming headers
    Serial.println("Setting content type for stream");
    server.setContentLength(CONTENT_LENGTH_UNKNOWN);
    server.send(200, _STREAM_CONTENT_TYPE, "");

    static int64_t last_frame = 0;
    if (!last_frame) {
        last_frame = esp_timer_get_time();
    }

    // Begin streaming loop
    while (server.client().connected()) {
        fb = esp_camera_fb_get();  // Get a frame from the camera
        if (!fb) {
            Serial.println("Camera capture failed");
            res = ESP_FAIL;
        } else {
            if (fb->format != PIXFORMAT_JPEG) {
                bool jpeg_converted = frame2jpg(fb, 80, &_jpg_buf, &_jpg_buf_len);
                esp_camera_fb_return(fb);
                fb = NULL;
                if (!jpeg_converted) {
                    Serial.println("JPEG compression failed");
                    res = ESP_FAIL;
                }
            } else {
                _jpg_buf_len = fb->len;
                _jpg_buf = fb->buf;
            }
        }

        if (res == ESP_OK) {
            // Send the multipart headers and image
            size_t hlen = snprintf((char *)part_buf, 64, _STREAM_PART, _jpg_buf_len);
            server.sendContent((const char *)part_buf, hlen);   // Send header for this part
            server.sendContent((const char *)_jpg_buf, _jpg_buf_len); // Send image
            server.sendContent(_STREAM_BOUNDARY);               // Boundary between images
        }

        if (fb) {
            esp_camera_fb_return(fb); // Return frame buffer back to the camera
            fb = NULL;
            _jpg_buf = NULL;
        } else if (_jpg_buf) {
            free(_jpg_buf);
            _jpg_buf = NULL;
        }

        if (res != ESP_OK) {
            break;  // Exit the streaming loop if there's an error
        }

        int64_t fr_end = esp_timer_get_time();
        int64_t frame_time = fr_end - last_frame;
        last_frame = fr_end;
        frame_time /= 1000;

        Serial.printf("MJPG: %uB %ums (%.1ffps)\n", (uint32_t)(_jpg_buf_len),
                      (uint32_t)frame_time, 1000.0 / (uint32_t)frame_time);
    }

    // Close the stream when done
    last_frame = 0;
    Serial.println("Stream closed");
}

// Handle root route ("/") to serve the HTML page
void handleRoot() {
  const char* htmlContent = R"rawliteral(
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>ESP32 Camera</title>
  <style>
      body {
          font-family: Arial, sans-serif;
          display: flex;
          justify-content: center;
          align-items: center;
          height: 100vh;
          margin: 0;
          background-color: #f0f0f0;
      }
  
      .container {
          text-align: center;
          background-color: white;
          padding: 30px;
          border-radius: 15px;
          box-shadow: 0 0 20px rgba(0,0,0,0.1);
          width: 90%;
          max-width: 600px;
      }
  
      h1 {
          color: #333;
          margin-bottom: 30px;
      }
  
      .camera-container {
          position: relative;
          width: 100%;
          max-width: 400px;
          aspect-ratio: 1 / 1;
          overflow: hidden;
          margin: 0 auto 30px;
          border-radius: 50%;
          border: 5px solid #333;
          box-shadow: 0 5px 15px rgba(0,0,0,0.2);
      }
  
      #cameraFeed {
          width: 100%;
          height: 100%;
          object-fit: cover;
          border-radius: 50%;
      }
  
      button {
          background-color: #4CAF50;
          border: none;
          color: white;
          padding: 15px 32px;
          text-align: center;
          text-decoration: none;
          display: inline-block;
          font-size: 16px;
          margin: 4px 2px;
          cursor: pointer;
          border-radius: 8px;
          transition: background-color 0.3s;
          box-shadow: 0 2px 5px rgba(0,0,0,0.2);
      }

      button:hover {
          background-color: #45a049;
      }
  </style>
</head>
<body>
    <div class="container">
        <h1>ESP32 Camera</h1>
        <div class="camera-container">
            <img id="cameraFeed" src="/stream" alt="Camera Stream">
        </div>
        <button onclick="saveImage()">Save Image</button>
    </div>
    <script>
        function saveImage() {
            const video = document.getElementById('cameraFeed');
            const canvas = document.createElement('canvas');
            canvas.width = 1600;
            canvas.height = 1600;
            const context = canvas.getContext('2d');
            
            // Draw white background
            context.fillStyle = 'white';
            context.fillRect(0, 0, canvas.width, canvas.height);
            
            // Calculate new position for the circular camera feed
            const centerY = 800;
            const radius = 600;
            const videoSize = radius * 2;
            const videoY = centerY - radius;
            
            // Draw circular camera feed
            context.save();
            context.beginPath();
            context.arc(800, centerY, radius, 0, Math.PI * 2);
            context.clip();
            context.drawImage(video, 200, videoY, videoSize, videoSize);
            context.restore();
            
            // Draw border
            context.strokeStyle = '#333';
            context.lineWidth = 20;
            context.beginPath();
            context.arc(800, centerY, radius, 0, Math.PI * 2);
            context.stroke();
            
            canvas.toBlob(function(blob) {
                const link = document.createElement('a');
                link.href = URL.createObjectURL(blob);
                link.download = 'esp32_camera.jpg';
                link.click();
            }, 'image/jpeg');
        }
    </script>
</body>
</html>
)rawliteral";
  server.send(200, "text/html", htmlContent);
}

// Handle the capture request
void handleCapture() {
    // Inisialisasi kamera untuk capture
    init_camera(FRAME_SIZE_CAPTURE, PIXFORMAT_JPEG);

    // Ambil gambar
    camera_fb_t *fb = esp_camera_fb_get();
    if (!fb) {
        Serial.println("Camera capture failed");
        return;
    }

    // Kirim gambar melalui server
    server.send(200, "image/jpeg", String((const char *)fb->buf, fb->len)); // Mengonversi buffer ke String

    // Lepaskan frame buffer
    esp_camera_fb_return(fb);

    // Inisialisasi ulang kamera untuk streaming
    init_camera(FRAME_SIZE_STREAM, PIXFORMAT_JPEG);
}

// Function to clamp values between 0 and 255
inline uint8_t clamp(int value) {
    if (value < 0) return 0;
    if (value > 255) return 255;
    return (uint8_t)value;
}

// Function to convert YUV422 to RGB for high resolution (UXGA)
void yuv422_to_rgb(uint8_t* yuv_image, uint8_t* rgb_image, int width, int height) {
    int num_pixels = width * height;
    for (int i = 0; i < num_pixels; i += 2) {
        int base = i * 2;
        uint8_t y0 = yuv_image[base];
        uint8_t u  = yuv_image[base + 1];
        uint8_t y1 = yuv_image[base + 2];
        uint8_t v  = yuv_image[base + 3];

        // Konversi piksel pertama
        int c = y0 - 16;
        int d = u - 128;
        int e = v - 128;

        int r = (298 * c + 409 * e + 128) >> 8;
        int g = (298 * c - 100 * d - 208 * e + 128) >> 8;
        int b = (298 * c + 516 * d + 128) >> 8;

        rgb_image[i * 3]     = clamp(r);
        rgb_image[i * 3 + 1] = clamp(g);
        rgb_image[i * 3 + 2] = clamp(b);

        // Konversi piksel kedua
        c = y1 - 16;
        // d dan e tetap sama untuk kedua piksel
        r = (298 * c + 409 * e + 128) >> 8;
        g = (298 * c - 100 * d - 208 * e + 128) >> 8;
        b = (298 * c + 516 * d + 128) >> 8;

        rgb_image[(i + 1) * 3]     = clamp(r);
        rgb_image[(i + 1) * 3 + 1] = clamp(g);
        rgb_image[(i + 1) * 3 + 2] = clamp(b);
    }
}

// Function to convert RGB to Grayscale using integer arithmetic
void convertToGrayscale(uint8_t* rgb_image, uint8_t* gray_image, int width, int height) {
    int num_pixels = width * height;
    for (int i = 0; i < num_pixels; i++) {
        uint8_t r = rgb_image[i * 3];
        uint8_t g = rgb_image[i * 3 + 1];
        uint8_t b = rgb_image[i * 3 + 2];
        // Using integer approximation: 0.299*R + 0.587*G + 0.114*B ≈ (77*R + 150*G + 29*B) >> 8
        gray_image[i] = (77 * r + 150 * g + 29 * b) >> 8;
    }
}

// Function to perform PCA on grayscale image
int16_t* pca_grayscale(uint8_t* gray_image, int width, int height) {
    int num_pixels = width * height;
    int16_t* pca_result = new int16_t[num_pixels];
    
    // Calculate mean
    long sum = 0;
    for (int i = 0; i < num_pixels; i++) {
        sum += gray_image[i];
    }
    float mean = (float)sum / num_pixels;
    
    // Center the data
    for (int i = 0; i < num_pixels; i++) {
        pca_result[i] = gray_image[i] - (int16_t)mean;
    }
    
    // Note: This is still a simplified PCA implementation.
    // A full PCA would require computing eigenvectors, etc.
    
    return pca_result;
}

// Function to perform Otsu's thresholding
uint8_t* otsu_threshold(uint8_t* gray_image, int width, int height) {
    uint8_t* binary_image = new uint8_t[width * height];
    
    // Calculate histogram
    int histogram[256] = {0};
    for (int i = 0; i < width * height; i++) {
        histogram[gray_image[i]]++;
    }
    
    // Calculate total number of pixels
    int total = width * height;
    
    float sum = 0;
    for (int i = 0; i < 256; i++) {
        sum += i * histogram[i];
    }
    
    float sumB = 0;
    int wB = 0;
    int wF = 0;
    
    float varMax = 0;
    int threshold = 0;
    
    for (int i = 0; i < 256; i++) {
        wB += histogram[i];
        if (wB == 0) continue;
        wF = total - wB;
        if (wF == 0) break;
        
        sumB += i * histogram[i];
        float mB = sumB / wB;
        float mF = (sum - sumB) / wF;
        
        float varBetween = wB * wF * (mB - mF) * (mB - mF);
        
        if (varBetween > varMax) {
            varMax = varBetween;
            threshold = i;
        }
    }
    
    // Apply threshold
    for (int i = 0; i < width * height; i++) {
        binary_image[i] = (gray_image[i] > threshold) ? 255 : 0;
    }
    
    return binary_image;
}

// Updated pca_grayscale handler
esp_err_t pca_grayscale_handler() {
    Serial.printf("Initial Free heap: %d, Free PSRAM: %d\n", ESP.getFreeHeap(), ESP.getFreePsram());

    esp_err_t err = init_camera(FRAMESIZE_SXGA, PIXFORMAT_YUV422);
    if (err != ESP_OK) {
        Serial.printf("Camera init failed with error 0x%x\n", err);
        return err;
    }

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

    int width = fb->width;
    int height = fb->height;
    int num_pixels = width * height;

    Serial.printf("Image size: %dx%d (%d pixels)\n", width, height, num_pixels);

    // Calculate mean in chunks
    const int chunk_size = 10240; // Process 10K pixels at a time
    long long sum = 0;
    for (int i = 0; i < num_pixels; i += chunk_size) {
        int chunk_end = min(i + chunk_size, num_pixels);
        for (int j = i; j < chunk_end; j++) {
            sum += fb->buf[j * 2]; // Y component
        }
        yield();
    }
    float mean = (float)sum / num_pixels;

    // Find min and max values in chunks
    int16_t min_val = 32767, max_val = -32768;
    for (int i = 0; i < num_pixels; i += chunk_size) {
        int chunk_end = min(i + chunk_size, num_pixels);
        for (int j = i; j < chunk_end; j++) {
            int16_t centered = fb->buf[j * 2] - (int16_t)mean;
            if (centered < min_val) min_val = centered;
            if (centered > max_val) max_val = centered;
        }
        yield();
    }

    // Prepare for BMP creation
    int row_padding = (4 - ((width * 3) % 4)) % 4;
    int row_size = width * 3 + row_padding;
    int image_size = row_size * height;
    int file_size = 54 + image_size;

    // Send BMP headers
    uint8_t bmp_file_header[14] = {
        'B', 'M',
        (uint8_t)(file_size), (uint8_t)(file_size >> 8), (uint8_t)(file_size >> 16), (uint8_t)(file_size >> 24),
        0, 0, 0, 0,
        54, 0, 0, 0
    };

    uint8_t bmp_info_header[40] = {
        40, 0, 0, 0,
        (uint8_t)(width), (uint8_t)(width >> 8), (uint8_t)(width >> 16), (uint8_t)(width >> 24),
        (uint8_t)(height), (uint8_t)(height >> 8), (uint8_t)(height >> 16), (uint8_t)(height >> 24),
        1, 0,
        24, 0,
        0, 0, 0, 0,
        (uint8_t)(image_size), (uint8_t)(image_size >> 8), (uint8_t)(image_size >> 16), (uint8_t)(image_size >> 24),
        0x13, 0x0B, 0, 0,
        0x13, 0x0B, 0, 0,
        0, 0, 0, 0,
        0, 0, 0, 0
    };

    server.sendHeader("Content-Type", "image/bmp");
    server.sendHeader("Content-Disposition", "inline; filename=pca_grayscale.bmp");
    server.setContentLength(file_size);
    
    WiFiClient client = server.client();
    client.write(bmp_file_header, 14);
    client.write(bmp_info_header, 40);

    // Process and send image data in chunks
    float range = max_val - min_val;
    if (range == 0) range = 1;
    uint8_t *row_buffer = (uint8_t *)malloc(row_size);
    if (!row_buffer) {
        Serial.println("Failed to allocate row buffer");
        esp_camera_fb_return(fb);
        return ESP_FAIL;
    }

    for (int y = height - 1; y >= 0; y--) {
        for (int x = 0; x < width; x++) {
            int16_t centered = fb->buf[(y * width + x) * 2] - (int16_t)mean;
            uint8_t normalized = (uint8_t)(((centered - min_val) / range) * 255);
            row_buffer[x * 3] = normalized;     // Blue
            row_buffer[x * 3 + 1] = normalized; // Green
            row_buffer[x * 3 + 2] = normalized; // Red
        }
        // Add padding
        for (int p = 0; p < row_padding; p++) {
            row_buffer[width * 3 + p] = 0;
        }
        client.write(row_buffer, row_size);
        yield();
    }

    free(row_buffer);
    esp_camera_fb_return(fb);

    // Reset camera to JPEG mode for streaming
    init_camera(FRAME_SIZE_STREAM, PIXFORMAT_JPEG);

    Serial.printf("Final Free heap: %d, Free PSRAM: %d\n", ESP.getFreeHeap(), ESP.getFreePsram());
    return ESP_OK;
}

// Updated binary handler
esp_err_t binary_handler() {
    Serial.printf("Initial Free heap: %d, Free PSRAM: %d\n", ESP.getFreeHeap(), ESP.getFreePsram());

    esp_err_t err = init_camera(FRAMESIZE_SXGA, PIXFORMAT_YUV422);
    if (err != ESP_OK) {
        Serial.printf("Camera init failed with error 0x%x\n", err);
        return err;
    }

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

    int width = fb->width;
    int height = fb->height;
    int num_pixels = width * height;

    Serial.printf("Image size: %dx%d (%d pixels)\n", width, height, num_pixels);

    // Calculate histogram in chunks
    const int chunk_size = 10240; // Process 10K pixels at a time
    int histogram[256] = {0};
    for (int i = 0; i < num_pixels; i += chunk_size) {
        int chunk_end = min(i + chunk_size, num_pixels);
        for (int j = i; j < chunk_end; j++) {
            histogram[fb->buf[j * 2]]++; // Y component
        }
        yield();
    }

    // Calculate Otsu's threshold
    int total = num_pixels;
    long long sum = 0;
    for (int i = 0; i < 256; i++) {
        sum += i * (long long)histogram[i];
    }

    int wB = 0;
    long long sumB = 0;
    int wF = total;
    float maxVariance = 0;
    int threshold = 0;

    for (int t = 0; t < 256; t++) {
        wB += histogram[t];
        if (wB == 0) continue;
        wF = total - wB;
        if (wF == 0) break;

        sumB += (long long)t * histogram[t];
        float mB = (float)sumB / wB;
        float mF = (float)(sum - sumB) / wF;

        float variance = (float)wB * wF * (mB - mF) * (mB - mF);
        if (variance > maxVariance) {
            maxVariance = variance;
            threshold = t;
        }
    }

    Serial.printf("Otsu's threshold: %d\n", threshold);

    // Prepare for BMP creation
    int row_padding = (4 - ((width * 3) % 4)) % 4;
    int row_size = width * 3 + row_padding;
    int image_size = row_size * height;
    int file_size = 54 + image_size;

    // Send BMP headers
    uint8_t bmp_file_header[14] = {
        'B', 'M',
        (uint8_t)(file_size), (uint8_t)(file_size >> 8), (uint8_t)(file_size >> 16), (uint8_t)(file_size >> 24),
        0, 0, 0, 0,
        54, 0, 0, 0
    };

    uint8_t bmp_info_header[40] = {
        40, 0, 0, 0,
        (uint8_t)(width), (uint8_t)(width >> 8), (uint8_t)(width >> 16), (uint8_t)(width >> 24),
        (uint8_t)(height), (uint8_t)(height >> 8), (uint8_t)(height >> 16), (uint8_t)(height >> 24),
        1, 0,
        24, 0,
        0, 0, 0, 0,
        (uint8_t)(image_size), (uint8_t)(image_size >> 8), (uint8_t)(image_size >> 16), (uint8_t)(image_size >> 24),
        0x13, 0x0B, 0, 0,
        0x13, 0x0B, 0, 0,
        0, 0, 0, 0,
        0, 0, 0, 0
    };

    server.sendHeader("Content-Type", "image/bmp");
    server.sendHeader("Content-Disposition", "inline; filename=binary_image.bmp");
    server.setContentLength(file_size);
    
    WiFiClient client = server.client();
    client.write(bmp_file_header, 14);
    client.write(bmp_info_header, 40);

    // Process and send image data in chunks
    uint8_t *row_buffer = (uint8_t *)malloc(row_size);
    if (!row_buffer) {
        Serial.println("Failed to allocate row buffer");
        esp_camera_fb_return(fb);
        return ESP_FAIL;
    }

    for (int y = height - 1; y >= 0; y--) {
        for (int x = 0; x < width; x++) {
            uint8_t pixel = (fb->buf[(y * width + x) * 2] > threshold) ? 255 : 0;
            row_buffer[x * 3] = pixel;     // Blue
            row_buffer[x * 3 + 1] = pixel; // Green
            row_buffer[x * 3 + 2] = pixel; // Red
        }
        // Add padding
        for (int p = 0; p < row_padding; p++) {
            row_buffer[width * 3 + p] = 0;
        }
        client.write(row_buffer, row_size);
        yield();
    }

    free(row_buffer);
    esp_camera_fb_return(fb);

    // Reset camera to JPEG mode for streaming
    init_camera(FRAME_SIZE_STREAM, PIXFORMAT_JPEG);

    Serial.printf("Final Free heap: %d, Free PSRAM: %d\n", ESP.getFreeHeap(), ESP.getFreePsram());
    return ESP_OK;
}

// Start the web server
void startCameraServer() {
    server.on("/", HTTP_GET, []() {
        Serial.println("Root route accessed");
        handleRoot();
    });
    server.on("/stream", HTTP_GET, streamVideo);
    server.on("/capture", HTTP_GET, handleCapture);
    server.on("/pca_grayscale", HTTP_GET, pca_grayscale_handler);
    server.on("/binary", HTTP_GET, binary_handler);
    
    server.begin();
    Serial.println("HTTP server started");
}

// Setup function
void setup() {
    Serial.begin(115200);
    Serial.println("Starting setup...");

    // Setup WiFi AP mode
    WiFi.softAP(ap_ssid, ap_password);

    IPAddress IP = WiFi.softAPIP();
    Serial.print("Access Point IP: ");
    Serial.println(IP);

    #ifdef RGB_BUILTIN
      digitalWrite(RGB_BUILTIN, HIGH);   // Turn the RGB LED white
      delay(1000);
      digitalWrite(RGB_BUILTIN, LOW);    // Turn the RGB LED off
      delay(1000);
 
      neopixelWrite(RGB_BUILTIN,RGB_BRIGHTNESS,0,0); // Red
      delay(1000);
      neopixelWrite(RGB_BUILTIN,0,RGB_BRIGHTNESS,0); // Green
      delay(1000);
      neopixelWrite(RGB_BUILTIN,0,0,RGB_BRIGHTNESS); // Blue
      delay(1000);
      neopixelWrite(RGB_BUILTIN,0,0,0); // Off / black
      delay(1000);
    #endif

    // Initialize camera with low resolution for streaming
    Serial.println("Initializing camera...");
    esp_err_t err = init_camera(FRAMESIZE_SVGA, PIXFORMAT_JPEG);
    if (err != ESP_OK) {
        Serial.printf("Camera init failed with error 0x%x\n", err);
        return;
    }
    Serial.println("Camera initialized successfully");

    Serial.println("Starting camera server...");
    startCameraServer();
    Serial.println("Camera server started");

    Serial.println("Setup complete");
}

// Main loop
void loop() {
    server.handleClient();
}

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