The counter with camera processes is always 0

I just made a program to count objects using a camera, the results appear on the serial monitor but why does it still say 0 on the web?
Cuplikan layar pada 2024-10-14 14-27-30
Cuplikan layar pada 2024-10-14 14-27-20

#include "esp_camera.h"
#include "WiFi.h"
#include "esp_timer.h"
#include "img_converters.h"
#include <algorithm>
#include <numeric>
#include "Arduino.h"
#include "soc/soc.h"
#include "soc/rtc_cntl_reg.h"
#include "esp_http_server.h"

// Select camera model
#define CAMERA_MODEL_ESP32S3_EYE // Has PSRAM
#include "camera_pins.h"

// Set up Access Point credentials
const char* ap_ssid = "ESP32-CAM-AP";
const char* ap_password = "12345678";

// Stream content type and boundary definitions
#define PART_BOUNDARY "123456789000000000000987654321"
static const char* _STREAM_CONTENT_TYPE = "multipart/x-mixed-replace;boundary=" PART_BOUNDARY;
static const char* _STREAM_BOUNDARY = "\r\n--" PART_BOUNDARY "\r\n";
static const char* _STREAM_PART = "Content-Type: image/jpeg\r\nContent-Length: %u\r\n\r\n";

void startCameraServer();
int countRandom(camera_fb_t *fb);
void addrandomCountOverlay(camera_fb_t *fb, int random_count);

httpd_handle_t stream_httpd = NULL;
httpd_handle_t server = NULL;

volatile int last_random_count = 0;

static 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 {
      last_random_count = countRandom(fb);  // Update last_random_count here
      Serial.printf("Random counted: %d\n", last_random_count);

      addrandomCountOverlay(fb, last_random_count);

      if (fb->format != PIXFORMAT_JPEG) {
        bool jpeg_converted = frame2jpg(fb, 15, &_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
    );
    delay(30);
  }

  last_frame = 0;
  return res;
}

// Function to perform PCA-based grayscale conversion
void pca_grayscale(uint8_t* rgb_image, uint8_t* gray_image, int width, int height) {
    // Calculate mean of each channel
    float mean_r = 0, mean_g = 0, mean_b = 0;
    for (int i = 0; i < width * height; i++) {
        mean_r += rgb_image[i * 3];
        mean_g += rgb_image[i * 3 + 1];
        mean_b += rgb_image[i * 3 + 2];
    }
    mean_r /= (width * height);
    mean_g /= (width * height);
    mean_b /= (width * height);

    // Calculate covariance matrix
    float cov[3][3] = {{0}};
    for (int i = 0; i < width * height; i++) {
        float r = rgb_image[i * 3] - mean_r;
        float g = rgb_image[i * 3 + 1] - mean_g;
        float b = rgb_image[i * 3 + 2] - 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] /= (width * height - 1);
        }
    }

    // Find eigenvector with largest eigenvalue (simplified approach)
    float eigen_vector[3] = {cov[0][0], cov[1][0], cov[2][0]};
    float mag = sqrt(eigen_vector[0] * eigen_vector[0] + 
                     eigen_vector[1] * eigen_vector[1] + 
                     eigen_vector[2] * eigen_vector[2]);
    eigen_vector[0] /= mag;
    eigen_vector[1] /= mag;
    eigen_vector[2] /= mag;

    // Project RGB values onto eigenvector
    for (int i = 0; i < width * height; i++) {
        float gray_value = eigen_vector[0] * rgb_image[i * 3] +
                           eigen_vector[1] * rgb_image[i * 3 + 1] +
                           eigen_vector[2] * rgb_image[i * 3 + 2];
        gray_image[i] = (uint8_t)std::min(255.0f, std::max(0.0f, gray_value));
    }
}

// Function to perform Otsu's thresholding
uint8_t otsu_threshold(uint8_t* gray_image, int width, int height) {
    int histogram[256] = {0};
    for (int i = 0; i < width * height; i++) {
        histogram[gray_image[i]]++;
    }

    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;
    uint8_t threshold = 0;

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

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

        float varBetween = wB * wF * (mB - mF) * (mB - mF);

        if (varBetween > varMax) {
            varMax = varBetween;
            threshold = t;
        }
    }

    return threshold;
}

// New global variables for calibration and object properties
int MIN_OBJECT_AREA = 100;  // Minimum area of an object in pixels
int MAX_OBJECT_AREA = 10000;  // Maximum area of an object in pixels
float PIXELS_PER_CM = 10;  // Calibration factor: pixels per cm

// Function prototypes
void setupLighting();
void calibratePixelsPerCm();
int countObjects(camera_fb_t *fb);
void applyImageFilters(uint8_t* image, size_t width, size_t height);
void detectEdges(uint8_t* image, size_t width, size_t height);

