Skip to content

Object-Triggered Image Capture

An object-triggered camera system that automatically captures and serves images over Ethernet when objects are detected within 20 centimeter

ronpang

Published February 04, 2026

Original author: Crazy CatOriginal source (new tab)

Object-Triggered Image Capture

Components

Hardware components

Project description

Introduction

Modern IoT security and monitoring applications require intelligent systems that can autonomously detect objects and capture visual evidence. This project demonstrates how to build an object-triggered camera system using the W6300-EVB-PICO2 microcontroller with Ethernet capabilities. The system automatically detects objects using ultrasonic sensing and captures images when objects enter the detection zone, serving them through a real-time web interface.

Step 1: Gather Components

For this project, you will need:

- W6300-EVB-PICO2 Microcontroller

- HC-SR04 Ultrasonic Distance Sensor

- OV2640 Camera Module

- 10kΩ resistors (2 units for voltage divider, 2 units for I2C pull-up)

- Breadboard and jumper wires

Step 1: Gather Components

Step 2: Hardware Setup

Connections

HC-SR04 Ultrasonic Sensor → W6300-EVB-PICO2:

- VCC → 5V

- Trig → GP27

- Echo → GP26 (via voltage divider)

- GND → GND

Step 2: Hardware Setup

OV2640 Camera → W6300-EVB-PICO2:

- VSYNC → GP12

- HREF → GP11

- PCLK → GP10

- D0-D7 → GP0-GP7 (data bus)

- SCL → GP9 (I2C clock) WITH 2kΩ pull-up to 3.3V

- SDA → GP8 (I2C data) WITH 2kΩ pull-up to 3.3V

- RESET → GP13 (optional)

Important Notes:

- Voltage divider essential for HC-SR04 (5V output → 3.3V safe input)

- I2C pull-up resistors required for reliable camera communication

- Double-check all connections before powering on the board

1. Why Voltage Divider for HC-SR04?

Safety Requirement: HC-SR04 outputs 5V on Echo pin, but RP2040 GPIO maximum is 3.6V

Signal Protection: Prevents damage to microcontroller GPIO pins

Reliable Operation: Ensures proper logic level recognition (2.5V = HIGH, safe margin)

2. Why I2C Pull-up Resistors?

Protocol Requirement: I2C uses open-drain bus requiring pull-up resistors

Signal Integrity: Ensures proper HIGH/LOW logic levels

Camera Detection: Required for camera I2C address (0x30) detection

Step 2: Hardware Setup

Step 3: Software Libraries Setup

Required Libraries

Create a `lib` folder on your board with these libraries:

Core Libraries:

- `adafruit_wiznet5k` - Ethernet connectivity

- `adafruit_wiznet5k_socketpool` - Socket management

- `adafruit_ov2640` - Camera control

- `adafruit_hcsr04` - Ultrasonic sensor

Import Required Libraries

import time
import gc
import board
import busio
import digitalio
import wiznet
from adafruit_wiznet5k.adafruit_wiznet5k import WIZNET5K
import adafruit_wiznet5k.adafruit_wiznet5k_socketpool as socketpool
import adafruit_ov2640
import adafruit_hcsr04

 

Network Configuration

MY_MAC = "00:08:DC:03:04:05"
# Using DHCP for automatic IP assignment

 

Hardware Initialization

Ethernet Setup:

# Reset W6300 chip
ethernetRst = digitalio.DigitalInOut(board.W5K_RST)
ethernetRst.direction = digitalio.Direction.OUTPUT
ethernetRst.value = False
time.sleep(1)
ethernetRst.value = True
time.sleep(1)

# SPI setup for Ethernet
spi_bus = wiznet.PIO_SPI(
    board.W5K_SCK,              # Clock pin
    quad_io0=board.W5K_MOSI,    # Master Out Slave In (MOSI)
    quad_io1=board.W5K_MISO,    # Master In Slave Out (MISO)
    quad_io2=board.W5K_IO2,     # Additional IO for quad-SPI
    quad_io3=board.W5K_IO3,     # Additional IO for quad-SPI
)

