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:

  1. Stage 1: Edge detection using Canny edge detection to identify plate-like regions
  2. Stage 2: Contour analysis with aspect ratio filtering to isolate license plates
  3. 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:

  1. Extracting contours from edge-detected frames
  2. Filtering by aspect ratio (width/height between 2.0 and 5.0) to match typical license plate proportions
  3. Validating fill ratio (percentage of contour-enclosed pixel area that contains edges) to identify rectangular structures
  4. 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.