Esp32 cam counting object with camera not found

I just made a program to count fish in a bucket. The problem is the camera can't count the number of fish and it's always 0. Where is the error?
Board ESP32s3 Camera Eye

#include <Arduino.h>
#include "esp_camera.h"
#include <cmath>
#include <WiFi.h>
#include "esp_http_server.h"
#include <esp_system.h>
#include <esp_err.h>

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

#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"

// Web server
httpd_handle_t server = NULL;
httpd_handle_t stream_httpd = NULL;

// Number of detected objects
int objectCount = 0;

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

// Improved object detection parameters
#define OBJECT_THRESHOLD 30  // Nilai threshold lebih rendah untuk objek dalam air
#define MIN_OBJECT_SIZE 50   // Ukuran minimal objek lebih kecil
#define MAX_OBJECTS 20       // Meningkatkan jumlah maksimum objek yang dapat dideteksi

#define FRAME_SIZE_STREAM FRAMESIZE_SVGA
#define FRAME_SIZE_CAPTURE FRAMESIZE_UXGA

// Function prototypes
void startCameraServer();

// Fungsi untuk inisialisasi kamera dengan resolusi dan format yang fleksibel
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

    // Deinit kamera jika sebelumnya sudah diinisialisasi
    esp_camera_deinit();

    // Init ulang kamera dengan konfigurasi yang baru
    return esp_camera_init(&config);
}

// HTTP handler untuk streaming video
esp_err_t stream_handler(httpd_req_t *req) {
    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];

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

    res = httpd_resp_set_type(req, _STREAM_CONTENT_TYPE);
    if(res != ESP_OK){
        return res;
    }

    while(true) {
        fb = esp_camera_fb_get();
        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){
            size_t hlen = snprintf((char *)part_buf, 64, _STREAM_PART, _jpg_buf_len);
            res = httpd_resp_send_chunk(req, (const char *)part_buf, hlen);
        }
        if(res == ESP_OK){
            res = httpd_resp_send_chunk(req, (const char *)_jpg_buf, _jpg_buf_len);
        }
        if(res == ESP_OK){
            res = httpd_resp_send_chunk(req, _STREAM_BOUNDARY, strlen(_STREAM_BOUNDARY));
        }
        if(fb){
            esp_camera_fb_return(fb);
            fb = NULL;
            _jpg_buf = NULL;
        } else if(_jpg_buf){
            free(_jpg_buf);
            _jpg_buf = NULL;
        }
        if(res != ESP_OK){
            break;
        }
        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
        );
    }

    last_frame = 0;
    return res;
}

// Gaussian blur kernel
int gaussianKernel[3][3] = {
    {1, 2, 1},
    {2, 4, 2},
    {1, 2, 1}
};

// Function to apply Gaussian Blur
void applyGaussianBlur(uint8_t* buf, int width, int height) {
    uint8_t tempBuf[width * height * 3]; // Temporary buffer for blurring

    for (int i = 1; i < height - 1; i++) {
        for (int j = 1; j < width - 1; j++) {
            int sum = 0;
            for (int m = -1; m <= 1; m++) {
                for (int n = -1; n <= 1; n++) {
                    int neighborIndex = ((i + m) * width + (j + n)) * 3;
                    sum += buf[neighborIndex] * gaussianKernel[m + 1][n + 1];
                }
            }
            int pixelIndex = (i * width + j) * 3;
            tempBuf[pixelIndex] = sum / 16;  // Gaussian blur normalization
        }
    }

    // Copy blurred data back to original buffer
    memcpy(buf, tempBuf, width * height * 3);
}

// Function to apply histogram equalization
void applyHistogramEqualization(uint8_t* buf, int len) {
    int histogram[256] = {0};
    int lut[256] = {0};
    int totalPixels = len / 3;

    // Compute histogram
    for (int i = 0; i < len; i += 3) {
        int intensity = buf[i]; // Grayscale intensity
        histogram[intensity]++;
    }

    // Compute cumulative distribution function (CDF)
    lut[0] = histogram[0];
    for (int i = 1; i < 256; i++) {
        lut[i] = lut[i - 1] + histogram[i];
    }

    // Normalize CDF
    for (int i = 0; i < 256; i++) {
        lut[i] = (lut[i] * 255) / totalPixels;
    }

    // Apply equalization
    for (int i = 0; i < len; i += 3) {
        int intensity = buf[i];
        buf[i] = buf[i + 1] = buf[i + 2] = lut[intensity];
    }
}