# Initialize Ethernet with DHCP
cs = digitalio.DigitalInOut(board.W5K_CS)
eth = WIZNET5K(spi_bus, cs, is_dhcp=True, mac=MY_MAC, debug=False)
ip_address = eth.pretty_ip(eth.ip_address)
print(f"IP Address: {ip_address}")

Camera Setup:

i2c = busio.I2C(board.GP9, board.GP8)
cam = adafruit_ov2640.OV2640(
    i2c,                      # I2C bus for camera control
    data_pins=[board.GP0, board.GP1, board.GP2, board.GP3,
              board.GP4, board.GP5, board.GP6, board.GP7],  # 8-bit parallel data
    clock=board.GP10,         # Pixel clock (20MHz)
    vsync=board.GP12,         # Vertical sync (new frame)
    href=board.GP11,          # Horizontal reference (new line)
    reset=board.GP13,         # Hardware reset control
    mclk_frequency=20_000_000,# Master clock frequency
)
cam.size = adafruit_ov2640.OV2640_SIZE_SVGA
cam.colorspace = adafruit_ov2640.OV2640_COLOR_JPEG
time.sleep(2)

Ultrasonic Sensor Setup:

sonar = adafruit_hcsr04.HCSR04(
    trigger_pin=board.GP27,     # Output: 10µs pulse to start measurement
    echo_pin=board.GP26,        # Input: Pulse width proportional to distance
    timeout=0.5                 # Maximum wait time for echo (0.5s = ~85m max)
)

 

System Configuration

# Frame buffer for image capture
frame_buffer = bytearray(40 * 1024)

# Detection threshold (20 cm)
DETECTION_THRESHOLD = 20.0

# Critical state variables
latest_detection_image = None     # Buffer: Stores JPEG data (up to 40KB)
current_distance = None          # Float: Last measured distance in cm
show_image = False              # Boolean: Control image display on webpage
last_detection_time = 0         # Float: Timestamp of last detection
detection_cooldown = 1.0        # Float: Minimum time between captures

 

Core Functions

Distance Measurement:

def measure_distance():
    try:
        distance = sonar.distance
        if 2 <= distance <= 400:
            return distance
    except RuntimeError:
        pass
    return None

*Measures distance with validation (2-400cm range)*

Image Capture:

def capture_image():
    try:
        jpeg_data = cam.capture(frame_buffer)
        if jpeg_data:
            return bytes(jpeg_data)
    except Exception as e:
        print(f"Capture error: {e}")
    return None

*Captures and validates JPEG images*

 

Web Server Functions:

send_html_response() - Detailed Explanation

HTTP Response Construction Process

def send_html_response(conn, client_ip):
    """Send HTML page"""
    global current_distance, latest_detection_image, show_image
    
    # Handle None distance
    distance_str = f"{current_distance:.1f} cm" if current_distance is not None else "-- cm"
    
    # Check if we should show the image
    show_image = current_distance is not None and current_distance < DETECTION_THRESHOLD
    
    # Border color based on detection
    border_color = "red" if show_image else "#ccc"

Step 1: Data Gathering

Memory State → HTML Variables

─────────────────────────────────────────

current_distance: 8.5 → "8.5 cm"

show_image: True → border_color: "red"

latest_detection_image: [JPEG data] → Image will be shown

 

Step 2: Conditional HTML Generation

if show_image:
    text = """
     <img src="/latest.jpg" alt="Camera Image">
    """
else:
    text = """
    <div id="placeholder">
        <div>
            <h3>No Object Detected</h3>
            <p>Image will appear when object is within 20cm</p>
            <p>Current distance: {distance_str}</p>
        </div>
    </div>
    """

 

Step 3: HTTP Protocol Assembly

