Skip to content

Instantly share code, notes, and snippets.

@hernantech
Last active February 9, 2025 01:44
Show Gist options
  • Save hernantech/362c36485922fc3560a8ccdeb3b35a0f to your computer and use it in GitHub Desktop.
Save hernantech/362c36485922fc3560a8ccdeb3b35a0f to your computer and use it in GitHub Desktop.
fsae cs interns espy computer vision

ESP32-S3 Camera Integration Discussion

Context: Discussion regarding integration of OV9281 camera module with specifications:

  • USB 2.0 interface
  • Resolution options:
    • 2560x720 at 60FPS
    • 1600x600 at 100FPS
    • 1280x480 at 100FPS
    • 1280x400 at 120FPS
  • Power consumption: 1000mW
  • YUY2/MJPEG output formats

Analysis of integration options:

  1. Direct Integration Challenges:

    • ESP32-S3 lacks native USB host capabilities
    • Need for alternative approach
  2. Recommended Alternative Cameras:

    • OV2640
    • OV5640
    • GC0308 These cameras connect directly via ESP32's camera interface pins (VSYNC, HREF, PCLK)
  3. Custom Integration Option:

    • Requires USB host controller (MAX3421E)
    • Additional power management circuitry
    • Custom driver development needed

CH334F Integration Discussion

User Question: What about with a CH334F mini USB 4 port hub?

Response: Analysis of CH334F integration approach:

  1. Power Considerations:

    • OV9281 power requirement: 1000mW
    • CH334F needs stable 5V power provision
    • Level shifting required for ESP32-S3's 3.3V logic
  2. Data Path Challenges:

    • CH334F primary design: USB to UART/SPI/I2C conversion
    • Bandwidth limitations for 120FPS operation
  3. Solution Architecture:

OV9281 Camera -> CH334F -> UART -> ESP32-S3
                   |
                   -> 5V Power Management

Limitations:

  • CH334F UART speed bottleneck
  • Constraints on resolution and framerate
  • 2M baud maximum limitation

Alternative Suggestions:

  1. USB Host shield with SPI interface (MAX3421E)
  2. Parallel camera interface native to ESP32-S3
  3. More capable processor for camera interface

WiFi Streaming Implementation

Can I use the WiFi capabilities to feed it images through web? Something like RTC?

Yes, with a WebSocket-based solution:

ESP32-S3 WebSocket Client Code:

#include <WiFi.h>
#include <AsyncTCP.h>
#include <ESPAsyncWebServer.h>
#include <WebSocketsClient.h>

// WiFi credentials
const char* ssid = "YOUR_WIFI_SSID";
const char* password = "YOUR_WIFI_PASSWORD";

// WebSocket client
WebSocketsClient webSocket;

// Buffer for receiving image data
const size_t BUFFER_SIZE = 1024;
uint8_t imageBuffer[BUFFER_SIZE];
size_t bufferIndex = 0;

void setup() {
  Serial.begin(115200);
  
  // Connect to WiFi
  WiFi.begin(ssid, password);
  while (WiFi.status() != WL_CONNECTED) {
    delay(1000);
    Serial.println("Connecting to WiFi...");
  }
  Serial.println("Connected to WiFi");

  // Initialize WebSocket client
  webSocket.begin("your_server_ip", 8080, "/ws");
  webSocket.onEvent(webSocketEvent);
  webSocket.setReconnectInterval(5000);
}

void loop() {
  webSocket.loop();
}

void webSocketEvent(WStype_t type, uint8_t * payload, size_t length) {
  switch(type) {
    case WStype_DISCONNECTED:
      Serial.println("Disconnected from WebSocket server");
      break;
    
    case WStype_CONNECTED:
      Serial.println("Connected to WebSocket server");
      break;
    
    case WStype_BIN:
      // Handle incoming image data
      if (length > 0) {
        // Process the received image data
        processImageData(payload, length);
      }
      break;
  }
}

void processImageData(uint8_t* data, size_t length) {
  // Here you would implement your image processing logic
  // For example, displaying on a connected display or forwarding to another device
  
  // Example: Print first few bytes for debugging
  Serial.print("Received image data. First bytes: ");
  for(int i = 0; i < min(length, (size_t)16); i++) {
    Serial.printf("%02X ", data[i]);
  }
  Serial.println();
}

