Back to projects
ALPR 5 weeks 2 pers.

Automatic License Plate Recognition

Full ALPR system built without Deep Learning (Classical Computer Vision + Machine Learning), featuring rigorous Python vs C++ performance benchmarks.

Python C++17 OpenCV Random Forest CMake Google Test
Aperçu du projet ALPR

Context & Problem Statement

Automatic License Plate Recognition (ALPR) is a core building block of intelligent transportation systems and automated road access control. The goal of this project was to analyze high-resolution image streams and locate vehicle license plates with high precision, regardless of vehicle type or background environment.

Technical Challenges


Workflow Overview

To solve this problem robustly, we structured the processing according to a 4-stage sequential pipeline:

01 Raw Image Full HD 1080p
02 Preprocessing Resize & Grayscale
03 ROI Generation Filters & Morpho
04 HOG Features 293D Vector
05 Classification Random Forest
06 Result Located Plate
  1. Preprocessing: Scale normalization and channel reduction.
  2. Candidate Generation (ROI): Multi-scale extraction of rectangular regions with a high probability of containing a plate.
  3. Feature Extraction: Numerical representation of each candidate using a vector of geometric descriptors and HOG.
  4. Classification & Validation: Evaluation of candidate regions by a Machine Learning model and selection of the top candidate.

Step-by-Step Algorithmic Pipeline

1. Image Preprocessing

The first step prepares the image to stabilize object dimensions and accelerate subsequent calculations:

1. Original Image 2. Preprocessed Image
Original Preprocessed

2. Candidate Detection & Extraction (ROI)

To avoid naively scanning the entire image, which would be extremely slow, we generate a small set of candidate Regions of Interest (ROI) by combining 3 complementary branches:

  1. Main Branch (Morphology + Sobel): Local contrast amplification of characters (MMLPF filter), followed by a vertical Sobel filter, Otsu thresholding, and morphological closing ($17 \times 3$).
  2. Oversampled Branch ($2\times$): Application of the same pipeline on the image enlarged to $2\times$ its size to capture very small or distant plates.
  3. Canny Branch: Adaptive edge detection based on the median image intensity.

Independent Geometric Filtering: Each branch immediately filters its own candidate regions based on their area ($50 \text{ px} \le \text{area} \le 40\,000 \text{ px}$) and aspect ratio ($1.0 \le w/h \le 8.0$) to eliminate obvious false candidates right away.

Steps of the Main Branch:

1: MMLPF Filter 2: Vertical Sobel
MMLPF Base Sobel Base
3: Otsu Thresholding 4: Morphological Closing
Otsu Base Fermeture Base
5: Extracted Candidates
CCA Base

3-Branch Fusion & NMS Deduplication

Similarly, the Oversampled Branch ($2\times$) and the Canny Branch extract and filter their own candidates. All candidate regions retained across all 3 branches are merged and deduplicated via Non-Maximum Suppression (NMS based on IoU) to eliminate overlaps.

Fused candidate regions (3 branches) deduplicated via NMS
Fused candidate regions (3 branches) deduplicated via NMS

3. Digital Signature (HOG & Geometry Descriptors)

Each retained candidate patch is cropped and resized to a fixed size of $64 \times 32$ pixels, then converted into a vector of 293 features:


4. Classification & Final Localization

Each feature vector is fed to a Random Forest classifier (cv::ml::RTrees). The model assigns a confidence score to each candidate, and the region with the highest positive score is selected as the final plate.

Final license plate detection result
Final license plate detection result

Engineering Approach: From Python Prototype to C++17

The project evolved through two major phases:

1. Python Prototype (PoC)

Allowed rapid prototyping of the pipeline using scikit-learn and OpenCV Python, validating morphological filters, and training the classification model.

2. C++17 Port & Custom MyCV Module

To achieve the required real-time performance and move away from opaque OpenCV abstractions, we rewrote the pipeline in C++17 and designed a custom MyCV module containing:


Results & Benchmarks

Rigorous evaluation performed on the UFPR-ALPR test dataset (1,440 test images):

Inference Performance

Implementation True Positives (TP) Precision Recall F1-Score Average Time
Python 955 0.8580 0.6632 0.7481 468.50 ms
C++17 (Optimized) 963 0.8629 0.6687 0.7535 402.28 ms
C++17 (MyCV) 955 0.8504 0.6632 0.7452 583.67 ms

Key Benchmark Takeaways