Fastest way to send images from ESP32CAM to cloud server

Hi,

My idea is to use ESP32CAM for image processing projects. Since the ESP32 core is not powerful enough, I am planning to send the video (as images) from ESP32CAM into my python flask server and perform image processing over there using openCV.

I am aware that ESP32CAM is not powerful enough to stream video live into my cloud server, so I will be doing it image by image. Right now I have used the below code to get things working

#include <Arduino.h>
#include <WiFi.h>
#include <WiFiClientSecure.h>
#include "soc/soc.h"
#include "soc/rtc_cntl_reg.h"
#include "esp_camera.h"

const char* ssid = "Semicon Media";
const char* password = "cracksen1605";
String serverName = "circuitdigest.pythonanywhere.com";   // Replace with your PythonAnywhere domain
String serverPath = "/receive_image";     // Adjust path based on your Flask route
const int serverPort = 443; // Server port for HTTPS

int count = 0;

WiFiClientSecure client;

// Camera GPIO pins - adjust based on your ESP32-CAM board
#define PWDN_GPIO_NUM     32
#define RESET_GPIO_NUM    -1
#define XCLK_GPIO_NUM      0
#define SIOD_GPIO_NUM     26
#define SIOC_GPIO_NUM     27
#define Y9_GPIO_NUM       35
#define Y8_GPIO_NUM       34
#define Y7_GPIO_NUM       39
#define Y6_GPIO_NUM       36
#define Y5_GPIO_NUM       21
#define Y4_GPIO_NUM       19
#define Y3_GPIO_NUM       18
#define Y2_GPIO_NUM        5
#define VSYNC_GPIO_NUM    25
#define HREF_GPIO_NUM     23
#define PCLK_GPIO_NUM     22

void setup() {
  WRITE_PERI_REG(RTC_CNTL_BROWN_OUT_REG, 0); 
  Serial.begin(115200);

  WiFi.mode(WIFI_STA);
  Serial.println();
  Serial.print("Connecting to ");
  Serial.println(ssid);
  WiFi.begin(ssid, password);  
  while (WiFi.status() != WL_CONNECTED) {
    Serial.print(".");
    delay(500);
  }
  Serial.println();
  Serial.print("ESP32-CAM IP Address: ");
  Serial.println(WiFi.localIP());

  // Configure 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_sscb_sda = SIOD_GPIO_NUM;
  config.pin_sscb_scl = SIOC_GPIO_NUM;
  config.pin_pwdn = PWDN_GPIO_NUM;
  config.pin_reset = RESET_GPIO_NUM;
  config.xclk_freq_hz = 20000000;
  config.pixel_format = PIXFORMAT_GRAYSCALE;

  if(psramFound()){
    config.frame_size = FRAMESIZE_96X96;
    config.jpeg_quality = 20;  // Lower number means higher quality (0-63)
    config.fb_count = 2;
    Serial.printf("PSRAM found");
  } else {
    config.frame_size = FRAMESIZE_CIF;
    config.jpeg_quality = 12;  // Lower number means higher quality (0-63)
    config.fb_count = 1;
  }
  
  // Initialize camera
  esp_err_t err = esp_camera_init(&config);
  if (err != ESP_OK) {
    Serial.printf("Camera init failed with error 0x%x", err);
    delay(1000);
    ESP.restart();
  }

  // Initial photo send
  sendPhoto(); 
}

void loop() {
  // Example: Send photo every 30 seconds
  unsigned long currentMillis = millis();
  static unsigned long previousMillis = 0;
  const int timerInterval = 10000;

  if (currentMillis - previousMillis >= timerInterval) {
    Serial.println("Lets send next image..");
    sendPhoto();
    previousMillis = currentMillis;
  }
}