// Convert image to grayscale
void convertToGrayscale(uint8_t* buf, int len) {
    for (int i = 0; i < len; i += 3) {
        uint8_t r = buf[i];
        uint8_t g = buf[i + 1];
        uint8_t b = buf[i + 2];
        uint8_t gray = (r + g + b) / 3; // Simple average grayscale conversion
        buf[i] = buf[i + 1] = buf[i + 2] = gray;
    }
}

// Function to convert YUV422 to RGB
RGB yuv422_to_rgb(uint8_t y, uint8_t u, uint8_t v) {
    int r = y + 1.402f * (v - 128);
    int g = y - 0.344f * (u - 128) - 0.714f * (v - 128);
    int b = y + 1.772f * (u - 128);

    r = r > 255 ? 255 : (r < 0 ? 0 : r);
    g = g > 255 ? 255 : (g < 0 ? 0 : g);
    b = b > 255 ? 255 : (b < 0 ? 0 : b);

    return {static_cast<uint8_t>(r), static_cast<uint8_t>(g), static_cast<uint8_t>(b)};
}

// Function to perform PCA on RGB data
uint8_t* pca_grayscale(camera_fb_t* fb) {
    size_t pixel_count = fb->width * fb->height;
    uint8_t* grayscale = (uint8_t*)malloc(pixel_count);
    if (!grayscale) return nullptr;

    // Calculate mean of each channel
    float mean_r = 0, mean_g = 0, mean_b = 0;
    for (size_t i = 0; i < pixel_count * 2; i += 4) {
        RGB rgb = yuv422_to_rgb(fb->buf[i], fb->buf[i+1], fb->buf[i+3]);
        mean_r += rgb.r;
        mean_g += rgb.g;
        mean_b += rgb.b;
    }
    mean_r /= pixel_count;
    mean_g /= pixel_count;
    mean_b /= pixel_count;

    // Calculate covariance matrix
    float cov[3][3] = {{0}};
    for (size_t i = 0; i < pixel_count * 2; i += 4) {
        RGB rgb = yuv422_to_rgb(fb->buf[i], fb->buf[i+1], fb->buf[i+3]);
        float r = rgb.r - mean_r;
        float g = rgb.g - mean_g;
        float b = rgb.b - mean_b;

        cov[0][0] += r * r; cov[0][1] += r * g; cov[0][2] += r * b;
        cov[1][1] += g * g; cov[1][2] += g * b;
        cov[2][2] += b * b;
    }
    cov[1][0] = cov[0][1]; cov[2][0] = cov[0][2]; cov[2][1] = cov[1][2];
    for (int i = 0; i < 3; i++) {
        for (int j = 0; j < 3; j++) {
            cov[i][j] /= pixel_count - 1;
        }
    }

    // Find eigenvector with largest eigenvalue (power iteration method)
    float vec[3] = {1, 1, 1};
    for (int iter = 0; iter < 10; iter++) {
        float new_vec[3] = {0};
        for (int i = 0; i < 3; i++) {
            for (int j = 0; j < 3; j++) {
                new_vec[i] += cov[i][j] * vec[j];
            }
        }
        float norm = sqrt(new_vec[0]*new_vec[0] + new_vec[1]*new_vec[1] + new_vec[2]*new_vec[2]);
        for (int i = 0; i < 3; i++) vec[i] = new_vec[i] / norm;
    }

    // Project colors onto principal component
    for (size_t i = 0, j = 0; i < pixel_count * 2; i += 4, j++) {
        RGB rgb = yuv422_to_rgb(fb->buf[i], fb->buf[i+1], fb->buf[i+3]);
        float val = rgb.r * vec[0] + rgb.g * vec[1] + rgb.b * vec[2];
        grayscale[j] = static_cast<uint8_t>(fmin(fmax(val, 0), 255));
    }

    return grayscale;
}

// Function to perform Otsu thresholding
uint8_t* otsu_threshold(uint8_t* grayscale, size_t pixel_count) {
    uint8_t* thresholded = (uint8_t*)malloc(pixel_count);
    if (!thresholded) return nullptr;

    // Calculate histogram
    int histogram[256] = {0};
    for (size_t i = 0; i < pixel_count; i++) {
        histogram[grayscale[i]]++;
    }

    // Calculate total mean level
    float total_mean = 0;
    for (int i = 0; i < 256; i++) {
        total_mean += i * histogram[i];
    }
    total_mean /= pixel_count;

    // Find threshold that maximizes between-class variance
    int best_threshold = 0;
    float max_variance = 0;
    int first_class_pixel_count = 0;
    float first_class_mean = 0;

    for (int threshold = 0; threshold < 256; threshold++) {
        first_class_pixel_count += histogram[threshold];
        first_class_mean += threshold * histogram[threshold];

        if (first_class_pixel_count == 0 || first_class_pixel_count == pixel_count) continue;

        float first_class_prob = first_class_pixel_count / (float)pixel_count;
        float second_class_prob = 1.0f - first_class_prob;

        float first_class_mean_level = first_class_mean / first_class_pixel_count;
        float second_class_mean_level = (total_mean - first_class_mean) / (pixel_count - first_class_pixel_count);

        float mean_diff = first_class_mean_level - second_class_mean_level;
        float variance = first_class_prob * second_class_prob * mean_diff * mean_diff;

        if (variance > max_variance) {
            max_variance = variance;
            best_threshold = threshold;
        }
    }

    // Apply threshold
    for (size_t i = 0; i < pixel_count; i++) {
        thresholded[i] = grayscale[i] > best_threshold ? 255 : 0;
    }

    return thresholded;
}

