---
license: mit
language:
- fa
library_name: onnxruntime
pipeline_tag: image-to-text
tags:
- alpr
- license-plate-recognition
- ocr
- persian
- farsi
- iranian-license-plate
- crnn
- ctc
- yolo
- onnx
- object-detection
---
# Platrix — Iranian License-Plate Recognition Models
Production ONNX models for detecting and reading **Iranian vehicle license
plates**, powering the [**Platrix**](https://github.com/AliAkrami1375/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.
```bash
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)
```bash
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/
```
```python
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/`](./tree/main/img-test) so you can try it immediately.
---
## 🧠 How it works
1. **Detect** — YOLOv8 locates the plate; weak/non-plate boxes are ignored.
2. **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.
3. **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