Contents — 27 sections
Project Overview
An ANPR pipeline that finds vehicle number plates in a camera feed and reads their characters — detect the plate, then recognise the text.
Automatic Number Plate Recognition (ANPR) reads vehicle registration plates from camera images automatically, and it quietly runs a huge amount of the modern world — car-park entry and billing, tolling, access control, traffic enforcement and security. This project builds an ANPR pipeline: given a camera feed, it locates each vehicle's number plate and reads the characters on it, turning a picture of traffic into a stream of plate strings. It is an excellent applied-vision project because it is a clear, real two-stage pipeline — a pattern that recurs across computer vision.
The two stages are detection then recognition. First, a detector finds where the plate is in the frame — a bounding box around the plate — because a plate is small and the rest of the scene is irrelevant clutter; locating it first means the reader only has to deal with the plate. Second, the cropped plate is passed to OCR (optical character recognition), which reads the characters — the letters and digits of the registration. Between them sit important glue steps: cropping and often rectifying the plate (correcting perspective/skew so the text is level), and post-processing the OCR output against the expected plate format to catch errors.
The value is a robust, reusable recognition pipeline and a clear lesson in staged vision systems (detect the region of interest, then analyse it). It is honest about the real-world difficulty: plates are read at angles, in motion-blur, poor light, glare and rain, at varying distances; OCR confuses look-alike characters (0/O, 1/I, 8/B); and different regions have different plate formats that constrain and help decoding. It is also candid that ANPR is a surveillance-capable technology whose use carries privacy and legal responsibilities — plate data identifies vehicles and, indirectly, people. Built with those realities in view, an ANPR pipeline is both genuinely useful and a definitive example of composing detection and recognition into a working end-to-end vision system.
What this project does
- Locates vehicle number plates in a camera feed
- Reads the plate characters via OCR
- Rectifies/crops plates before reading (perspective/skew)
- Post-processes text against the expected plate format
- Outputs plate strings from traffic imagery
- Demonstrates a detect-then-recognise pipeline
- Handles angle, blur, light and format variation (to a degree)
Real-World Applications
| Setting | How it is used |
|---|---|
| Parking / access control | Entry, billing and barrier control by plate. |
| Tolling | Automated toll charging from plates. |
| Security / watchlists | Flagging vehicles of interest (lawfully). |
| Traffic / logistics | Vehicle logging and flow analysis. |
Deployment contexts where a build of this kind earns its keep.
Features & Capabilities
- Plate detection (region of interest)
- OCR character recognition
- Perspective/skew rectification
- Format-aware post-processing
- Two-stage pipeline architecture
- Edge or GPU deployment
- Honest about conditions, OCR errors and privacy/law
Difficulty, Time & Required Skills
| Attribute | Value |
|---|---|
| Difficulty level | Advanced |
| Estimated completion time | 16–22 hours |
| Indicative build cost | Software + camera; compute-dependent |
| Primary discipline | Computer Vision |
| Reference platform | Jetson/Pi (edge) or GPU workstation |
Skills you should have (or will pick up)
- Object detection for a specific region (plate)
- OCR and text post-processing
- Perspective rectification of a cropped region
- Format-constrained decoding
- Composing a two-stage vision pipeline
Bill of Materials
Every part below is commonly available from Indian and international hobby-electronics suppliers. Prices are indicative 2026 retail figures in Indian rupees and will drift — treat them as a budgeting guide, not a quotation.
| Component | Key specification | Qty | Approx. cost |
|---|---|---|---|
| Raspberry Pi Camera Module 3 Pi 5 uses a narrower 22-pin CSI cable — the old 15-pin ribbon will not fit. | 12 MP IMX708, autofocus, HDR, 1080p50, CSI-2 ribbon | 1 | ₹2,600 |
| Raspberry Pi 4 Model B (4 GB) Use an official 5 V 3 A supply — brown-outs from phone chargers corrupt SD cards. | Quad-core Cortex-A72 @ 1.8 GHz, 4 GB LPDDR4, Gigabit Ethernet, Wi-Fi 5, BT 5.0, 2× USB 3.0, 40-pin GPIO | 1 | ₹5,800 |
| Camera | Camera positioned to capture plates (angle/light matter) | 1 | ₹2,000 |
| Edge/GPU compute | Jetson/Pi for edge, GPU for training | 1 | — |
| Plate detector + OCR | Detection model + OCR engine | 1 | — |
| Format rules/dataset | Regional plate formats; labelled plates if training | 1 | — |
Estimated total: ₹10,400, excluding tools, shipping and consumables.
Tools and consumables
- Soldering iron (temperature controlled, 350 °C) with 0.8 mm 60/40 or lead-free solder
- Digital multimeter — continuity, DC volts and current ranges
- Wire strippers, flush cutters and a small set of precision screwdrivers
- Heat-shrink tubing and a heat gun (or a lighter, carefully)
- A laptop with a USB port and the toolchain listed above
Hardware Specifications
| Part | Specification | Supply | Interface | Reference |
|---|---|---|---|---|
| Raspberry Pi Camera Module 3 | 12 MP IMX708, autofocus, HDR, 1080p50, CSI-2 ribbon | 3.3 V via CSI | CSI-2 | Datasheet |
| Raspberry Pi 4 Model B (4 GB) | Quad-core Cortex-A72 @ 1.8 GHz, 4 GB LPDDR4, Gigabit Ethernet, Wi-Fi 5, BT 5.0, 2× USB 3.0, 40-pin GPIO | 5 V / 3 A USB-C | GPIO, SPI, I²C, UART, CSI, DSI | Datasheet |
Consolidated electrical and interface specifications for every active part in the build.
Power Budget & Supply Sizing
Add up the typical active current of every part, then size the supply with at least 50 % headroom so transmit bursts and motor inrush never brown out the controller.
| Load | Supply rail | Typical current (mA) | Notes |
|---|---|---|---|
| Raspberry Pi Camera Module 3 | 3.3 V via CSI | 250 | Pi 5 uses a narrower 22-pin CSI cable — the old 15-pin ribbon will not fit. |
| Raspberry Pi 4 Model B (4 GB) | 5 V / 3 A USB-C | 1200 | Use an official 5 V 3 A supply — brown-outs from phone chargers corrupt SD cards. |
Summed typical draw is 1450 mA. With a 1.5× design margin the supply should deliver at least 2200 mA continuously at the stated rail voltage.
Software Requirements & Development Environment
Reference toolchain: Python 3.11 + PyTorch / OpenCV / OCR. Anything newer normally works; anything older may lack the board definitions used here.
- Flash Raspberry Pi OS (64-bit) with Raspberry Pi Imager; pre-configure Wi-Fi, hostname and SSH in the Imager settings so the board comes up headless.
- Update first:
sudo apt update && sudo apt full-upgrade -y, then reboot. - Work inside a virtual environment —
python3 -m venv ~/venv && source ~/venv/bin/activate. Bookworm blocks system-widepip installby design. - Enable the buses you need with
sudo raspi-config→ Interface Options (I²C, SPI, Serial, Camera). - Develop over VS Code Remote-SSH so you edit on your laptop but run on the Pi.
Required libraries
| Library | Why it is needed | Install |
|---|---|---|
| Python 3.11+ | Runtime for the analysis, training and service code. | sudo apt install python3 python3-venv python3-pip |
| PyTorch 2.4+ | Model definition, autograd and GPU training. | pip install torch torchvision --index-url https://download.pytorch.org/whl/cu121 |
| OpenCV 4.10+ | Frame capture, colour conversion, drawing and classical CV operators. | pip install opencv-python |
| NumPy 1.26+ | Vectorised array maths underpinning every other library here. | pip install numpy |
Block Diagram
The block diagram shows the functional decomposition of the system — what senses, what decides, what acts, and where the data ends up.
Circuit Diagram & Wiring
The "wiring" is the pipeline data flow — a camera frame goes to plate detection, the cropped/rectified plate goes to OCR, and format-checked text is the output.
| Peripheral | Peripheral pin | Controller pin | Signal |
|---|---|---|---|
| Camera | frames | — | Traffic images |
| Plate detector | locate | — | Plate box |
| Rectify + OCR | read | — | Characters |
| Format check | validate | — | Plate string |
Wire one row at a time and tick it off — most "it does not work" reports trace back to a single swapped pair.
Wiring explanation
- Position the camera to capture plates with good angle, light and resolution.
- Stage 1: detect the plate region in the frame.
- Crop and rectify the plate (correct perspective/skew) before reading.
- Stage 2: OCR the plate; post-process against the expected format.
- Plate data is identifying — handle it lawfully and securely.
System Architecture
Read the stack from the bottom up: physical hardware, the firmware that drives it, the transport that moves data off the device, and the software a human actually looks at.
Working Principle
ANPR is the archetypal two-stage vision pipeline, and its structure is its main lesson: you almost never read text (or analyse anything fine-grained) straight from a full scene — you first find the region of interest, then analyse just that region. A number plate occupies a tiny fraction of a traffic image, surrounded by irrelevant road, bodywork and background. Trying to OCR the whole frame is hopeless; locating the plate first turns an impossible problem into a manageable one. This detect-then-recognise decomposition recurs everywhere in vision (find the face then recognise it; find the document then read it), which is why ANPR is such a clean teaching example.
Stage one is detection. A detector (a trained object detector, or classical techniques exploiting a plate's rectangular shape and high-contrast text) finds the plate and returns a bounding box. Its job is purely localisation — where is the plate — not reading. Doing this well under real conditions (varying distance, angle, and lighting) is half the battle, because everything downstream depends on getting a clean crop of the plate.
Between the stages sits crucial glue: crop the plate and rectify it. A plate viewed off-axis appears as a skewed quadrilateral, and OCR reads level text far better than slanted, perspective-distorted text, so a perspective transform warps the crop back to a straight-on, rectangular view. This preprocessing — deskewing, normalising size and contrast — often makes the difference between the reader succeeding and failing, and it is easy to underrate.
Stage two is recognition — OCR reads the characters from the rectified plate — followed by format-aware post-processing, which is where real robustness comes from. Raw OCR makes predictable mistakes: it confuses visually similar characters (0/O, 1/I/l, 8/B, 5/S, 2/Z), and it may misfire on dirt, screws or borders. But plates are not arbitrary strings — each region has a defined format (a pattern of letters and digits in fixed positions), and enforcing that format corrects many errors: if a position must be a digit, an "O" there is almost certainly a "0". Validating and correcting the OCR output against the expected format is what lifts a flaky reader into a dependable one. The honest difficulties are ever-present: plates are captured at angles, in motion blur, glare, rain and darkness, at a range of distances and plate designs, and no pipeline reads them all perfectly — confidence, retries across frames, and graceful failure matter. And ANPR is surveillance-capable: plate reads identify vehicles and, by extension, people and their movements, so the technology carries genuine privacy and legal responsibilities (lawful purpose, retention limits, access control) that a responsible build must respect. As an end-to-end system, though, ANPR teaches the essential craft of composing detection, geometric correction, and recognition into a pipeline that turns raw imagery into structured, validated data.
The maths behind it
Two-stage pipeline
frame → DETECT plate box → CROP + RECTIFY → OCR chars
→ FORMAT post-process → plate string
Find the region of interest first; analyse only that region.
(The recurring pattern of staged vision systems.)
Perspective rectification
Plate seen off-axis = a skewed quadrilateral.
H = perspective_transform(plate_corners → rectangle)
rectified = warp(crop, H)
OCR reads level text far better than skewed text.
Format-aware correction
Plates follow a regional pattern, e.g. AA 00 A 0000.
for each position: constrain to letter OR digit
fix look-alikes by position:
digit slot: O→0, I→1, B→8, S→5, Z→2
letter slot: 0→O, 1→I, ...
Format turns a flaky OCR read into a valid plate.
Program Flowchart
The firmware is a single cooperative loop. Nothing blocks for long, so networking, sensing and the user interface all stay responsive.
Assembly Instructions
Build on a breadboard first and only commit to solder once the whole system has run for an hour without a fault.
Build stage one: plate detection
Position the camera for good plate capture and detect the plate region in each frame.
Rectify and read (stage two)
Crop and rectify the plate (perspective/skew), then OCR the characters.
Post-process and stabilise
Correct and validate the OCR against the expected format, and vote across frames for a confident final read.
Step-by-Step Implementation Guide
Work through these in order. Each step ends in something you can observe, so a failure is always localised to the step you just finished.
Detect, rectify, read, validate
Run the two-stage pipeline: locate the plate, rectify and OCR it, then correct and validate against the plate format.
pythonpipeline.pyimport re PLATE_RE = re.compile(r"^[A-Z]{2}\d{2}[A-Z]{1,2}\d{4}$") def anpr(frame, detector, ocr): box = detector.detect_plate(frame) # STAGE 1: where is the plate if box is None: return None plate_img = rectify(crop(frame, box)) # deskew so OCR reads level text raw = ocr.read(plate_img) # STAGE 2: read characters text = apply_format_rules(raw.upper()) # fix 0/O, 1/I by position return text if PLATE_RE.match(text) else None # validate vs formatbox = detector.detect_plate(frame) # STAGE 1: where is the plateDetection localises the tiny plate in the cluttered frame, so the reader only ever sees the plate — the essence of the two-stage design.plate_img = rectify(crop(frame, box)) # deskew so OCR reads level textRectifying the crop corrects perspective/skew, which often decides whether OCR succeeds — the underrated glue step.raw = ocr.read(plate_img) # STAGE 2: read charactersOCR reads the characters from the clean, level plate image.text = apply_format_rules(raw.upper()) # fix 0/O, 1/I by positionFormat-aware correction fixes predictable look-alike errors using the plate's fixed letter/digit pattern — the robustness step.Vote across frames and output
Aggregate reads across several frames for a confident plate string, and output it (with confidence) for the application.
Complete Source Code
The listing below is complete and compiles as written — there are no elided sections. Read the annotations under each block before you upload it.
#!/usr/bin/env python3
"""
License Plate Recognition (ANPR) — detect-then-recognise pipeline
STAGE 1 detects the plate region; the crop is rectified (deskewed);
STAGE 2 OCRs the characters; format-aware post-processing fixes
look-alike errors and validates the plate. Multi-frame voting stabilises
reads. ANPR is surveillance-capable — use lawfully and securely.
"""
import re
from collections import Counter
PLATE_RE = re.compile(r"^[A-Z]{2}\d{2}[A-Z]{1,2}\d{4}$") # example region format
class ANPR:
def __init__(self, detector, ocr, votes=5):
self.detector = detector; self.ocr = ocr
self.votes = votes; self.recent = []
def read_frame(self, frame):
box = self.detector.detect_plate(frame) # STAGE 1: locate
if box is None:
return None
plate = rectify(crop(frame, box)) # deskew for OCR
raw = self.ocr.read(plate) # STAGE 2: characters
text = apply_format_rules(raw.upper()) # fix 0/O,1/I by position
return text if PLATE_RE.match(text) else None # validate vs format
def process(self, frame):
r = self.read_frame(frame)
if r:
self.recent.append(r)
self.recent = self.recent[-self.votes:] # multi-frame window
best, n = Counter(self.recent).most_common(1)[0]
if n >= max(2, self.votes // 2): # consistent read
return best # confident plate string
return None
if __name__ == "__main__":
anpr = ANPR(PlateDetector(), PlateOCR())
for frame in camera():
plate = anpr.process(frame)
if plate:
handle(plate) # lawful use only
# Plate data identifies vehicles/people — lawful purpose, retention,
# access control and compliance are required.
Configuration & Calibration
Configuration steps
- Configure the plate detector, OCR engine and camera placement.
- Configure rectification, the regional plate format and correction rules.
- Configure multi-frame voting and confidence gates.
- Configure lawful use, retention limits and access control.
Calibration procedure
An uncalibrated sensor produces confident, precise, wrong numbers. Do this once per physical unit and record the constants.
Detection
Tune detection confidence and camera angle/light so plates are reliably found.
OCR + format
Validate OCR and format correction on real plates; encode the correct regional pattern.
Robustness
Test across angle, blur, night and weather; use voting to stabilise.
Dataset, Model & Training
Dataset
Plate detection is trained/uses a detector on plate-annotated images; OCR uses a text-recognition model, ideally tuned to plate fonts and regional formats.
Real-condition data (angles, blur, night, weather) and regional plate designs matter for accuracy.
| Dataset | Size | Licence | Use here |
|---|---|---|---|
| Plate-detection dataset | Annotated plates | Varies | Train/validate the detector |
| Plate OCR dataset | Char-labelled plates | Varies | Train/tune the reader |
| Regional plate designs | Per region | Public | Format rules / decoding |
| Hard-condition samples | Night/blur/angle | Varies | Robustness |
Data preprocessing
- Detect the plate; crop with margin; rectify perspective/skew.
- Normalise size, contrast; denoise for OCR.
- For training, augment for angle, blur, lighting, occlusion.
| Layer / stage | Shape or configuration | Purpose |
|---|---|---|
| Detector | object detector / classical | Locate the plate region |
| Rectifier | perspective transform | Level text for OCR |
| OCR | text-recognition model | Read characters |
| Post-process | format constraints | Fix look-alikes; validate |
| Aggregator | multi-frame voting | Confident final read |
Hyperparameters
| Hyperparameter | Value | Why |
|---|---|---|
| Detection confidence | app-specific | Miss vs false plates |
| OCR confidence gate | app-specific | Accept vs retry |
| Rectify margin | small | Include full plate cleanly |
| Multi-frame votes | ≈ 3–5 | Stability vs latency |
Training process
- Train/tune the detector on plate images and the OCR on plate fonts/formats.
- Augment heavily for real conditions; validate on held-out hard cases.
- Encode regional format rules for post-processing.
Evaluation, Metrics & Deployment
End-to-end plate-read accuracy (exact string) matters most, with detection and OCR sub-metrics and robustness across conditions.
| Metric | Value | What it tells you |
|---|---|---|
| End-to-end plate accuracy | condition-dependent | Exact correct string |
| Detection recall | high (target) | Plates found |
| OCR char accuracy | high (target) | Characters read |
| Robustness (night/angle) | lower (honest) | Hard conditions |
Figures from the reference training run described above — reproduce them before trusting your own changes.
Inference example
import re
PLATE_RE = re.compile(r"^[A-Z]{2}\d{2}[A-Z]{1,2}\d{4}$") # example format
def read_plate(frame, detector, ocr):
box = detector.detect_plate(frame) # STAGE 1: locate
if box is None:
return None
plate = rectify(crop(frame, box)) # deskew for OCR
raw = ocr.read(plate) # STAGE 2: characters
text = format_correct(raw) # fix 0/O, 1/I by position
if PLATE_RE.match(text): # validate vs format
return text # confident plate string
return None # reject / retry next frame
def format_correct(s):
# apply position-aware look-alike fixes toward the expected pattern
return apply_format_rules(s.upper())
# NOTE: plate reads identify vehicles/people — handle lawfully & securely.
Testing Procedure & Expected Output
Test from the bottom up. Confirm power, then each sensor in isolation, then the integrated loop — the first failing step tells you exactly where to look.
| Test | What you should see |
|---|---|
| Clear front plate, daylight | Correct plate string |
| Angled plate | Rectified and read (maybe lower confidence) |
| Look-alike chars (0/O) | Fixed by format post-processing |
| Motion blur / night | Harder — voting/retry helps; may fail |
| Wrong-region format | Rejected/needs region rules |
| Check data handling | Lawful, retention-limited, access-controlled |
Bench-test checklist. If a row fails, stop and fix it before moving on.
Expected output
Validated plate strings from the camera feed, stabilised across frames.
{
"plate": "DL3CAB1234",
"confidence": 0.93,
"frames_agreed": 4,
"rectified": true,
"format_valid": true
}
A plate read consistently across four frames, rectified and format-validated — the two-stage pipeline turning imagery into structured, checked data (handled lawfully).
Troubleshooting: Common Errors & Fixes
Performance Optimisation
- Detect the plate first; OCR only the clean, rectified crop.
- Rectify perspective/skew — it often decides OCR success.
- Use format post-processing to fix predictable errors.
- Vote across frames for confident, stable reads.
- Pin the hot loop to one core with
tasksetand leave the others free for the OS. - Prefer MJPEG over raw YUY2 when capturing from USB cameras — the decode cost is far lower than the USB bandwidth cost.
- Log to a tmpfs RAM disk and flush to the SD card once a minute; per-sample SD writes are what kills cards.
- Run the service under
systemdwithRestart=alwaysso a crash never means a dead deployment. - Profile before optimising — print
micros()deltas around each stage and fix the slowest one first.
Safety Precautions
- ANPR is surveillance-capable — deploy only for a lawful purpose, with retention limits and access control, complying with local law.
- Plate data identifies vehicles and, indirectly, people — secure and minimise it.
- No pipeline is perfect — do not use raw reads for punitive/automated action without human review.
- Be transparent about deployment where required.
- Wear eye protection when soldering or cutting, and solder in a ventilated space — rosin flux fumes are a respiratory irritant.
- Power the circuit through a bench supply with a current limit while you are testing. A 300 mA limit turns a wiring mistake into a beep instead of a dead board.
- Disconnect power before changing any wiring. Hot-plugging a sensor onto a live bus is the fastest way to lose a controller.
Maintenance
- Retune detector/OCR for new plate designs and conditions.
- Update regional format rules as they change.
- Monitor accuracy and review misreads.
- Audit data handling for privacy/legal compliance.
- Re-check every screw terminal and header after the first week — thermal cycling loosens connections that felt tight on day one.
- Recalibrate at the interval given in the calibration section, and keep the constants in a text file next to the firmware — not only in flash.
- Keep a short logbook of firmware versions and what changed. Six months later you will not remember why that constant is 1.083.
Future Improvements & Upgrades
A working v1 is a platform, not a finish line. These are the upgrades that add the most capability for the least rework.
- Add vehicle make/model/colour attributes.
- Add end-to-end trainable detection+recognition.
- Add region auto-detection and multi-format support.
- Add better night/IR capture and deblurring.
- Design a proper PCB. Once the breadboard version has run for a month, moving to a two-layer board removes the intermittent-contact failures that dominate prototype faults.
- Add connectivity — an ESP32 and an MQTT publish turn a local gadget into something you can graph, alert on and analyse over months.
- Add persistent local storage (microSD or the on-chip flash) so a network outage does not create a hole in your data.
- Move configuration out of the source: a captive-portal setup page or a JSON config file makes the build reusable without a recompile.
- Add a battery and solar option so the unit survives a power cut and can be sited away from a socket.
- Write a small test harness that feeds synthetic sensor values through the decision logic, so you can validate thresholds without physically triggering the event.
Frequently Asked Questions
References & Learning Resources
These are the primary sources worth reading in full. Manufacturer datasheets always outrank forum posts when the two disagree.
- Automatic number-plate recognitionReference
- Optical character recognitionReference
- Perspective transform / homographyReference
- Object detectionReference
- ANPR privacy considerationsReference