// HTTP handler for PCA grayscale conversion and Otsu thresholding
esp_err_t pca_grayscale_handler(httpd_req_t *req) {
    camera_fb_t * fb = NULL;
    esp_err_t res = ESP_OK;
    size_t fb_len = 0;
    int64_t fr_start = esp_timer_get_time();

    // Ubah resolusi ke FRAMESIZE_UXGA (1600x1200) sebelum capture
    esp_err_t err = init_camera(FRAMESIZE_UXGA, PIXFORMAT_JPEG);
    if (err != ESP_OK) {
        Serial.printf("Failed to set high resolution: 0x%x\n", err);
        return ESP_FAIL;
    }

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

    fb_len = fb->len;

    size_t out_len = 0;
    uint8_t * out_buf = NULL;
    bool s;

    if(!frame2bmp(fb, &out_buf, &out_len)){
        Serial.println("BMP conversion failed");
        esp_camera_fb_return(fb);
        return ESP_FAIL;
    }
    esp_camera_fb_return(fb);

    // Extract width and height from BMP header
    size_t out_width = out_buf[18] + (out_buf[19] << 8);
    size_t out_height = out_buf[22] + (out_buf[23] << 8);

    // Perform PCA-based grayscale conversion
    uint8_t* pca_gray = pca_grayscale(fb);
    if (pca_gray) {
        // Copy PCA result to BMP buffer (starting from pixel data offset, typically 54 bytes)
        memcpy(out_buf + 54, pca_gray, out_width * out_height);
        free(pca_gray);
    } else {
        // If PCA fails, fall back to simple grayscale conversion
        for (size_t i = 54; i < out_len; i += 3) {
            uint8_t gray = (out_buf[i] + out_buf[i+1] + out_buf[i+2]) / 3;
            out_buf[i] = out_buf[i+1] = out_buf[i+2] = gray;
        }
    }

    httpd_resp_set_type(req, "image/bmp");
    httpd_resp_set_hdr(req, "Content-Disposition", "inline; filename=pca_grayscale.bmp");
    s = httpd_resp_send(req, (const char *)out_buf, out_len);
    free(out_buf);

    // Kembali ke resolusi rendah setelah capture
    err = init_camera(FRAME_SIZE_STREAM, PIXFORMAT_JPEG);
    if (err != ESP_OK) {
        Serial.printf("Failed to set low resolution: 0x%x\n", err);
    }

    int64_t fr_end = esp_timer_get_time();
    Serial.printf("BMP: %uB %ums\n", (uint32_t)(fb_len), (uint32_t)((fr_end - fr_start)/1000));

    return s ? ESP_OK : ESP_FAIL;
}

// Auto exposure control (adjust brightness)
void autoAdjustExposure(camera_fb_t * fb) {
    int brightness = 0;
    int pixelCount = 0;

    // Compute average brightness
    for (int i = 0; i < fb->len; i += 3) {
        brightness += fb->buf[i]; // Grayscale value
        pixelCount++;
    }

    brightness /= pixelCount;

    // Adjust brightness (exposure)
    if (brightness < 100) {
        sensor_t * s = esp_camera_sensor_get();
        if (s->set_aec_value) {
            s->set_aec_value(s, s->status.aec_value + 10); // Increase exposure
        }
        if (s != NULL) {
            s->set_exposure_ctrl(s, 1);  // Enable auto exposure control
            s->set_aec2(s, 1);  // Set second AEC (auto exposure control)
        }
    } else if (brightness > 150) {
        sensor_t * s = esp_camera_sensor_get();
        if (s->set_aec_value) {
            s->set_aec_value(s, s->status.aec_value - 10); // Decrease exposure
        }
        if (s != NULL) {
            s->set_exposure_ctrl(s, 1);  // Enable auto exposure control
            s->set_aec2(s, 1);  // Set second AEC (auto exposure control)
        }
    }
}

