Platrix β Iranian License-Plate Recognition Models
Production ONNX models for detecting and reading Iranian vehicle license plates, powering the Platrix real-time, self-hosted plate-surveillance system.
π¦ Code & full pipeline: https://github.com/AliAkrami1375/Platrix π€ This model repo: https://huggingface.co/Dibachain/Platrix
The pipeline is two-stage: a YOLO detector locates the plate in the
frame, an image-quality enhancement step cleans the crop, then a
segmentation-free CRNN + CTC reader reads the whole plate at once and returns
the standard Iranian layout DD L DDD DD (two digits Β· letter Β· three digits Β·
two-digit region), e.g. ΫΈΫ± Ω ΫΆΫ³ΫΈ Ϋ±Ϋ³.
All models run with ONNX Runtime β no PyTorch or TensorFlow needed at inference time.
π Performance
Measured on 220 real Iranian surveillance photos (grayscale gate/road cameras β the hard, real-world domain, not staged shots).
A lightweight secondary detector runs only when the primary finds nothing. It recovers plates the primary is blind to β trucks, night shots, small/far and dim plates β lifting the end-to-end read rate from 95.9% β 97.7% with no regression on the easy majority (the fallback fires on only ~2% of frames).
Training curves
The reader reaches 93% whole-plate accuracy in training and **98% on real
photos**; the detector reaches mAP@0.5 β 0.99 on held-out real frames.
π Files
| File | Role | Input | Output |
|---|---|---|---|
plate_yolo.onnx |
Plate detector (YOLOv8, primary) | 1Γ3ΓHΓW RGB, letterboxed, /255 |
1Γ5ΓN β cx,cy,w,h,conf |
plate_yolo_fallback.onnx |
Secondary detector β runs only when the primary finds nothing; recovers hard surveillance frames | 1Γ3Γ640Γ640 |
1Γ5ΓN |
ocr_crnn.onnx |
Whole-plate reader (CRNN+CTC) β recommended | 1Γ1Γ32Γ128 grayscale, /255 |
1ΓTΓ(C+1) logits (CTC, blank = last) |
ocr_crnn.labels.json |
Class list for the CRNN (index β character) | β | 32 classes |
ocr_cnn.onnx |
Per-character classifier (lightweight fallback reader) | 1Γ1Γ32Γ32 grayscale |
class logits |
ocr_cnn.labels.json |
Class list for the per-char classifier | β | β |
Character set (32 classes): digits 0β9 and the Persian plate letters
Ψ§ Ψ¨ Ψͺ Ψ« Ψ¬ Ψ Ψ― Ψ² Ψ³ Ψ΄ Ψ΅ Ψ· ΨΉ Ω Ω Ω
Ω Ω Ω ΩΎ Ϊ Ϋ.
π Getting started
Option A β Run the full Platrix system (recommended)
The complete app (web dashboard, multi-camera streaming, watchlists, API) lives in the GitHub repo. It downloads these models for you.
git clone https://github.com/AliAkrami1375/Platrix.git
cd Platrix
# fetch the models from this repo into ./models
pip install -U "huggingface_hub[cli]"
huggingface-cli download Dibachain/Platrix \
plate_yolo.onnx plate_yolo_fallback.onnx \
ocr_crnn.onnx ocr_crnn.labels.json ocr_cnn.onnx ocr_cnn.labels.json \
--local-dir models/
# then either:
docker compose up --build # Docker
# β or β
python -m venv .venv && source .venv/bin/activate
pip install -r requirements.txt && pip install -e .
platrix serve # dashboard at http://localhost:8080
Option B β Use the models directly (ONNX Runtime)
pip install onnxruntime opencv-python-headless numpy
huggingface-cli download Dibachain/Platrix \
plate_yolo.onnx plate_yolo_fallback.onnx ocr_crnn.onnx ocr_crnn.labels.json \
--local-dir models/
import json, cv2, numpy as np, onnxruntime as ort
# --- Reader (CRNN + CTC) ---
labels = json.load(open("models/ocr_crnn.labels.json", encoding="utf-8"))
blank = len(labels) # CTC blank is the last index
crnn = ort.InferenceSession("models/ocr_crnn.onnx", providers=["CPUExecutionProvider"])
def read_plate(plate_bgr):
g = cv2.cvtColor(plate_bgr, cv2.COLOR_BGR2GRAY)
g = cv2.resize(g, (128, 32)).astype(np.float32) / 255.0 # 1x1x32x128
logits = crnn.run(None, {"input": g[None, None]})[0][0] # T x (C+1)
ids, out, prev = logits.argmax(1), [], -1
for i in ids: # greedy CTC decode
if i != blank and i != prev:
out.append(labels[i])
prev = i
return "".join(out)
Two-stage flow: run plate_yolo.onnx first (standard YOLOv8 letterbox
pre-process + confidence/NMS post-process) to crop the plate, then pass the crop
to read_plate. If the primary detector returns nothing, run
plate_yolo_fallback.onnx at 640Γ640 as a second pass β it recovers the hard
surveillance frames the primary is blind to. A few real test photos ship
under img-test/ so you can try it immediately.
π§ How it works
- Detect β YOLOv8 locates the plate; weak/non-plate boxes are ignored.
- Enhance β the crop is upscaled, denoised, contrast-corrected and sharpened. The same enhancement is applied during training, so there is no train/serve mismatch β the enhancement genuinely helps instead of shifting the input.
- Read β the segmentation-free CRNN reads the whole plate in one pass with a
CTC head. It is trained on real Iranian plate-character shapes, so
look-alike glyphs (e.g. the digit
Ϋ΄vsΫΆ) are read correctly.
Splitting a plate into individual characters is fragile on real photos (shadows, motion blur, tilt, dirt); reading the entire plate at once is far more robust.
Intended use & limitations
- Intended for lawful applications such as parking management, access control, gate automation and traffic analytics.
- Optimised for standard private Iranian plates. Very low-resolution, heavily occluded or non-standard plates may reduce accuracy.
- You are responsible for complying with the privacy and surveillance laws that apply to your deployment.
Links
- Project & full pipeline: https://github.com/AliAkrami1375/Platrix
- License: MIT