// Function to handle sending commands back to the server if needed
void sendCommand(const char* command) {
  webSocket.sendTXT(command);
}

Python WebSocket Server Implementation:

import asyncio
import websockets
import cv2
import numpy as np

# Camera setup
camera = cv2.VideoCapture(0)  # Use appropriate index for your USB camera
camera.set(cv2.CAP_PROP_FRAME_WIDTH, 1280)
camera.set(cv2.CAP_PROP_FRAME_HEIGHT, 400)
camera.set(cv2.CAP_PROP_FPS, 120)

async def stream_camera(websocket, path):
    try:
        print("Client connected")
        while True:
            ret, frame = camera.read()
            if not ret:
                break
                
            # Convert frame to JPEG
            ret, buffer = cv2.imencode('.jpg', frame, [cv2.IMWRITE_JPEG_QUALITY, 50])
            jpg_bytes = buffer.tobytes()
            
            # Send frame
            await websocket.send(jpg_bytes)
            
            # Control frame rate
            await asyncio.sleep(1/30)  # Adjust for desired frame rate
            
    except websockets.exceptions.ConnectionClosed:
        print("Client disconnected")
    except Exception as e:
        print(f"Error: {e}")

start_server = websockets.serve(stream_camera, "0.0.0.0", 8080)

asyncio.get_event_loop().run_until_complete(start_server)
print("Server started")
asyncio.get_event_loop().run_forever()

UART Implementation

Also tho! UART implementation with bandwidth considerations:

Bandwidth Calculations:

  • ESP32-S3 UART maximum speed: 5Mbps
  • Camera capability: 1280x400 at 120FPS
  • Raw data rate: 61.44 MB/s (491.52 Mbps)

Python UART Sender Implementation:

import cv2
import serial
import time
import numpy as np

def setup_camera():
    # Initialize camera (adjust index as needed)
    cap = cv2.VideoCapture(0)
    # Set lower resolution for UART bandwidth
    cap.set(cv2.CAP_PROP_FRAME_WIDTH, 320)
    cap.set(cv2.CAP_PROP_FRAME_HEIGHT, 240)
    cap.set(cv2.CAP_PROP_FPS, 30)
    return cap

def setup_uart():
    # Higher baud rate for faster transmission
    ser = serial.Serial(
        port='COM3',  # Change to your port
        baudrate=921600,
        parity=serial.PARITY_NONE,
        stopbits=serial.STOPBITS_ONE,
        bytesize=serial.EIGHTBITS
    )
    return ser

def compress_frame(frame):
    # Convert to grayscale
    gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
    # Basic compression - you might want to use more sophisticated methods
    compressed = cv2.imencode('.jpg', gray, [cv2.IMWRITE_JPEG_QUALITY, 30])[1]
    return compressed

def send_frame(ser, data):
    # Frame start marker
    ser.write(b'START')
    # Send frame size
    size = len(data)
    ser.write(size.to_bytes(4, byteorder='big'))
    # Send frame data
    ser.write(data)
    # Frame end marker
    ser.write(b'END')
    
def main():
    cap = setup_camera()
    ser = setup_uart()
    
    try:
        while True:
            ret, frame = cap.read()
            if not ret:
                break
                
            # Compress frame
            compressed = compress_frame(frame)
            
            # Send over UART
            send_frame(ser, compressed.tobytes())
            
            # Control frame rate
            time.sleep(1/30)  # 30 FPS
            
    except KeyboardInterrupt:
        print("Stopping...")
    finally:
        cap.release()
        ser.close()

if __name__ == "__main__":
    main()

ESP32-S3 UART Receiver Implementation:

#include <Arduino.h>

// Buffer for receiving image data
const size_t MAX_FRAME_SIZE = 32768;  // Adjust based on your needs
uint8_t frameBuffer[MAX_FRAME_SIZE];
size_t bufferIndex = 0;

// State machine states
enum State {
    WAITING_FOR_START,
    READING_SIZE,
    READING_FRAME,
    READING_END
};

State currentState = WAITING_FOR_START;
uint32_t expectedSize = 0;
uint8_t sizeBuf[4];
uint8_t sizeIndex = 0;

void setup() {
    // Debug serial
    Serial.begin(115200);
    // Data serial - use a different UART
    Serial1.begin(921600);
    
    Serial.println("ESP32 UART receiver started");
}