// Fungsi untuk menerapkan filter median
void applyMedianFilter(uint8_t* buf, int width, int height) {
    uint8_t window[9];
    uint8_t* tempBuf = (uint8_t*)malloc(width * height);
    if (!tempBuf) return;

    for (int y = 1; y < height - 1; y++) {
        for (int x = 1; x < width - 1; x++) {
            int k = 0;
            for (int j = -1; j <= 1; j++) {
                for (int i = -1; i <= 1; i++) {
                    window[k++] = buf[(y+j)*width + (x+i)];
                }
            }
            // Sort window
            for (int i = 0; i < 9; i++) {
                for (int j = i + 1; j < 9; j++) {
                    if (window[i] > window[j]) {
                        uint8_t temp = window[i];
                        window[i] = window[j];
                        window[j] = temp;
                    }
                }
            }
            tempBuf[y*width + x] = window[4]; // Median value
        }
    }
    memcpy(buf, tempBuf, width * height);
    free(tempBuf);
}

// Fungsi untuk meningkatkan kontras
void enhanceContrast(uint8_t* buf, int len) {
    int min = 255, max = 0;
    for (int i = 0; i < len; i++) {
        if (buf[i] < min) min = buf[i];
        if (buf[i] > max) max = buf[i];
    }
    for (int i = 0; i < len; i++) {
        buf[i] = ((buf[i] - min) * 255) / (max - min);
    }
}

// Fungsi untuk menghitung threshold adaptif
int computeAdaptiveThreshold(uint8_t* buf, int width, int height) {
    int sum = 0, count = 0;
    for (int y = 0; y < height; y += 10) {  // Sample every 10th row
        for (int x = 0; x < width; x += 10) {  // Sample every 10th column
            sum += buf[y * width + x];
            count++;
        }
    }
    return (sum / count) - 10;  // Slightly lower than average for better detection
}

// Improved object counting algorithm
int countObjectsInFrame(camera_fb_t* fb) {
    if (!fb || !fb->buf) return 0;

    int width = fb->width;
    int height = fb->height;
    uint8_t* buf = fb->buf;

    // Ensure the frame buffer is large enough
    if (fb->len < width * height * 3) {
        Serial.println("Frame buffer is too small");
        return 0;
    }

    // Convert YUV422 to RGB
    uint8_t* rgb_buf = (uint8_t*)malloc(width * height * 3);
    if (!rgb_buf) {
        Serial.println("Failed to allocate memory for RGB conversion");
        return 0;
    }
    for (int i = 0, j = 0; i < width * height * 2; i += 4, j += 6) {
        RGB rgb = yuv422_to_rgb(fb->buf[i], fb->buf[i+1], fb->buf[i+3]);
        rgb_buf[j] = rgb.r;
        rgb_buf[j+1] = rgb.g;
        rgb_buf[j+2] = rgb.b;
        rgb_buf[j+3] = rgb.r;
        rgb_buf[j+4] = rgb.g;
        rgb_buf[j+5] = rgb.b;
    }

    // Convert to grayscale
    uint8_t* gray_buf = (uint8_t*)malloc(width * height);
    if (!gray_buf) {
        Serial.println("Failed to allocate memory for grayscale conversion");
        free(rgb_buf);
        return 0;
    }
    convertToGrayscale(rgb_buf, width * height * 3);
    for (int i = 0, j = 0; i < width * height; i++, j += 3) {
        gray_buf[i] = rgb_buf[j];
    }
    free(rgb_buf);

    // Apply image processing steps
    applyGaussianBlur(gray_buf, width, height);
    applyHistogramEqualization(gray_buf, width * height);
    autoAdjustExposure(fb);  // Note: This function might need modification to work with grayscale
    applyMedianFilter(gray_buf, width, height);
    enhanceContrast(gray_buf, width * height);
    applyWaterDistortionFilter(gray_buf, width, height);
    reduceSurfaceReflections(gray_buf, width, height);

    // Perform PCA-based grayscale conversion
    uint8_t* pca_gray = pca_grayscale(fb);
    if (pca_gray) {
        free(gray_buf);
        gray_buf = pca_gray;
    }

    // Apply Otsu thresholding
    uint8_t* thresholded = otsu_threshold(gray_buf, width * height);
    if (thresholded) {
        free(gray_buf);
        gray_buf = thresholded;
    } else {
        // If Otsu thresholding fails, use adaptive thresholding
        int threshold = computeAdaptiveThreshold(gray_buf, width, height);
        for (int i = 0; i < width * height; i++) {
            gray_buf[i] = (gray_buf[i] > threshold) ? 255 : 0;
        }
    }

    // Perform color segmentation (Note: This step might not be very useful after thresholding)
    int* labels = (int*)calloc(width * height, sizeof(int));
    if (labels) {
        colorSegmentation(fb->buf, width, height, labels);
        free(labels);
    }

    // Count objects using connected component labeling
    int objectCount = 0;
    int label = 1;
    int* ccl_labels = (int*)calloc(width * height, sizeof(int));
    if (!ccl_labels) {
        Serial.println("Failed to allocate memory for labels");
        free(gray_buf);
        return 0;
    }

    for (int y = 0; y < height && label <= MAX_OBJECTS; y++) {
        for (int x = 0; x < width && label <= MAX_OBJECTS; x++) {
            if (gray_buf[y * width + x] == 255 && ccl_labels[y * width + x] == 0) {
                int pixelCount = floodFill(gray_buf, ccl_labels, width, height, x, y, label);
                if (pixelCount >= MIN_OBJECT_SIZE) {
                    objectCount++;
                }
                label++;
            }
        }
    }

    free(ccl_labels);
    free(gray_buf);
    return objectCount;
}

