Siddhant Kumar
Project A06 · Computer Vision

License Plate Recognition.

An ANPR pipeline that finds vehicle number plates in a camera feed and reads their characters — detect the plate, then recognise the text.

Advanced 16–22 hours 24 min read ANPROCRVision
Jump to source Bill of materials
License Plate Recognition — reference build illustration MCU VCC · GND · SIG · NC
Difficulty
Advanced
Build time
16–22 hours
Indicative cost
Software + camera; compute-dependent
Platform
Jetson/Pi (edge) or GPU workstation
Category
Computer Vision
Last updated
28 July 2026
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.

The dashboard of a modern car
An ANPR pipeline detects the plate, then reads its characters — the classic two-stage vision system. Photograph sourced from Wikimedia Commons — Car dashboard.jpg. Reused under the licence stated on that page; please check it before republishing.

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

SettingHow it is used
Parking / access controlEntry, billing and barrier control by plate.
TollingAutomated toll charging from plates.
Security / watchlistsFlagging vehicles of interest (lawfully).
Traffic / logisticsVehicle 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

AttributeValue
Difficulty levelAdvanced
Estimated completion time16–22 hours
Indicative build costSoftware + camera; compute-dependent
Primary disciplineComputer Vision
Reference platformJetson/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.

ComponentKey specificationQtyApprox. 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 ribbon1₹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 GPIO1₹5,800
CameraCamera positioned to capture plates (angle/light matter)1₹2,000
Edge/GPU computeJetson/Pi for edge, GPU for training1
Plate detector + OCRDetection model + OCR engine1
Format rules/datasetRegional plate formats; labelled plates if training1

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

PartSpecificationSupplyInterfaceReference
Raspberry Pi Camera Module 312 MP IMX708, autofocus, HDR, 1080p50, CSI-2 ribbon3.3 V via CSICSI-2Datasheet
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 GPIO5 V / 3 A USB-CGPIO, SPI, I²C, UART, CSI, DSIDatasheet

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.

LoadSupply railTypical current (mA)Notes
Raspberry Pi Camera Module 33.3 V via CSI250Pi 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-C1200Use 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-wide pip install by 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

LibraryWhy it is neededInstall
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.

License Plate Recognition — system block diagramFunctional block diagram of the License Plate Recognition system. InputCameratrafficDetectPlate detectorfind plateCrop/rectifylevel textReadOCRcharactersFormat checkfix errorsOutputPlate stringe.g. DL3CAB1234rightrightnone
License Plate Recognition — system block diagram

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.

License Plate Recognition — wiring schematicConnection schematic showing which controller pin drives each peripheral. Sensors / InputsControllerActuators / OutputsJetson/Pi (edge) orGPU workstation5 V / 3 A USB-CCameraTraffic imagesPlate detectorPlate boxRectify + OCRCharactersFormat checkPlate string
License Plate Recognition — wiring schematic
PeripheralPeripheral pinController pinSignal
CameraframesTraffic images
Plate detectorlocatePlate box
Rectify + OCRreadCharacters
Format checkvalidatePlate 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.
A wall-mounted CCTV surveillance camera
Rectifying the off-axis plate to a level, straight-on view is what lets OCR read it reliably. Photograph sourced from Wikimedia Commons — CCTV camera.jpg. Reused under the licence stated on that page; please check it before republishing.

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.

License Plate Recognition — architecture stackLayered architecture from hardware to user interface. Hardware layerRaspberry Pi 4 Model B (4 GB) · Raspberry Pi Camera Module 3Driver layerpython · torch · opencv · numpyApplication logicsampling loop · filtering · thresholds · state machinePresentation layerlocal display · serial console · logged output
License Plate Recognition — architecture stack

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 recognitionOCR 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

plainTwo-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

plainPerspective 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

plainFormat-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.

License Plate Recognition — firmware flowchartControl flow through the main program loop. Grab a frameDetect plate regionPlate found?Crop + rectify plateNext frameCrop + rectify plateOCR the charactersPost-process vs formatValid plate?Output plate stringDiscard/retryOutput plate stringDiscard/retry
License Plate Recognition — firmware flowchart

Assembly Instructions

Build on a breadboard first and only commit to solder once the whole system has run for an hour without a fault.

  1. Build stage one: plate detection

    Position the camera for good plate capture and detect the plate region in each frame.

  2. Rectify and read (stage two)

    Crop and rectify the plate (perspective/skew), then OCR the characters.

  3. 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.

  1. 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.py
    import 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 format
    box = 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.
  2. 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.