void processFrame(uint8_t* data, size_t length) {
    // Here you would process the received JPEG data
    // For example, decompress and display it, or forward it to another device
    Serial.printf("Received frame of size: %d\n", length);
}

void loop() {
    if (Serial1.available()) {
        uint8_t byte = Serial1.read();
        
        switch (currentState) {
            case WAITING_FOR_START:
                static uint8_t startSequence[] = {'S', 'T', 'A', 'R', 'T'};
                static uint8_t startIndex = 0;
                
                if (byte == startSequence[startIndex]) {
                    startIndex++;
                    if (startIndex == 5) {
                        startIndex = 0;
                        currentState = READING_SIZE;
                    }
                } else {
                    startIndex = 0;
                }
                break;
                
            case READING_SIZE:
                sizeBuf[sizeIndex++] = byte;
                if (sizeIndex == 4) {
                    expectedSize = (sizeBuf[0] << 24) | (sizeBuf[1] << 16) | 
                                 (sizeBuf[2] << 8) | sizeBuf[3];
                    sizeIndex = 0;
                    bufferIndex = 0;
                    currentState = READING_FRAME;
                }
                break;
                
            case READING_FRAME:
                if (bufferIndex < MAX_FRAME_SIZE) {
                    frameBuffer[bufferIndex++] = byte;
                }
                if (bufferIndex == expectedSize) {
                    currentState = READING_END;
                }
                break;
                
            case READING_END:
                static uint8_t endSequence[] = {'E', 'N', 'D'};
                static uint8_t endIndex = 0;
                
                if (byte == endSequence[endIndex]) {
                    endIndex++;
                    if (endIndex == 3) {
                        endIndex = 0;
                        processFrame(frameBuffer, expectedSize);
                        currentState = WAITING_FOR_START;
                    }
                } else {
                    endIndex = 0;
                }
                break;
        }
    }
}

Implementation Optimizations:

  1. Resolution reduced to 320x240
  2. Frame rate limited to 30 FPS
  3. JPEG compression with quality=30
  4. High baud rate (921600)
  5. Frame protocol with START/END markers and size header

Original Hardware Specifications

Full specifications of the OV9281 camera module from the original document:

Module Specifications:

  • Module No.: HBVCAM-W2237-2 V11
  • Module Size: 156mm × 36mm ×24.60mm±0.5MM
  • Temperature (Operation): -30°C to 70°C
  • Temperature (Stable Image): 0°C to 50°C
  • Assembly technique: SMT (ROSH)
  • Focus: Fixed
  • Object distance: 30CM-infinity
  • Resolution: 600LW/PH (Center)
  • PCB printing ink: Black

Interface Specifications:

  • Interface: USB 2.0
  • Power supply: USB bus power
  • Supply voltage: DC 5V±5%
  • Maximum Power consumption: 1000 mW
  • Operating system compatibility: Win XP/win 7、8 / vista /android 4.0/ mac /Linux with uvc

Sensor Specifications:

  • Sensor: OV9281 (1/4")
  • Sensor Package: CSP,Bare Die
  • Maximum resolution: 2560*720
  • Sensitivity: 1300mV /(lux-sec)
  • Pixel Size: 3µm x 3µm

Image Transfer Rates:

  • MJPEG 1280x400: 120FPS / YUY2 1280x400: 120FPS
  • MJPEG 1280x480: 100FPS / YUY2 1280x480: 100FPS
  • MJPEG 1600x600: 100FPS / YUY2 1600x600: 100FPS
  • MJPEG 2560x720: 60FPS / YUY2 2560x720: 60FPS

Additional Specifications:

  • S/N Ratio: 38db
  • Max Dynamic Range: 68db
  • Shutter type: Electronic rolling shutter
  • AGC/AEC/Whiter balance: Auto
  • Output Formats: YUY2/MJPEG
  • Microphone: NO
  • Lens Construction: 2G2P
  • F/No: 2.4
  • EFL: 3.6 mm
  • FOV: 72°
  • TV Distortion: <5%
  • Relative Illumination (Sensor): 70%
  • IR Filter: 650±10nm
  • Module Package: Anti-electrostatic tray
  • Certifications: FCC/CE/ROSH
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment