A comprehensive real-time circle detection and servo control system designed for precise object tracking and automated canard positioning using a Raspberry Pi, PCA9685 servo controller, and computer vision.
- Features
- System Architecture
- Project Structure
- Components
- Installation
- Usage
- Configuration
- Workflow
- Technical Details
- Results & Analysis
- Real-Time Circle Detection: Hough Circle Transform-based detection with configurable sensitivity
- Dual-Axis Servo Control: Independent PID controllers for horizontal (yaw) and vertical (pitch) positioning
- Centered Coordinate System: Origin at frame center for intuitive control mapping
- Data Logging: Comprehensive CSV logging of coordinates, errors, and servo commands
- Performance Metrics: Automatic calculation of FPS, detection recall, and tracking error
- Multi-Format Output: MP4 video, JPG images, PNG analysis plots, and CSV data
- Flexible Testing: Picture and video modes with interactive servo testing
┌─────────────────────────────────────────────────────────┐
│ DETECTION PIPELINE │
├─────────────────────────────────────────────────────────┤
│ │
│ Camera Input │
│ ↓ │
│ Frame Capture (detection.py) │
│ ↓ │
│ Image Processing (process.py) │
│ - Grayscale conversion │
│ - Gaussian blur │
│ - Hough Circle Transform │
│ ↓ │
│ Circle Detection Result │
│ ↓ │
│ Coordinate Mapping (detection.py) │
│ - Convert to centered coordinates │
│ ↓ │
│ Error Calculation (analysis.py) │
│ - Euclidean distance from target │
│ ↓ │
│ PID Control (control.py) │
│ - Proportional term │
│ - Integral term │
│ - Derivative term │
│ ↓ │
│ Servo Actuation (control.py) │
│ - Angle conversion │
│ - PCA9685 PWM output │
│ ↓ │
│ Data Logging (files.py) │
│ - CSV format (Time, X, Y, Pitch, Yaw, Detections) │
│ - Video frame output │
│ ↓ │
│ Analysis & Visualization (analysis.py) │
│ - Multi-subplot plots (X, Y, Tracking Error vs Time) │
│ │
└─────────────────────────────────────────────────────────┘
Detection/
├── code/
│ ├── __init__.py # Package initialization
│ ├── analysis.py # Data analysis & visualization
│ ├── control.py # PID servo control
│ ├── detection.py # Main detection pipeline
│ ├── files.py # File I/O operations
│ ├── process.py # Circle detection (Hough Transform)
│ ├── run.py # Entry point & orchestration
│ └── FILE_SUMMARY.md # Detailed module documentation
├── tests/
│ ├── test.py # General tests
│ ├── camera_test.py # Camera functionality tests
│ └── raspberry_test.py # Servo control tests (interactive menu)
├── results/
│ ├── Data/ # CSV logs & PNG plots
│ ├── picture/ # Saved images
│ └── video/ # Saved video files
├── src/
│ ├── __main__ # Main entry script
│ └── img/ # Sample images
├── __main__.py # Application launcher
├── pyproject.toml # Project configuration
├── README.md # This file
└── detection.service # Systemd service file
- Raspberry Pi (5 or 4): Main processing unit
- PCA9685 PWM Controller: 16-channel servo driver
- Servo Motors: 2x (horizontal & vertical control)
- USB Webcam (25+ FPS): Video input
- Power Supply: 5V for servos (external recommended)
- Python 3.11+
- OpenCV (cv2): Image processing
- NumPy: Numerical computations
- Pandas: CSV data handling
- Matplotlib: Data visualization
- Adafruit CircuitPython ServoKit: PCA9685 control
- pyfirmata: optional board support
- gpiozero: Raspberry Pi GPIO support
- lgpio: low-level GPIO access
python3 -m venv .venv
source .venv/bin/activate # On Windows: .venv\Scripts\activatepip install opencv-python numpy pandas matplotlib adafruit-circuitpython-servokit pyfirmata gpiozero lgpio# Enable I2C interface
sudo raspi-config
# Navigate to Interface Options → I2C → Enable
# Install I2C tools (optional)
sudo apt-get install i2c-tools
# Verify PCA9685 detection
i2cdetect -y 1# Run picture detection
PICTURE_TEST=true pdm run pic_test
# Run video detection with servo control
PICTURE_TEST=false pdm run start
# Run video detection in test mode which not require Rpi connection (no saved MP4)
TEST_MODE=true PICTURE_TEST=false pdm run start_test
# Run interactive servo test
python tests/raspberry_test.py- Loads image from
src/img/pic2.jpg - Performs single circle detection
- Saves annotated image to
results/picture/ - No servo control
- Continuous video stream from webcam
- Real-time circle detection and tracking
- PID servo control based on circle position
- Saves MP4 video with overlays
- Logs all data to CSV
- Generates analysis plots
Interactive menu with 3 options:
- Sweep Test (Positive): Moves servo from 75° to 105° (±15° from center)
- Sweep Test (Negative): Moves servo from 105° to 75° (±15° from center)
- Custom Angle: User-specified angle (0-180°)
PICTURE_TEST=true|false # Picture mode (true) or video mode (false)
TEST_MODE=true|false # When true, disables video file saving and uses test defaults
CAMERA_TEST=true|false # If true, test runner uses camera test path
FIN_CANARD_RATIO=<value> # Logged fin to canard ratio
WIND_SPEED=<value> # Logged wind speed parameter
OL_MODE=true|false # Open-loop baseline servo modescale = 0.5 # Frame downscaling factor
minDist = 1/4 * frame_size # Minimum distance between circles
param1 = 180 # Canny edge detection threshold
param2 = 40 # Hough accumulator threshold (lower = more detections)
minRadius = 5 # Minimum circle radius (pixels)
maxRadius = 100 # Maximum circle radius (pixels)pitch_pid = PID(Kp=1.3, Ki=0.03, Kd=0.15, clamp=(-1, 1)) # Vertical control
yaw_pid = PID(Kp=1.3, Ki=0.03, Kd=0.15, clamp=(-1, 1)) # Horizontal control
# Tested PID
wind_speed_5 = PID(Kp=1.3, Ki=0.03, Kd=0.15, clamp=(-1, 1)) # Horizontal control
wind_speed_10 = PID(Kp=0.55, Ki=0.02, Kd=0.2, clamp=(-1, 1)) # Horizontal control
wind_speed_150 = PID(Kp=0.55, Ki=0.02, Kd=0.2, clamp=(-1, 1)) # Horizontal control- Center Position: 90°
- Range: 75° to 105° (±15°) for safety
- Channels:
- CH_HORIZONTAL = 1
- CH_VERTICAL = 0
- Offsets:
OFFSET_H = 0,OFFSET_V = 10
flowchart TD
A[Start] --> B[Load Image]
B --> C[Preprocess & Detect Circle]
C --> D{Circle Found?}
D -->|Yes| E[Draw Overlays]
D -->|No| F[No Detection]
E --> G[Save Image]
F --> G
G --> H[End]
flowchart TD
A[Start] --> B[Initialize Servos]
B --> C[Open Camera]
C --> D[Capture Frame]
D --> E[Detect Circle]
E --> F[Calculate Error]
F --> G[Apply PID Control]
G --> H[Update Servos]
H --> I[Log Data & Save]
I --> J{ESC Pressed?}
J -->|No| D
J -->|Yes| K[Generate Plots]
K --> L[End]
- Downscale frame by 0.5x to reduce computation
- Convert to grayscale for intensity analysis
- Apply Gaussian blur (5×5 kernel) to reduce noise
- Edge detection via Canny algorithm (param1=180)
- Hough voting to detect circular patterns
- Upscale coordinates back to original frame size
- Return list of (x, y, radius) tuples
- Origin: Frame center (width/2, height/2)
- X-axis: Positive right, negative left
- Y-axis: Positive up, negative down (inverted from OpenCV)
- Range: Typical ±200 pixels for 640×480 frame
output = Kp × error + Ki × integral(error) + Kd × d(error)/dt
- P (Proportional): Immediate response to error
- I (Integral): Corrects steady-state error
- D (Derivative): Dampens oscillations
- Clamping: Output limited to [-1, 1] → servo angle [45°, 135°]
Tracking Error = √(x_centered² + y_centered²)
Represents Euclidean distance from target (frame center)
| Type | Location | Format | Description |
|---|---|---|---|
| Video | results/video/ | .mp4 | Tracked video with overlays |
| Images | results/picture/ | .jpg | Detected circle snapshots |
| Plots | results/Data/ | .png | X/Y/Error vs Time subplots |
| Data | results/Data/ | .csv | Frame-by-frame tracking data |
Time,x,y,pitch,yaw,detections
0.025,12,-8,0.42,-0.18,1
0.050,15,-5,0.48,-0.22,1
0.075,18,-2,0.51,-0.25,1
The system generates a single PNG with 3 subplots:
- X Coordinate vs Time: Horizontal position tracking
- Y Coordinate vs Time: Vertical position tracking
- Tracking Error vs Time: Distance from center with threshold line
- FPS: Frames per second during detection
- Recall: Proportion of frames with valid detection
- Total Detections: Number of frames with detected circles
- Average Tracking Error: Mean distance from target
# Check camera connection
ls /dev/video*
# Test camera access
python -c "import cv2; cap = cv2.VideoCapture(0); print(cap.isOpened())"# Check I2C connection
i2cdetect -y 1 # Should show device at 0x40
# Verify power supply
# Ensure external 5V power for servos- Adjust
param2(lower = more detections) - Check lighting conditions
- Verify
minRadiusandmaxRadiusmatch target - Reduce
minDistfor closer circles
- Check servo voltage (5V external recommended)
- Verify channel numbers in control.py
- Test with
raspberry_test.py - Check pulse width range (500-2500 µs)
- OpenCV Docs: https://docs.opencv.org/
- Hough Circle Transform: https://en.wikipedia.org/wiki/Hough_transform
- PID Control: https://en.wikipedia.org/wiki/PID_controller
- Adafruit ServoKit: https://github.com/adafruit/Adafruit_CircuitPython_ServoKit
- PCA9685 Datasheet: https://www.nxp.com/docs/en/data-sheet/PCA9685.pdf
This project is part of the Master Detection initiative.
For issues or suggestions, please refer to the project documentation in code/FILE_SUMMARY.md.