void setupLighting() {
  // Example: If you have a GPIO pin connected to LEDs
  const int LED_PIN = 2;  // Change this to your actual LED pin
  pinMode(LED_PIN, OUTPUT);
  digitalWrite(LED_PIN, HIGH);  // Turn on the LED
}

void calibratePixelsPerCm() {
  // This function should be called with a known object in the frame
  // For example, you could place a 10cm ruler in the frame
  // Then, detect the ruler and calculate pixels per cm
  // This is a placeholder implementation
  camera_fb_t * fb = esp_camera_fb_get();
  if (!fb) {
    Serial.println("Camera capture failed");
    return;
  }
  
  // Assume we've detected a 10cm object that's 100 pixels long
  PIXELS_PER_CM = 100 / 10.0;
  
  esp_camera_fb_return(fb);
  Serial.printf("Calibrated to %.2f pixels per cm\n", PIXELS_PER_CM);
}

void applyImageFilters(uint8_t* image, size_t width, size_t height) {
  // Apply a simple Gaussian blur to reduce noise
  for (int y = 1; y < height - 1; y++) {
    for (int x = 1; x < width - 1; x++) {
      int sum = 0;
      for (int dy = -1; dy <= 1; dy++) {
        for (int dx = -1; dx <= 1; dx++) {
          sum += image[(y + dy) * width + (x + dx)];
        }
      }
      image[y * width + x] = sum / 9;
    }
  }
}

void detectEdges(uint8_t* image, size_t width, size_t height) {
  // Simple Sobel edge detection
  uint8_t* temp = (uint8_t*)malloc(width * height);
  memcpy(temp, image, width * height);
  
  for (int y = 1; y < height - 1; y++) {
    for (int x = 1; x < width - 1; x++) {
      int gx = (-1 * temp[(y-1)*width + (x-1)]) + (-2 * temp[y*width + (x-1)]) + (-1 * temp[(y+1)*width + (x-1)]) +
                (1 * temp[(y-1)*width + (x+1)]) + (2 * temp[y*width + (x+1)]) + (1 * temp[(y+1)*width + (x+1)]);
      int gy = (-1 * temp[(y-1)*width + (x-1)]) + (-2 * temp[(y-1)*width + x]) + (-1 * temp[(y-1)*width + (x+1)]) +
                (1 * temp[(y+1)*width + (x-1)]) + (2 * temp[(y+1)*width + x]) + (1 * temp[(y+1)*width + (x+1)]);
      int mag = sqrt(gx*gx + gy*gy);
      image[y*width + x] = (mag > 100) ? 255 : 0;  // Threshold
    }
  }
  
  free(temp);
}

// Modified Random counting function
int countRandom(camera_fb_t *fb) {
    int width = fb->width;
    int height = fb->height;
    uint8_t *rgb_buffer = fb->buf;
    
    // Convert to grayscale using PCA (from original code)
    uint8_t *gray_buffer = (uint8_t*)malloc(width * height);
    pca_grayscale(rgb_buffer, gray_buffer, width, height);
    
    // Apply image filters
    applyImageFilters(gray_buffer, width, height);
    
    // Perform Otsu's thresholding (from original code)
    uint8_t threshold = otsu_threshold(gray_buffer, width, height);
    
    // Count Random (white pixels)
    int random_pixels = 0;
    for (int i = 0; i < width * height; i++) {
        if (gray_buffer[i] > threshold) {
            random_pixels++;
        }
    }
    
    // Free allocated memory
    free(gray_buffer);
    
    // Convert pixel count to Random count (adjust this based on expected Random size)
    int random_count = random_pixels / 1000;  // Assume each Random is roughly 1000 pixels
    
    return random_count;
}

int floodFill(uint8_t* image, uint8_t* labeled, int width, int height, int x, int y, uint8_t label) {
    if (x < 0 || x >= width || y < 0 || y >= height || image[y*width + x] != 255 || labeled[y*width + x] != 0) {
        return 0;
    }
    
    labeled[y*width + x] = label;
    int area = 1;
    
    area += floodFill(image, labeled, width, height, x+1, y, label);
    area += floodFill(image, labeled, width, height, x-1, y, label);
    area += floodFill(image, labeled, width, height, x, y+1, label);
    area += floodFill(image, labeled, width, height, x, y-1, label);
    
    return area;
}

// Function to add overlay text to the frame buffer
void addrandomCountOverlay(camera_fb_t *fb, int random_count) {
  char overlay_text[32];
  snprintf(overlay_text, sizeof(overlay_text), "Random Count: %d", random_count);

  // For now, just print to serial, as fb_gfx or similar method needs the right library.
  Serial.println(overlay_text);
}