// Function to apply water distortion correction
void applyWaterDistortionFilter(uint8_t* buf, int width, int height) {
    // Create a temporary buffer for the filtered image
    uint8_t* tempBuf = (uint8_t*)malloc(width * height);
    if (!tempBuf) return;

    // Parameters for the distortion correction
    float A = 0.01; // Amplitude of the distortion
    float T = 10.0; // Period of the distortion

    for (int y = 0; y < height; y++) {
        for (int x = 0; x < width; x++) {
            // Apply sinusoidal distortion
            float dx = A * sin(2 * M_PI * y / T);
            float dy = A * sin(2 * M_PI * x / T);

            // Calculate the source pixel coordinates
            int sx = x + (int)(dx * width);
            int sy = y + (int)(dy * height);

            // Ensure the coordinates are within bounds
            sx = max(0, min(sx, width - 1));
            sy = max(0, min(sy, height - 1));

            // Copy the pixel from the source to the destination
            tempBuf[y * width + x] = buf[sy * width + sx];
        }
    }

    // Copy the filtered image back to the original buffer
    memcpy(buf, tempBuf, width * height);
    free(tempBuf);
}

// Function to reduce surface reflections
void reduceSurfaceReflections(uint8_t* buf, int width, int height) {
    const int kernelSize = 5;
    const float threshold = 0.8; // Adjust this value to control reflection detection sensitivity

    uint8_t* tempBuf = (uint8_t*)malloc(width * height);
    if (!tempBuf) return;

    for (int y = kernelSize/2; y < height - kernelSize/2; y++) {
        for (int x = kernelSize/2; x < width - kernelSize/2; x++) {
            float sum = 0;
            float maxVal = 0;
            
            // Calculate local average and find maximum value
            for (int ky = -kernelSize/2; ky <= kernelSize/2; ky++) {
                for (int kx = -kernelSize/2; kx <= kernelSize/2; kx++) {
                    int idx = (y+ky)*width + (x+kx);
                    sum += buf[idx];
                    maxVal = max(maxVal, (float)buf[idx]);
                }
            }
            
            float avg = sum / (kernelSize * kernelSize);
            
            // If the pixel is significantly brighter than its surroundings, reduce its intensity
            if (buf[y*width + x] > avg * threshold && buf[y*width + x] > maxVal * threshold) {
                tempBuf[y*width + x] = avg;
            } else {
                tempBuf[y*width + x] = buf[y*width + x];
            }
        }
    }

    memcpy(buf, tempBuf, width * height);
    free(tempBuf);
}

// Function to perform color segmentation
void colorSegmentation(uint8_t* buf, int width, int height, int* labels) {
    // Define color ranges for different objects (in HSV space)
    const int SHRIMP_HUE_MIN = 0, SHRIMP_HUE_MAX = 20;
    const int FISH_HUE_MIN = 80, FISH_HUE_MAX = 150;

    for (int y = 0; y < height; y++) {
        for (int x = 0; x < width; x++) {
            int idx = (y * width + x) * 3;
            uint8_t r = buf[idx], g = buf[idx+1], b = buf[idx+2];
            
            // Convert RGB to HSV
            float h, s, v;
            rgb2hsv(r, g, b, &h, &s, &v);
            
            // Classify pixels based on color
            if (h >= SHRIMP_HUE_MIN && h <= SHRIMP_HUE_MAX) {
                labels[y * width + x] = 1; // Shrimp
            } else if (h >= FISH_HUE_MIN && h <= FISH_HUE_MAX) {
                labels[y * width + x] = 2; // Fish
            } else {
                labels[y * width + x] = 0; // Background
            }
        }
    }
}

