Project Overview
The License Plate Reader is a two-stage license plate detection pipeline designed for real-time operation on resource-constrained devices like the Raspberry Pi. It prioritizes lightweight computation over accuracy, using edge detection and contour analysis instead of machine learning frameworks.
The system is structured as:
- Stage 1: Edge detection using Canny edge detection to identify plate-like regions
- Stage 2: Contour analysis with aspect ratio filtering to isolate license plates
- Stage 3: Tesseract OCR for text extraction and recognition
This approach eliminates dependency on heavy ML frameworks, making it suitable for deployment on embedded ARM64 systems where memory and CPU are limited.
Key Design Decisions
Why Edge Detection Over ML?
Traditional approaches using neural networks require significant computational resources. For embedded environments:
- Memory: YOLO, TensorFlow, or PyTorch models easily consume 200MB+ RAM
- CPU: GPU acceleration rarely available on Raspberry Pi
- Startup Time: ML frameworks impose initialization overhead
- Reliability: Edge detection is deterministic; model inference can be unpredictable
Edge detection provides a predictable, lightweight alternative optimized for the specific problem domain.
Real-time Scanning
The system captures and processes frames every 2 seconds (configurable), balancing detection frequency with CPU utilization. Detection runs asynchronously without blocking the UI, allowing continuous live viewing while scanning in the background.
Architecture
Detection Pipeline
Camera Frame
↓
Bilateral Filter (noise reduction)
↓
Canny Edge Detection
↓
Morphological Dilation
↓
Contour Detection
↓
Aspect Ratio Filtering (2.0 - 5.0)
↓
Fill Ratio Validation (0.4 - 0.95)
↓
Tesseract OCR
↓
Text Validation & Logging
Each stage filters out false positives, progressively narrowing the candidate set until only valid plate regions remain.
Contour Analysis
The system identifies candidate regions by:
- Extracting contours from edge-detected frames
- Filtering by aspect ratio (width/height between 2.0 and 5.0) to match typical license plate proportions
- Validating fill ratio (percentage of contour-enclosed pixel area that contains edges) to identify rectangular structures
- Passing candidates to OCR for text validation
This approach is effective for structured, high-contrast objects like license plates while rejecting irregular shapes and low-confidence regions.
User Interface Features
Mobile-like Zoom Navigation
Users navigate the camera view using arrow keys for precise positioning:
- Arrow Keys: Move the zoom region up, down, left, right
- +/-: Adjust zoom level (1.0x to 5.0x magnification)
- 1/2: Decrease/increase brightness (-100 to +100)
- 3/4: Decrease/increase contrast (-100 to +100)
- s: Toggle scanning ON/OFF
- q: Quit (saves all settings)
Zoom level, position, brightness, and contrast are persisted in zoom_config.json between sessions, allowing users to resume from their previous configuration.
Live Telemetry Display
The status bar shows real-time information:
Detection #42 [SCANNING] | Scan: ✓ ON
Res: 1920×1080 | Zoom: 2.5x | Scope: 768×432
Brightness: +15 | Contrast: -10
This provides immediate feedback on system state and detected regions during operation.
Implementation Details
File Structure
plate-reader/
├── main_lightweight.py # Main application with GUI
├── debug/
│ ├── view_camera.py # Standalone camera viewer
│ ├── find_camera.py # Camera detection utility
│ ├── extract_regions.py # Contour extraction tool
│ └── analyze_test_frames_v2.py # Frame analysis
├── captured_plates/ # Detected plate images
├── zoom_config.json # Persistent configuration
├── plate_log.txt # Detection log
├── requirements.txt # Python dependencies
└── README.md
Detection Parameters
Key tunable parameters (adjustable in main_lightweight.py):
CAPTURE_INTERVAL = 2 # Seconds between detections
MIN_ASPECT_RATIO = 2.0 # Minimum plate width/height ratio
MAX_ASPECT_RATIO = 5.0 # Maximum plate width/height ratio
MIN_WIDTH = 30 # Minimum region width (pixels)
MIN_HEIGHT = 10 # Minimum region height (pixels)
MIN_FILL_RATIO = 0.4 # Minimum contour fill percentage
MAX_FILL_RATIO = 0.95 # Maximum fill percentage
MIN_TEXT_LENGTH = 6 # Expected plate character count
These parameters are tuned for typical license plate dimensions and should be adjusted based on regional plate standards and lighting conditions.
System Requirements
Hardware
- Primary: Raspberry Pi 4/5 (ARM64, 4GB+ RAM recommended)
- Alternative: Any Linux system with USB camera support
- Camera: USB camera with 1080p support (fallback to 640×480)
Software
- Python 3.7+
- OpenCV 4.8.1+
- PyTesseract 0.3.10+
- Tesseract OCR engine
Installation
System Dependencies (Raspberry Pi)
sudo apt update && sudo apt upgrade -y
sudo apt install -y tesseract-ocr libtesseract-dev
sudo apt install -y python3-dev python3-pip
sudo apt install -y libv4l-dev v4l-utils
Python Setup
git clone https://github.com/hamzehnasajpour/plate-reader.git
cd plate-reader
python3 -m venv venv
source venv/bin/activate
pip install -r requirements.txt
Verification
# Test imports
python3 -c "import cv2, pytesseract, numpy; print('✓ All imports OK')"
# Test camera
python3 debug/find_camera.py
# Run application
python3 main_lightweight.py
Output and Logging
Detection Log
Detected plates are logged to plate_log.txt with timestamp and OCR confidence:
Registration Number | Timestamp
==================================================
ABC1234 (95%) | 2026-05-06 22:15:42
XYZ5678 (87%) | 2026-05-06 22:16:13
Captured Images
Each detected plate is saved to captured_plates/ with bounding box visualization:
captured_plates/
├── 20260506_221542_ABC1234.jpg
├── 20260506_221613_XYZ5678.jpg
└── ...
The buffer keeps the last 50 images and automatically cleans up older captures.
Performance Considerations
For Raspberry Pi
To optimize for limited resources:
# Reduce detection frequency
CAPTURE_INTERVAL = 3 # Check every 3 seconds
# Lower resolution for faster processing
cap.set(cv2.CAP_PROP_FRAME_WIDTH, 1280)
cap.set(cv2.CAP_PROP_FRAME_HEIGHT, 720)
# Reduce stored image buffer
MAX_STORED_IMAGES = 25
Memory and CPU usage are significantly lower than ML-based approaches. Edge detection processing is fast and predictable, allowing the system to maintain real-time responsiveness even on Raspberry Pi 4 with 4GB RAM.
Troubleshooting
Camera Not Found
v4l2-ctl --list-devices
ls -la /dev/video*
Try different camera indices in the code: CAMERA_INDEX = 0, 1, 2, etc.
Tesseract Not Found
which tesseract
tesseract --version
If needed, set the path:
export TESSDATA_PREFIX=/usr/share/tesseract-ocr/4.00/tessdata
Poor Detection Quality
Adjust detection parameters based on conditions:
MIN_ASPECT_RATIO = 1.5 # More permissive
MAX_ASPECT_RATIO = 6.0
MIN_FILL_RATIO = 0.3 # Accept less filled regions
Use brightness/contrast controls (keys 1-4) to compensate for overexposed or underexposed scenes.
Design Philosophy
This project demonstrates that specialized, lightweight algorithms can be more practical than general-purpose ML for constrained environments. The trade-off is specificity—this system excels at license plate detection but is not a general-purpose object detector.
By eliminating framework overhead, the system achieves:
- Predictable behavior under varying load
- Deterministic edge detection without model variance
- Minimal dependencies simplifying deployment
- Fast startup with no initialization overhead
- Real-time operation on modest hardware
Resources
Summary
The License Plate Reader demonstrates a practical approach to real-time computer vision on embedded systems. By leveraging edge detection and contour analysis instead of neural networks, it achieves reliable, efficient operation on resource-constrained ARM64 devices while maintaining a simple, understandable codebase.
The project is lightweight, production-ready, and serves as a reference implementation for similar edge-detection-based systems in embedded environments.