// Return the Random count as JSON
static esp_err_t random_count_handler(httpd_req_t *req) {
  char json_response[64];
  snprintf(json_response, sizeof(json_response), "{\"randomCount\": %d}", last_random_count);
  httpd_resp_set_type(req, "application/json");
  httpd_resp_send(req, json_response, strlen(json_response));

  // Add this line for logging
  Serial.printf("Sending random count to client: %d\n", last_random_count);

  return ESP_OK;
}

// 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);
        }
        h1 {
            color: #333;
        }
        .bg-container {
            position: relative;
            width: 500px;
            height: auto;
            overflow: hidden;
            margin: 20px auto;
        }
        .camera-container {
            position: relative;
            width: 300px;
            height: 300px;
            overflow: hidden;
            margin: 20px auto;
            border-radius: 50%;
            border: 5px solid #333;
        }
        #cameraFeed {
            width: 100%;
            height: 100%;
            object-fit: cover;
            border-radius: 50%;
        }
        #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;
        }
    </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;
}

void startCameraServer() {
  httpd_config_t config = HTTPD_DEFAULT_CONFIG();
  config.server_port = 80;
  config.max_open_sockets = 5;
  config.backlog_conn = 10;
  config.lru_purge_enable = true;

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

  httpd_uri_t random_count_uri = {
    .uri       = "/randomCount",
    .method    = HTTP_GET,
    .handler   = random_count_handler,
    .user_ctx  = NULL
  };

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

  Serial.printf("Starting web server on port: '%d'\n", config.server_port);
  if (httpd_start(&server, &config) == ESP_OK) {
    httpd_register_uri_handler(server, &stream_uri);
    httpd_register_uri_handler(server, &random_count_uri);
    httpd_register_uri_handler(server, &index_uri);
  }
}

void setup() {
  Serial.begin(115200);
  Serial.setDebugOutput(true);
  Serial.println();

  Serial.println("Setting up camera...");
  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_sccb_sda = SIOD_GPIO_NUM;
  config.pin_sccb_scl = SIOC_GPIO_NUM;
  config.pin_pwdn = PWDN_GPIO_NUM;
  config.pin_reset = RESET_GPIO_NUM;
  config.xclk_freq_hz = 10000000;
  config.pixel_format = PIXFORMAT_JPEG;
  config.frame_size = FRAMESIZE_VGA;  // Meningkatkan resolusi
  config.jpeg_quality = 12;  // Mengurangi kompresi untuk kualitas lebih baik
  config.fb_count = 2;

  if (psramFound()) {
    config.jpeg_quality = 10;
    config.fb_count = 2;
  } else {
    config.frame_size = FRAMESIZE_SVGA;
    config.jpeg_quality = 12;
    config.fb_count = 1;
  }

  // Initialize the camera
  esp_err_t err = ESP_FAIL;
  int retry = 0;
  while (err != ESP_OK && retry < 5) {
    err = esp_camera_init(&config);
    if (err != ESP_OK) {
      Serial.printf("Camera init failed with error 0x%x", err);
      delay(500);
      retry++;
    }
  }
  if (err != ESP_OK) {
    Serial.println("Camera init failed after 5 attempts");
    return;
  }
  delay(100);

  setupLighting();
  calibratePixelsPerCm();

  // Adjust camera settings
  sensor_t * s = esp_camera_sensor_get();
  if (s) {
      s->set_brightness(s, 1);  // Increase brightness for better visibility in closed bucket
      s->set_contrast(s, 2);    // Increase contrast to make objects more distinguishable
      s->set_saturation(s, 1);  // Slight increase in saturation
      s->set_special_effect(s, 0); // No special effect
      s->set_whitebal(s, 1);    // Enable white balance
      s->set_awb_gain(s, 1);    // Enable auto white balance gain
      s->set_wb_mode(s, 0);     // Auto white balance mode
      s->set_exposure_ctrl(s, 1); // Enable auto exposure
      s->set_aec2(s, 1);        // Enable auto exposure (DSP)
      s->set_gain_ctrl(s, 1);   // Enable auto gain control
      s->set_bpc(s, 1);         // Enable black pixel correction
      s->set_wpc(s, 1);         // Enable white pixel correction
      s->set_raw_gma(s, 1);     // Enable gamma correction
      s->set_lenc(s, 1);        // Enable lens correction
      s->set_hmirror(s, 0);     // Disable horizontal mirror
      s->set_vflip(s, 0);       // Disable vertical flip
      s->set_dcw(s, 1);         // Enable downsize EN
  }

  // Start the Access Point
  WiFi.softAP(ap_ssid, ap_password);
  IPAddress IP = WiFi.softAPIP();
  Serial.print("AP IP address: ");
  Serial.println(IP);

  // Start the Camera Server
  startCameraServer();
}

void loop() {
  delay(30);
}

As your topic does not relate directly to the installation or operation of the IDE it has been moved to the Programming Questions category of the forum