// Helper function to convert RGB to HSV
void rgb2hsv(uint8_t r, uint8_t g, uint8_t b, float* h, float* s, float* v) {
    float r_prime = r / 255.0f;
    float g_prime = g / 255.0f;
    float b_prime = b / 255.0f;
    
    float c_max = max(max(r_prime, g_prime), b_prime);
    float c_min = min(min(r_prime, g_prime), b_prime);
    float delta = c_max - c_min;
    
    // Calculate hue
    if (delta == 0) {
        *h = 0;
    } else if (c_max == r_prime) {
        *h = 60 * fmod(((g_prime - b_prime) / delta), 6);
    } else if (c_max == g_prime) {
        *h = 60 * (((b_prime - r_prime) / delta) + 2);
    } else {
        *h = 60 * (((r_prime - g_prime) / delta) + 4);
    }
    
    // Calculate saturation
    *s = (c_max == 0) ? 0 : (delta / c_max);
    
    // Calculate value
    *v = c_max;
}

// Helper function for flood fill
int floodFill(uint8_t* buf, int* labels, int width, int height, int x, int y, int label) {
    int pixelCount = 0;
    int stack[width * height][2], top = 0;
    stack[top][0] = x;
    stack[top][1] = y;
    top++;

    while (top > 0) {
        top--;
        int cx = stack[top][0];
        int cy = stack[top][1];

        if (cx < 0 || cx >= width || cy < 0 || cy >= height) continue;
        if (buf[cy * width + cx] != 255 || labels[cy * width + cx] != 0) continue;

        labels[cy * width + cx] = label;
        pixelCount++;

        if (top < width * height - 4) {
            stack[top][0] = cx + 1; stack[top][1] = cy; top++;
            stack[top][0] = cx - 1; stack[top][1] = cy; top++;
            stack[top][0] = cx; stack[top][1] = cy + 1; top++;
            stack[top][0] = cx; stack[top][1] = cy - 1; top++;
        }
    }

    return pixelCount;
}

// HTTP handler for object count using the countObjectsInFrame function
static esp_err_t count_handler(httpd_req_t *req) {
    // Capture the current frame from the camera
    camera_fb_t * fb = esp_camera_fb_get();
    if (!fb) {
        // If capture fails, return an error
        Serial.println("Failed to get camera frame buffer");
        httpd_resp_send_500(req);  // Return HTTP 500 Internal Server Error
        return ESP_FAIL;
    }

    // Use the object counting function to count objects in the frame
    int count = countObjectsInFrame(fb);

    // Return the frame buffer after processing
    esp_camera_fb_return(fb);

    // Prepare the response JSON
    char resp_str[100];
    snprintf(resp_str, sizeof(resp_str), "{\"objectCount\": %d}", count);

    // Set the response type as JSON and send the object count
    httpd_resp_set_type(req, "application/json");
    return httpd_resp_send(req, resp_str, strlen(resp_str));
}

// HTTP handler untuk menangkap gambar dengan resolusi tinggi
esp_err_t capture_handler(httpd_req_t *req) {
    camera_fb_t *fb = NULL;
    esp_err_t res = ESP_OK;
    size_t fb_len = 0;
    int64_t fr_start = esp_timer_get_time();

    // Set high resolution
    esp_err_t err = init_camera(FRAME_SIZE_CAPTURE, PIXFORMAT_JPEG);
    if (err != ESP_OK) {
        Serial.printf("Failed to set high resolution: 0x%x\n", err);
        httpd_resp_send_500(req);
        return ESP_FAIL;
    }

    vTaskDelay(500 / portTICK_PERIOD_MS);

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

    fb_len = fb->len;
    res = httpd_resp_set_type(req, "image/jpeg");
    if (res == ESP_OK) {
        res = httpd_resp_set_hdr(req, "Content-Disposition", "inline; filename=capture.jpg");
    }
    if (res == ESP_OK) {
        res = httpd_resp_send(req, (const char *)fb->buf, fb->len);
    }
    esp_camera_fb_return(fb);

    // Set low resolution
    err = init_camera(FRAME_SIZE_STREAM, PIXFORMAT_JPEG);
    if (err != ESP_OK) {
        Serial.printf("Failed to set low resolution: 0x%x\n", err);
    }

    int64_t fr_end = esp_timer_get_time();
    Serial.printf("JPG: %uB %ums\n", (uint32_t)(fb_len), (uint32_t)((fr_end - fr_start)/1000));

    if (res != ESP_OK) {
        Serial.printf("Error in capture_handler: 0x%x\n", res);
        httpd_resp_send_500(req);
    }

    return res;
}