String sendPhoto() {
  camera_fb_t * fb = NULL;
  fb = esp_camera_fb_get();
  if(!fb) {
    Serial.println("Camera capture failed");
    return "Camera capture failed";
  }
  
  Serial.println("Connecting to server: " + serverName);
  
  client.setInsecure(); // Skip certificate validation (for simplicity, consider security implications in a production environment)
  if (client.connect(serverName.c_str(), serverPort)) {
    Serial.println("Connection successful!");
    count=count+1;
    Serial.println(count);

    // Generate a unique filename using the current timestamp
    //String filename = "esp32-cam-" + String(count) + ".jpg";
    String filename = "esp32-cam-image.jpg";
    
    String head = "--RandomNerdTutorials\r\nContent-Disposition: form-data; name=\"imageFile\"; filename=\"" + filename + "\"\r\nContent-Type: image/jpeg\r\n\r\n";
    String tail = "\r\n--RandomNerdTutorials--\r\n";

    uint32_t imageLen = fb->len;
    uint32_t extraLen = head.length() + tail.length();
    uint32_t totalLen = imageLen + extraLen;

    // Send HTTP POST request with multipart/form-data
    client.println("POST " + serverPath + " HTTP/1.1");
    client.println("Host: " + serverName);
    client.println("Content-Length: " + String(totalLen));
    client.println("Content-Type: multipart/form-data; boundary=RandomNerdTutorials");
    client.println();
    client.print(head);

    // Send image data in chunks
    uint8_t *fbBuf = fb->buf;
    size_t fbLen = fb->len;
    for (size_t n = 0; n < fbLen; n = n + 1024) {
      if (n + 1024 < fbLen) {
        client.write(fbBuf, 1024);
        fbBuf += 1024;
      } else {
        size_t remainder = fbLen % 1024;
        client.write(fbBuf, remainder);
      }
    }

    client.print(tail);
    
    // Clean up
    esp_camera_fb_return(fb);
    
    // Wait for server response
    String response;
    long startTime = millis();
    while (client.connected() && millis() - startTime < 10000) {
      if (client.available()) {
        char c = client.read();
        response += c;
      }
    }
    
    // Print server response
    Serial.println(response);
    client.stop();
    return response;
  } else {
    Serial.println("Connection to server failed");
    return "Connection to server failed";
  }
}

On my flask server side I am saving the image using the below code

[code]# Ensure the 'mysite/static' directory exists
UPLOAD_FOLDER = 'mysite/static/'
if not os.path.exists(UPLOAD_FOLDER):
    os.makedirs(UPLOAD_FOLDER)

# Path to save the image
IMAGE_FILE_PATH = os.path.join(UPLOAD_FOLDER, 'received_image.jpg')

@app.route('/mysite/static/<filename>')
def uploaded_file(filename):
    return send_from_directory(UPLOAD_FOLDER, filename)


@app.route('/receive_image', methods=['POST'])
def receive_image():
    if 'imageFile' not in request.files:
        return jsonify({'error': 'No image file provided'}), 400

    imageFile = request.files['imageFile']
    if imageFile.filename == '':
        return jsonify({'error': 'No selected file'}), 400

    # Save the file to the specified directory
    file_path = os.path.join(UPLOAD_FOLDER, imageFile.filename)
    imageFile.save(file_path)

    return jsonify({'message': 'Image received and saved successfully'}), 200[/code]

I am trying to stitch the images together to get a video so that i can do real time image processing. I am even okay with very slow FPS, something like 3-5. But here in the above code it takes 7-9 seconds for a single image to reach my cloud. How can i speed this up?

I have tried capturing images in grayscale but that did not improve the speed
I also tried reducing image size to 96x96 but then the code stopped working with error shown below

"""ESP32-CAM IP Address: 192.168.29.178
PSRAM foundConnecting to server: circuitdigest.pythonanywhere.com
Connection successful!
1
[ 19876][E][ssl_client.cpp:37] _handle_error(): [send_ssl_data():382]: (-80) UNKNOWN ERROR CODE (0050)

Lets send next image..
Connecting to server: circuitdigest.pythonanywhere.com
Connection successful!
2
[ 24205][E][ssl_client.cpp:37] _handle_error(): [send_ssl_data():382]: (-80) UNKNOWN ERROR CODE (0050)

Lets send next image..
Connecting to server: circuitdigest.pythonanywhere.com
"""

I believe your using psram to save and read the image. Psram is not the fastest

You would have to do some profiling to see where its taking to long. Record the timing it takes for example to send the image, recieve the image, upload the image. A simple way is to measure in micros is:

Before a section of code
uint32_t startTime = micros():

After a section of code
uint32_t endTime = micros() - startTime:
Serial.println(endTime):

Ive never done what youve done before, but looking at it, if your constantly sending photos every photo do you have to re-establish the connection to the server? Can you just connect in setup?