pythonanpr_pipeline.py
#!/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.
box = self.detector.detect_plate(frame) # STAGE 1: locateThe detector localises the plate first, so the reader is never asked to find text in a whole cluttered scene.
plate = rectify(crop(frame, box)) # deskew for OCRRectification levels the plate so OCR reads it reliably — the geometric glue between the two stages.
text = apply_format_rules(raw.upper()) # fix 0/O,1/I by positionFormat-aware post-processing corrects predictable OCR confusions and validates the result — the source of real robustness.
if n >= max(2, self.votes // 2): # consistent readVoting across frames yields a confident final read instead of trusting one noisy frame.
# Plate data identifies vehicles/people — lawful purpose, retention,The privacy/legal responsibilities of a surveillance-capable technology are made explicit in the code itself.

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.

  1. Detection

    Tune detection confidence and camera angle/light so plates are reliably found.

  2. OCR + format

    Validate OCR and format correction on real plates; encode the correct regional pattern.

  3. 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.

DatasetSizeLicenceUse here
Plate-detection datasetAnnotated platesVariesTrain/validate the detector
Plate OCR datasetChar-labelled platesVariesTrain/tune the reader
Regional plate designsPer regionPublicFormat rules / decoding
Hard-condition samplesNight/blur/angleVariesRobustness

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.
License Plate Recognition — ML pipelineFrom raw data through training to deployed inference. 1Framecamera2Detect platebox3Rectifydeskew4OCRcharacters5Format checkvalidate/fix6Plate stringoutput
License Plate Recognition — ML pipeline
Layer / stageShape or configurationPurpose
Detectorobject detector / classicalLocate the plate region
Rectifierperspective transformLevel text for OCR
OCRtext-recognition modelRead characters
Post-processformat constraintsFix look-alikes; validate
Aggregatormulti-frame votingConfident final read

Hyperparameters

HyperparameterValueWhy
Detection confidenceapp-specificMiss vs false plates
OCR confidence gateapp-specificAccept vs retry
Rectify marginsmallInclude full plate cleanly
Multi-frame votes≈ 3–5Stability 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.

MetricValueWhat it tells you
End-to-end plate accuracycondition-dependentExact correct string
Detection recallhigh (target)Plates found
OCR char accuracyhigh (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.

Accuracy by conditionANPR is strong in good conditions and degrades with angle, blur and darkness — hence multi-frame voting and format checks (illustrative). Front, daylight96Angled84Motion blur72Night / glare62
Accuracy by condition

Inference example

pythonanpr.py
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.

TestWhat you should see
Clear front plate, daylightCorrect plate string
Angled plateRectified and read (maybe lower confidence)
Look-alike chars (0/O)Fixed by format post-processing
Motion blur / nightHarder — voting/retry helps; may fail
Wrong-region formatRejected/needs region rules
Check data handlingLawful, 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.

jsonanpr-read.json
{
  "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).

A schematic of a feed-forward artificial neural network
Format-aware post-processing fixes predictable look-alike errors, turning a flaky read into a valid plate. Photograph sourced from Wikimedia Commons — Artificial neural network.svg. Reused under the licence stated on that page; please check it before republishing.

Troubleshooting: Common Errors & Fixes

Plate not found

Likely cause. Detection/placement

Fix. Tune detector; improve camera angle/light/resolution

Garbled characters

Likely cause. No rectification / poor crop

Fix. Rectify perspective; crop with margin; denoise

Look-alike errors

Likely cause. No format correction

Fix. Apply position-aware format rules

Unstable reads

Likely cause. Single-frame trust

Fix. Vote across frames; gate on confidence

Fails at night/angle

Likely cause. Hard conditions

Fix. Better capture; augmented training; accept limits

Privacy/legal issue

Likely cause. Unbounded use

Fix. Lawful purpose, retention limits, access control

The Python script crashes with "externally-managed-environment" on pip install

Likely cause. Raspberry Pi OS Bookworm marks the system Python as managed by apt, and refuses global pip installs.

Fix. Create and activate a virtual environment — python3 -m venv ~/venv && source ~/venv/bin/activate — and install there. Use --system-site-packages if you also need apt-installed modules such as picamera2.

The Pi reboots or shows a lightning-bolt icon under load

Likely cause. Under-voltage. The supply sags below 4.63 V when the CPU and peripherals ramp up.

Fix. Use the official supply for your model (5 V 3 A for Pi 4, 5 V 5 A for Pi 5) and a short, thick USB-C cable. Check with vcgencmd get_throttled — anything other than 0x0 means power problems.

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 taskset and 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 systemd with Restart=always so 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

Why two stages instead of reading the whole image?

Because a plate is tiny amid irrelevant clutter. Detecting it first means OCR only has to read a clean crop of the plate, turning an impossible whole-scene read into a manageable one — the recurring detect-then-recognise pattern of vision pipelines.

Why rectify the plate?

A plate seen off-axis is skewed, and OCR reads level text far better than slanted, perspective-distorted text. A perspective transform warps the crop straight, which often decides success — an easily underrated step.

How does format post-processing help?

Plates follow a fixed regional pattern of letters and digits, so if a position must be a digit, an "O" there is almost certainly a "0". Enforcing the format corrects OCR's predictable look-alike confusions and validates the read.

Why is it hard in the real world?

Plates are captured at angles, in motion blur, glare, rain and darkness, at varying distances and designs. No pipeline reads all of these perfectly, which is why confidence, multi-frame voting and graceful failure matter.

What are the privacy responsibilities?

ANPR identifies vehicles and, indirectly, people and their movements, so it is surveillance-capable. Responsible use means a lawful purpose, retention limits, access control, and compliance with local law — and human review before any punitive action.

References & Learning Resources

These are the primary sources worth reading in full. Manufacturer datasheets always outrank forum posts when the two disagree.

  1. Automatic number-plate recognitionReference
  2. Optical character recognitionReference
  3. Perspective transform / homographyReference
  4. Object detectionReference
  5. ANPR privacy considerationsReference