// HTML for the web interface
static esp_err_t index_handler(httpd_req_t *req) {
  const char* resp_str = 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 Random Counter</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: 20px;
          border-radius: 10px;
          box-shadow: 0 0 10px rgba(0,0,0,0.1);
          width: 90%;
          max-width: 600px;
      }
  
      h1 {
          color: #333;
      }
  
      .bg-container {
          position: relative;
          width: 100%;
          max-width: 500px;
          height: auto;
          overflow: hidden;
          margin: 20px auto;
      }
  
      .camera-container {
          position: relative;
          width: 100%;
          max-width: 300px;
          aspect-ratio: 1 / 1; /* Ensures a perfect square, 1:1 ratio */
          overflow: hidden;
          margin: 20px auto;
          border-radius: 50%; /* Keeps it circular */
          border: 5px solid #333;
      }
  
      #cameraFeed {
          width: 100%;
          height: 100%;
          object-fit: cover;
          border-radius: 50%; /* Ensures image stays circular */
      }
  
      #randomCountDisplay {
          display: inline-block;
          background-color: rgba(0,0,0,0.5);
          border-radius: 20px;
          padding: 10px 20px;
          color: white;
          margin-top: 10px;
          margin-bottom: -30px;
          font-size: 24px;
          font-weight: bold;
      }
  
      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: 5px;
      }
  
      /* Media queries for responsiveness */
      @media (max-width: 768px) { /* Tablet */
          body {
              padding: 20px;
          }
  
          .container {
              width: 100%;
              max-width: 500px;
          }
  
          .camera-container {
              max-width: 250px;
          }
  
          #randomCountDisplay {
              font-size: 20px;
              padding: 8px 16px;
          }
  
          button {
              padding: 12px 24px;
              font-size: 14px;
          }
      }
  
      @media (max-width: 480px) { /* Smartphone */
          body {
              padding: 10px;
          }
  
          .container {
              width: 100%;
          }
  
          .camera-container {
              max-width: 200px;
          }
  
          #randomCountDisplay {
              font-size: 18px;
              padding: 6px 12px;
          }
  
          button {
              padding: 10px 20px;
              font-size: 14px;
          }
      }
  </style>
</head>
<body>
    <div class="container">
        <h1>ESP32 Random Counter</h1>
        <div id="randomCountDisplay">Random Count: <span id="randomCount">0</span></div>
        <div class="bg-container">
        <div class="camera-container">
            <img id="cameraFeed" src="/stream" alt="Camera Stream">
        </div>
        </div>
        <button onclick="saverandomCount()">Save Random Count</button>
    </div>
    <script>
        const url = "/randomCount";
        function updaterandomCount() {
            fetch(url)
                .then(response => response.json())
                .then(data => {
                    console.log("Received data from server:", data);  // Add this line
                    document.getElementById("randomCount").textContent = data.randomCount;
                    console.log("Updated DOM with new count:", data.randomCount);  // Add this line
                })
                .catch(error => console.error('Error fetching Random count:', error));
        }
        function saverandomCount() {
            const randomCountDisplay = document.getElementById('randomCountDisplay');
            const randomCount = document.getElementById('randomCount').textContent;
            const video = document.getElementById('cameraFeed');
            const canvas = document.createElement('canvas');
            canvas.width = 500; // Match the width of bg-container
            canvas.height = 500; // Make it square for simplicity
            const context = canvas.getContext('2d');
            
            // Draw white background
            context.fillStyle = 'white';
            context.fillRect(0, 0, canvas.width, canvas.height);
            
            // Draw circular camera feed
            context.save();
            context.beginPath();
            context.arc(250, 250, 150, 0, Math.PI * 2);
            context.clip();
            context.drawImage(video, 100, 100, 300, 300);
            context.restore();
            
            // Draw border
            context.strokeStyle = '#333';
            context.lineWidth = 5;
            context.beginPath();
            context.arc(250, 250, 150, 0, Math.PI * 2);
            context.stroke();
            
            // Draw Random count display with dynamic width
            context.font = 'bold 24px Arial';
            const text = "Random Count: " + randomCount;
            const textMetrics = context.measureText(text);
            const textWidth = textMetrics.width;
            const paddingX = 20;
            const rectWidth = textWidth + (paddingX * 2);
            const rectHeight = 40;
            const rectX = (canvas.width - rectWidth) / 2;
            const rectY = 50;

            context.fillStyle = 'rgba(0,0,0,0.5)';
            context.beginPath();
            context.roundRect(rectX, rectY, rectWidth, rectHeight, 20);
            context.fill();
            
            context.fillStyle = 'white';
            context.textAlign = 'center';
            context.fillText(text, canvas.width / 2, rectY + 28);
            
            canvas.toBlob(function(blob) {
                const link = document.createElement('a');
                link.href = URL.createObjectURL(blob);
                link.download = 'random_count.jpg';
                link.click();
            }, 'image/jpeg');
        }
        setInterval(updaterandomCount, 1000); // Update every 1 second
    </script>