response = (
    f"HTTP/1.1 200 OK\r\n"
    f"Content-Type: text/html; charset=utf-8\r\n"
    f"Connection: close\r\n"
    f"Content-Length: {len(html)}\r\n"
    f"\r\n{html}"
).encode()

Exact Bytes Sent to Browser:

Byte Stream (example):
48 54 54 50 2F 31 2E 31 20 32 30 30 20 4F 4B 0D 0A   HTTP/1.1 200 OK\r\n
43 6F 6E 74 65 6E 74 2D 54 79 70 65 3A 20 74 65 78   Content-Type: tex
74 2F 68 74 6D 6C 3B 20 63 68 61 72 73 65 74 3D 75   t/html; charset=u
74 66 2D 38 0D 0A 43 6F 6E 6E 65 63 74 69 6F 6E 3A   tf-8\r\nConnection:
20 63 6C 6F 73 65 0D 0A 43 6F 6E 74 65 6E 74 2D 4C    close\r\nContent-L
65 6E 67 74 68 3A 20 31 35 32 37 0D 0A 0D 0A 3C 21   ength: 1527\r\n\r\n<!
44 4F 43 54 59 50 45 20 68 74 6D 6C 3E 0A 3C 68 74   DOCTYPE html>\n<ht
6D 6C 3E...                                          ml>...

Why This Structure Matters:

Browser Expectation:            Our Delivery:
1. Status Line                  HTTP/1.1 200 OK
2. Headers                      Content-Type, Connection, Content-Length
3. Empty Line                   \r\n\r\n (CRLF CRLF)
4. Body                         <html>...</html>

 

Step 4: Network Transmission

try:
    conn.send(response)
    print(f"  Distance: {distance_str}")
    return True
except Exception as e:
    print(f"  Failed to send HTML: {e}")
    return False

 

 

send_image_response() - Detailed Explanation

Binary Image Transmission Process

def send_image_response(conn):
    """Send JPEG image with 2048 byte chunks"""
    global latest_detection_image
    
    if latest_detection_image:
        image_length = len(latest_detection_image)
        
        response = (
            f"HTTP/1.1 200 OK\r\n"
            f"Content-Type: image/jpeg\r\n"
            f"Content-Length: {image_length}\r\n"
            f"Cache-Control: no-cache\r\n"
            f"Connection: close\r\n"
            f"\r\n"
        ).encode()

 

Step 1: HTTP Headers for Binary Data

Critical Headers Explained:
Content-Type: image/jpeg
- Tells browser "this is a JPEG image, not HTML/text"
- Browser will render as image, not display raw bytes

Content-Length: 28765
- Exact byte count of JPEG data
- Browser knows when transmission is complete

Cache-Control: no-cache
- Prevents browser from storing old images
- Ensures fresh image on every request

Connection: close
- Close TCP after image sent
- Frees WIZNET socket for next request

 

Step 2: Chunked Transmission Algorith

# Send in 2048 byte chunks
chunk_size = 2048
for i in range(0, image_length, chunk_size):
    end = min(i + chunk_size, image_length)
    conn.send(latest_detection_image[i:end])

Send the image separately to prevents:

  • Buffer overflow (if chunk > free space)
  • Deadlock (waiting for ACK with full buffer)
  • Fragmentation (inefficient small packets)

 

Step 3 : Error Handling and Cleanup

Network Failure Scenarios Handled:

except Exception as e:
    print(f"  Image send error: {e}")

 

Step 4: Browser Reception and Rendering

Browser Processing Pipeline:

1. Receives HTTP/1.1 200 OK
2. Sees Content-Type: image/jpeg
3. Allocates buffer for 28,765 bytes (Content-Length)
4. Receives chunks, fills buffer
5. Validates JPEG (checks for FF D8 ... FF D9)
6. Decodes JPEG to RGB pixels
7. Renders in <img> tag on page
8. Cache-control: no-cache → won't reuse on next request

 