</body>
</html>
  )rawliteral";
  httpd_resp_send(req, resp_str, strlen(resp_str));
  return ESP_OK;
}

// Fungsi untuk memulai web server kamera
void startCameraServer() {
    httpd_config_t config = HTTPD_DEFAULT_CONFIG();
    config.stack_size = 10240; // Increase stack size if needed
    config.max_uri_handlers = 8; // Increase if you have many endpoints
    config.max_resp_headers = 8; // Adjust as needed

    httpd_uri_t index_uri = {
        .uri       = "/",
        .method    = HTTP_GET,
        .handler   = index_handler,
        .user_ctx  = NULL
    };

    httpd_uri_t stream_uri = {
        .uri       = "/stream",
        .method    = HTTP_GET,
        .handler   = stream_handler,
        .user_ctx  = NULL
    };

    httpd_uri_t capture_uri = {
        .uri       = "/capture",
        .method    = HTTP_GET,
        .handler   = capture_handler,
        .user_ctx  = NULL
    };

    httpd_uri_t count_uri = {
        .uri       = "/count",
        .method    = HTTP_GET,
        .handler   = count_handler,
        .user_ctx  = NULL
    };

    httpd_uri_t pca_uri = {
        .uri       = "/capture_grayscale",
        .method    = HTTP_GET,
        .handler   = pca_grayscale_handler,
        .user_ctx  = NULL
    };

    if (httpd_start(&server, &config) == ESP_OK) {
        httpd_register_uri_handler(server, &index_uri);
        httpd_register_uri_handler(server, &stream_uri);
        httpd_register_uri_handler(server, &capture_uri);
        httpd_register_uri_handler(server, &count_uri);
        httpd_register_uri_handler(server, &pca_uri);
    }

    config.server_port += 1;
    config.ctrl_port += 1;
    if (httpd_start(&stream_httpd, &config) == ESP_OK) {
        httpd_register_uri_handler(stream_httpd, &stream_uri);
    }
}

void handle_error(esp_err_t error) {
    if (error != ESP_OK) {
        Serial.printf("Error occurred: %s\n", esp_err_to_name(error));
        // Tambahkan tindakan yang sesuai, seperti restart ESP32
        // esp_restart();
    }
}

// Fungsi setup utama
void setup() {
    Serial.begin(115200);
    
    // Setup WiFi dalam mode AP
    WiFi.softAP(ap_ssid, ap_password);

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

    // Inisialisasi kamera pertama kali dengan resolusi VGA
    esp_err_t err = init_camera(FRAME_SIZE_STREAM, PIXFORMAT_JPEG);
    if (err != ESP_OK) {
        Serial.printf("Camera init failed with error 0x%x", err);
        return;
    }

    startCameraServer();
}

void loop() {
    if (server == NULL || stream_httpd == NULL) {
        Serial.println("Error: HTTP server not started. Restarting...");
        esp_restart();
    }
    delay(10000); // Check every 10 seconds
}

For help on this forum, have a look at the posting suggestions in the "How to get the best out of this forum" post, linked at the head of every forum category.

The error is that in order to count fish in a bucket the program needs to know what a fish is. This is a very difficult problem and is not something you can easily do.

Can you post a picture of your fish in a bucket you are trying to count?

Can you post a picture of the fish that is the reference fish that you are using to identify a fist.

None of these is discernible from your code.

You very well might have to employ the open CV library to do this.

This is the photo, we can use shrimp or anything that needs to be sorted. Are there any other libraries besides opencv?

You sad fish. Shrimps are not fish. They are crustaceans.

In fact you are attempting to count dots. So why make the image burned before you did this?

I don't know how your count library is supposed to work, but I am not surprised it doesn't.

I would experiment with the threshold values and see if you can get those tails to disappear.

Having looked round the forum it seems that no one has ever got the ESP to count things. Including a few current posts. See the links underneath this post to see what people are reporting.

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