Web Server Setup

pool = socketpool.SocketPool(eth)
server = pool.socket()
server.bind((ip_address, 80))
server.listen(1)
print(f"Server ready at http://{ip_address}")

Main Loop Logic

The system operates in a continuous loop with three primary functions:

1. DHCP Maintenance (every 30 seconds):

  • Renews DHCP lease to maintain network connectivity

2. Object Detection (every 0.5 seconds):

Measures distance using ultrasonic sensor

If object < 20cm AND cooldown period passed:

  • Captures JPEG image
  • Validates image (checks for JPEG header)
  • Stores image in memory
  • Updates web display flag

If no detection:

  • Clears stored images
  • Runs garbage collection

3. HTTP Request Handling:

Listens for browser connections (0.1s timeout)

Processes requests:

  • `GET /latest.jpg` → Serves captured image
  • Any other request → Serves HTML page with auto-refresh

Closes connections immediately after serving

# Main loop - SINGLE SERVER SOCKET
try:
    while True:
        current_time = time.monotonic()
        
        # 1. DHCP Maintenance (every 30 seconds)
        if current_time - last_dhcp_check > 30:
            eth.maintain_dhcp_lease()
            last_dhcp_check = current_time
        
        # 2. Object Detection Logic
        if connection_active:
            if current_time - last_distance_check > 0.5:
                distance = measure_distance()
                current_distance = distance
                
                if distance and distance < DETECTION_THRESHOLD:
                    if current_time - last_detection_time > detection_cooldown:
                        # Detection confirmed - capture image
                        led.value = True
                        image_data = capture_image()
                        
                        if image_data:
                            # Validate JPEG magic number
                            if image_data[0] == 0xFF and image_data[1] == 0xD8:
                                latest_detection_image = image_data
                                show_image = True
                                last_detection_time = current_time
                                print(f"🚨 Detected: {distance:.1f}cm")
                        
                        time.sleep(0.1)
                        led.value = False
                elif distance is None or distance >= DETECTION_THRESHOLD:
                    show_image = False
                    latest_detection_image = None
                    gc.collect()  # Free memory
                
                last_distance_check = current_time
        
        # 3. Web Server Handling
        try:
            server.settimeout(0.1)
            conn, addr = server.accept()
            # ... handle HTTP requests ...
            
        except Exception as e:
            if "timeout" not in str(e):
                connection_active = False
                
        time.sleep(0.01)

HTML Interface Features

  • Real-time distance display with color coding
  • Automatic image display on detection
  • 2-second auto-refresh for updates
  • Responsive design for different devices
  • Visual status indicators (red/green)

Step 5: System Features and Benefits

Key Features:

1. Automatic Object Detection - Ultrasonic sensor triggers image capture

2. Real-time Web Interface - Live updates without manual refresh

3. Efficient Memory Management - Adaptive buffer usage and garbage collection

4. Network Reliability - Ethernet connection with DHCP support

5. Fail-safe Operation - Comprehensive error handling and recovery

Technical Benefits:

- Low Latency: Image capture within 100ms of detection

- Resource Efficient: Optimized for microcontroller constraints

- Scalable Design: Can be extended with multiple sensors

- Professional Interface: Clean web UI with status indicators

Step 6: Demonstration

Conclusion

This object detection camera system successfully demonstrates:

1. Intelligent Automation - Autonomous detection and capture

2. Real-time Monitoring - Instant web interface updates

3. Hardware Integration - Seamless sensor-camera-Ethernet coordination

4. Resource Optimization - Efficient operation on constrained hardware

The system provides a practical foundation for various applications including:

- Security and surveillance systems

- Package delivery monitoring

- Industrial automation

- Smart home applications

By combining reliable Ethernet connectivity with responsive sensing and imaging capabilities, this project showcases how embedded systems can deliver sophisticated IoT functionality with minimal hardware requirements.


 

Documents

Comments

Similar projects you might like

Comments