ppocr-rs
Pure-Rust OCR pipeline — PP-OCRv6 (50 lingue) + PP-DocLayoutV3 + RT-DETR-L Table Cell Detection + SLANet_plus Table Structure + PP-LCNet Doc Orientation, su
ort 2.0.0-rc.9(ONNX Runtime).
Crate name: ppocr-rs. Maintained by dariofinardi
under Apache-2.0.
Overview
Pipeline OCR documentale completa, puro Rust, senza dipendenze di sistema
(no OpenCV, no Python, no PaddlePaddle). Tutto il runtime è in-process via
ort (ONNX Runtime bindings). È il backend
OCR di Pseudo-Edge.
Key features
- Page orientation correction — PP-LCNet_x1_0_doc_ori_onnx, 4 classi
(0°/90°/180°/270°). Rileva e corregge automaticamente scansioni ruotate.
Abilitato per default via
OcrOptions::default(). Richiedeset_doc_orientation_model()suOcrLite; senza modello, il flag è ignorato silenziosamente. - Text detection — DBNet (PP-OCRv6 / PP-OCRv5), multi-oriented, any aspect ratio.
- Text recognition — PPLCNetV4 + LightSVTR + CTC/NRTR multi-head.
- PP-OCRv6 (default): 50 lingue in un unico modello — CH / EN / JP + 46 Latin
(IT, FR, DE, ES, PT, e altri 41). Tre tier:
tiny(6 MB),small,medium. - PP-OCRv5 Latin (legacy): 6 EU languages, modello separato.
- PP-OCRv6 (default): 50 lingue in un unico modello — CH / EN / JP + 46 Latin
(IT, FR, DE, ES, PT, e altri 41). Tre tier:
- Per-line orientation classifier — 0°/180° per-line (PP-OCRv2 cls).
- Layout analysis — PP-DocLayoutV3, 25 classi semantiche (testo, titolo, tabella, figura, header, footer, formula, list-item, …).
- Table structure recognition — SLANet_plus (488×488), output HTML token stream
→ cell bounding box → GFM Markdown. Utilizzato con
TableStructureRecognizer. - Table cell detection — RT-DETR-L (wired + wireless), griglia geometrica → GFM Markdown.
- Word-level boxes — CTC timestep tracking → per-word bbox (highlight / hit-test).
- Auto-download modelli —
ModelHubscarica i pesi ONNX da HuggingFace al primo utilizzo e li conserva in cache locale. - Cross-platform — Windows (x86_64 + ARM64), macOS (Intel + Apple Silicon), Linux.
Performance (release build, ARM64 Snapdragon X Elite)
Misurato su 6 pagine PDF (2 documenti: paper A4 + IEEE template con tabelle e rotazioni):
| Fase | Tempo medio/pag |
|---|---|
| PDF→PNG (pdftoppm 200 DPI) | ~1400ms |
| Page orientation (PP-LCNet) | incluso in OCR |
| Layout (PP-DocLayoutV3) | ~820ms |
| OCR (PP-OCRv6 tiny det+rec) | ~1800ms |
| Table structure (SLANet_plus) | ~18ms/tabella |
| Totale pipeline/pag | ~2.6s |
Build debug: layout ~3300ms, OCR ~20000ms — speedup release ~6-12×.
Pipeline
Input image
│
▼
┌──────────────────────────┐
│ PP-LCNet doc_ori │ → rileva rotazione (0/90/180/270°)
│ (se use_doc_orientation)│ ruota l'immagine prima dell'OCR
└──────────┬───────────────┘ OcrResult.page_angle = gradi corretti
│
▼
┌─────────────────┐
│ DBNet (det) │ → text polygon boxes
└────────┬────────┘
│
▼
┌─────────────────┐
│ Cls (orient.) │ → flip lines 180° se ruotate (per-line)
└────────┬────────┘
│
▼
┌─────────────────────────────────┐
│ PPLCNetV4 + SVTR-CTC (rec) │ → stringhe + confidence (50 lingue)
└────────┬────────────────────────┘
│
├────► Plain OCR result: Vec<TextBlock>
│
│ (path layout-aware opzionale:)
▼
┌─────────────────────┐
│ PP-DocLayoutV3 │ → 25-class regions w/ reading order
└────────┬────────────┘
│
▼
┌─────────────────────┐
│ XY-Cut / sort │ → TextBlock taggati per SemanticClass
└────────┬────────────┘
│ (per regioni Table:)
├──────────────────────────────────────┐
▼ ▼
┌─────────────────────┐ ┌──────────────────────┐
│ SLANet_plus │ │ RT-DETR-L cell det │
│ (table structure) │ │ (wired / wireless) │
└────────┬────────────┘ └──────────┬───────────┘
│ HTML token stream │ cell bbox
▼ ▼
┌─────────────────────┐ ┌──────────────────────┐
│ cell_boxes + OCR │ │ derive_grid (geom.) │
│ → GFM Markdown │ │ → GFM Markdown │
└─────────────────────┘ └──────────────────────┘
Crate layout
src/
├── lib.rs — public re-exports + module overview
├── model_hub.rs — auto-download ONNX da HuggingFace (feature fetch-models)
├── ocr_lite.rs — OcrLite orchestrator (det + cls + rec + doc_orientation)
├── db_net.rs — DBNet text detection (vendored da paddle-ocr-rs)
├── crnn_net.rs — PPLCNetV4/SVTR recognition (vendored)
├── angle_net.rs — Per-line orientation classifier (vendored)
├── ocr_utils.rs — Image preprocessing, perspective warp (vendored)
├── scale_param.rs — Resize-to-multiple-of-32 (vendored)
├── ocr_result.rs — DTOs (TextBox, TextLine, OcrResult, WordBox)
├── ocr_error.rs — Error type unificato via thiserror
├── compat.rs — Shim permanente ort rc.9 vs rc.11 API
│
│ Aggiunto in questo fork (non in paddle-ocr-rs upstream):
├── layout.rs — PP-DocLayoutV3 (RT-DETR-style) layout analyzer + XY-Cut
├── cell_detection.rs — RT-DETR-L cell detector + derive_grid → GFM helper
├── table_classifier.rs — PP-LCNet: TableTypeClassifier (wired/wireless) +
│ DocOrientationClassifier (0/90/180/270°)
├── table_structure.rs — SLANet_plus / SLANeXt table structure recognizer
│ → HTML token stream → TableCellBox → GFM Markdown
├── doc_unwarp.rs — UVDoc document unwarping (reserved, enabled=false)
└── formula_rec.rs — PP-FormulaNet-plus-L LaTeX recognition (reserved)
tests/
├── compat_pp_ocrv6.rs — Smoke test compatibilità PP-OCRv6 × ort rc.9
├── pipeline_layout.rs — Layout + OCR + XY-Cut su TIFF multi-pagina (CER metric)
├── table_pipeline.rs — Table detection + SLANet_plus decode su TIFF
└── art_pipeline.rs — Pipeline end-to-end su PDF (pdftoppm + orient + layout
+ OCR + table structure); benchmarking release vs debug
examples/
├── rogito_v6.rs — OCR PP-OCRv6 su rogito notarile scansionato
└── cell_aware_reorder.rs — Pipeline completa + cell-aware reordering
Modelli — PP-OCRv6 (default)
PP-OCRv6 è disponibile su HuggingFace in tre tier. I modelli ONNX sono
scaricabili via ModelHub::ensure() oppure manualmente.
Tier disponibili
| Tier | det.onnx | rec.onnx | Totale | Lingue |
|---|---|---|---|---|
Tiny |
1.8 MB | 4.5 MB | ~6 MB | 50 |
Small |
~6 MB | ~20 MB | ~26 MB | 50 |
Medium |
62 MB | 77 MB | ~139 MB | 50 |
Per deployment su ARM64 Snapdragon X Elite si consiglia Tiny come
punto di partenza (verifica i benchmark sulla tua macchina prima di scegliere).
Architettura PP-OCRv6
Detection (PP-OCRv6_{tiny|small|medium}_det):
- Backbone:
PPLCNetV4(unified, nuovo in v6) - Neck:
RepLKPAN(large-kernel convolutions) - Head:
DBHead+DiceFocalLoss - Input:
[N, 3, H, W]dinamico (min 32×32, opt 736×736) - Normalizzazione:
mean=[0.485, 0.456, 0.406],std=[0.229, 0.224, 0.225]
Recognition (PP-OCRv6_{tiny|small|medium}_rec):
- Backbone:
PPLCNetV4 {tiny|small|medium} - Decoder:
LightSVTR+CTC + NRTRmulti-head - Input:
[1, 3, 48, W]dinamico (max W = 3200) - Dizionario:
ppocrv6_dict.txt— 18 708 caratteri unificati (50 lingue)
Rispetto a PP-OCRv5: +5.1% recognition, +4.6% detection su eval set interno.
Auto-download con ModelHub
use ;
// 1. Scarica PP-OCRv6 tiny e il classificatore orientamento pagina.
// Default cache: %LOCALAPPDATA%\ppocr-rs\models\ (Windows)
// ~/.cache/ppocr-rs/models/ (Linux/macOS)
let hub = with_default_cache?;
let paths = hub.ensure?;
let ori_paths = hub.ensure_single?;
// 2. Inizializza OcrLite con il classificatore orientamento.
let mut ocr = new;
ocr.init_models_no_angle?;
// Carica il classificatore orientamento pagina (PP-LCNet, 7 MB).
// OcrOptions::default() ha use_doc_orientation=true: se il modello
// non è caricato, il flag è ignorato silenziosamente.
let ori_clf = from_path?;
ocr.set_doc_orientation_model;
// 3. OCR con correzione orientamento automatica.
let img = open?.to_rgb8;
let result = ocr.detect_with_options?;
if result.page_angle != 0
for line in &result.text_blocks
Il download è bloccante. In GUI o runtime async, esegui su un thread separato.
Cache dir personalizzata
let hub = new;
let paths = hub.ensure?;
Feature flag richiesta
# Cargo.toml del tuo progetto
[]
= { = "...", = ["fetch-models"] }
Senza fetch-models, ensure() ritorna OcrError::ModelHubError se i file
non sono già in cache. Utile per ambienti offline o deployment in cui i modelli
sono pre-installati.
Download manuale (alternativa a ModelHub)
PowerShell
$HF = "https://huggingface.co/PaddlePaddle"
$DIR = "$env:LOCALAPPDATA\ppocr-rs\models\pp_ocrv6_tiny"
New-Item -ItemType Directory -Force $DIR | Out-Null
# Tiny (6 MB totali):
curl -L -o "$DIR\det.onnx" "$HF/PP-OCRv6_tiny_det_onnx/resolve/main/inference.onnx"
curl -L -o "$DIR\rec.onnx" "$HF/PP-OCRv6_tiny_rec_onnx/resolve/main/inference.onnx"
curl -L -o "$DIR\dict.txt" "https://raw.githubusercontent.com/PaddlePaddle/PaddleOCR/v3.7.0/ppocr/utils/dict/ppocrv6_dict.txt"
# Oppure Medium (139 MB):
$DIR_M = "$env:LOCALAPPDATA\ppocr-rs\models\pp_ocrv6_medium"
New-Item -ItemType Directory -Force $DIR_M | Out-Null
curl -L -o "$DIR_M\det.onnx" "$HF/PP-OCRv6_medium_det_onnx/resolve/main/inference.onnx"
curl -L -o "$DIR_M\rec.onnx" "$HF/PP-OCRv6_medium_rec_onnx/resolve/main/inference.onnx"
curl -L -o "$DIR_M\dict.txt" "https://raw.githubusercontent.com/PaddlePaddle/PaddleOCR/v3.7.0/ppocr/utils/dict/ppocrv6_dict.txt"
Modelli legacy (PP-OCRv5 Latin, 6 EU languages)
$ONNX = "https://github.com/jingsongliujing/OnnxOCR/raw/main/onnxocr/models/ppocrv5"
$DIR = "models\latin"
New-Item -ItemType Directory -Force $DIR | Out-Null
curl -L -o "$DIR\det.onnx" "$ONNX/det/PP-OCRv5_server_det.onnx"
curl -L -o "$DIR\rec_latin.onnx" "$ONNX/rec/latin_PP-OCRv5_mobile_rec.onnx"
curl -L -o "$DIR\dict_latin.txt" "$ONNX/rec/dict/latin_dict.txt"
Layout + Table + Orientation (PP-DocLayoutV3 + SLANet_plus + PP-LCNet)
$HF = "https://huggingface.co/PaddlePaddle"
$B2O = "https://huggingface.co/datasets/kreuzberg-dev/paddle-to-onnx/resolve/main"
New-Item -ItemType Directory -Force models\layout, models\table, models\orientation | Out-Null
# Layout
curl -L -o models\layout\PP-DocLayoutV3.onnx "$B2O/PP-DocLayoutV3.onnx"
# Table cell detection (wired + wireless)
curl -L -o models\table\RT-DETR-L_wired_table_cell_det.onnx "$B2O/RT-DETR-L_wired_table_cell_det.onnx"
curl -L -o models\table\RT-DETR-L_wireless_table_cell_det.onnx "$B2O/RT-DETR-L_wireless_table_cell_det.onnx"
# Table structure — SLANet_plus (488×488, via ModelHub oppure manuale)
curl -L -o models\table\SLANet_plus.onnx "$HF/SLANet_plus_onnx/resolve/main/inference.onnx"
curl -L -o models\table\table_structure_dict.txt "$HF/SLANet_plus_onnx/resolve/main/table_structure_dict.txt"
# Page orientation (PP-LCNet, 224×224, 7 MB — via ModelHub oppure manuale)
curl -L -o models\orientation\inference.onnx "$HF/PP-LCNet_x1_0_doc_ori_onnx/resolve/main/inference.onnx"
API
Plain OCR con orientamento automatico (PP-OCRv6)
use ;
let hub = with_default_cache?;
let ocr_paths = hub.ensure?;
let ori_paths = hub.ensure_single?;
let mut ocr = new;
ocr.init_models_no_angle?;
ocr.set_doc_orientation_model;
let img = open?.to_rgb8;
// OcrOptions::default() → use_doc_orientation=true
let result = ocr.detect_with_options?;
println!;
for line in &result.text_blocks
Layout-aware OCR
use ;
let mut layout = from_path?;
let aware = ocr.detect_with_layout?;
for blk in &aware.blocks
Table structure recognition → GFM Markdown
use ;
let mut layout = from_path?;
let recognizer = from_path_with_dict?.with_input_size; // SLANet_plus usa 488×488; SLANeXt usa 512×512
let layout_boxes = layout.analyze?;
for lb in layout_boxes.iter.filter
Table cell detection + Markdown (via RT-DETR-L geometrico)
use ;
let detector = from_path?;
let cells = detector.detect?;
let grid = derive_grid;
let gfm = grid_to_gfm;
println!;
Word-level boxes
let opts = OcrOptions ;
let result = ocr.detect_with_options?;
for blk in &result.text_blocks
OcrOptions — flag disponibili
| Flag | Default | Funzione |
|---|---|---|
use_doc_orientation |
true |
Corregge rotazione pagina (0/90/180/270°) se modello caricato |
return_word_box |
false |
Aggiunge bbox per-parola via CTC timestep tracking |
lang |
None |
Routing lingua (reserved — PP-OCRv6 copre già 50 lingue) |
use_doc_unwarping |
false |
UVDoc prospettico (reserved) |
use_seal |
false |
Timbri circolari (reserved) |
use_formula |
false |
PP-FormulaNet LaTeX (reserved) |
Smoke test PP-OCRv6 × ort rc.9
Prima di integrare PP-OCRv6 nella pipeline completa, esegui i test di
compatibilità in tests/compat_pp_ocrv6.rs.
Scaricano i modelli tiny (~6 MB) e verificano che ort rc.9 possa caricarli
e produrre output di shape corretta.
# x86_64 (pyke prebuilt DLL):
cargo test --test compat_pp_ocrv6 --features test-binaries,fetch-models -- --nocapture
# ARM64 Snapdragon X Elite:
$env:ORT_DYLIB_PATH = "C:\path\to\onnxruntime.dll"
cargo test --test compat_pp_ocrv6 --features example-dynamic,fetch-models -- --nocapture
Pipeline end-to-end su PDF (tests/art_pipeline.rs)
Test completo che processa PDF reali via pdftoppm, misura i tempi per fase
e verifica orientamento + layout + OCR + table structure:
# Release (consigliato per benchmark):
$env:ORT_DYLIB_PATH = "...\onnxruntime.dll"
cargo test --test art_pipeline --release --features example-dynamic,fetch-models -- --nocapture
Build
Default (CPU only)
ONNX Runtime non è linkato staticamente. Al primo run, ort cerca
onnxruntime.dll / libonnxruntime.so accanto al binario. Due modalità:
-
test-binaries— scarica la DLL prebuilt di pyke.io (solo x86_64): -
example-dynamic— dlopen della DLL a runtime (necessario su ARM64, dove pyke.io non pubblica binari per rc.9):$env:ORT_DYLIB_PATH = "C:\path\to\onnxruntime.dll" cargo run --example cell_aware_reorder --features example-dynamic
Feature flags
| Flag | Effetto |
|---|---|
fetch-models |
Abilita ModelHub HTTP download via ureq |
test-binaries |
ort/download-binaries — DLL x86_64 per cargo test |
example-dynamic |
ort/load-dynamic — dlopen per ARM64 / deploy offline |
directml |
DirectML (Windows GPU) |
coreml |
CoreML (macOS Apple Silicon) |
cuda |
CUDA + cuDNN |
xnnpack |
XNNPACK CPU SIMD (ARM + x86) |
QNN (Snapdragon Hexagon NPU) è escluso: benchmark su Snapdragon X Elite mostrano che PP-OCRv5/v6 non beneficia dell'NPU per shape dinamiche e post-processing CPU-bound.
Differenze rispetto a upstream paddle-ocr-rs
Fork di meibel-ai/paddle-ocr-rs
(branch ort-rc11, Apache-2.0), che deriva da
mg-chao/paddle-ocr-rs.
| Cambiamento | Motivo |
|---|---|
ort = =2.0.0-rc.9 (era rc.11) |
rc.11+ si blocca su ARM64 Snapdragon X Elite — versione target permanente del workspace. |
ndarray = 0.16 (era 0.17) |
Richiesto da ort rc.9 come transitive dep. Mix di versioni → errori IntoValueTensor. |
compat.rs shim try_extract_tensor |
Return type cambiato fra rc.9 e rc.11. Shim permanente, stabile. |
Edition 2021 (era 2024) |
Compat con il toolchain minimo del workspace. |
layout.rs aggiunto |
PP-DocLayoutV3 (25 classi) + XY-Cut reading order. Non presente upstream. |
cell_detection.rs aggiunto |
RT-DETR-L cell detector + grid derivation → GFM. Non presente upstream. |
table_classifier.rs aggiunto |
PP-LCNet wired/wireless classifier + DocOrientationClassifier (224×224). |
table_structure.rs aggiunto |
SLANet_plus / SLANeXt structure recognizer → HTML token → cell bbox → GFM. |
doc_unwarp.rs aggiunto |
UVDoc document unwarping (reserved). |
formula_rec.rs aggiunto |
PP-FormulaNet-plus-L LaTeX output (reserved). |
model_hub.rs aggiunto |
Auto-download ONNX da HuggingFace. Non presente upstream. |
OcrLite.set_doc_orientation_model() |
Integra DocOrientationClassifier nella pipeline; attivato via OcrOptions.use_doc_orientation (default true). |
OcrResult.page_angle aggiunto |
Riporta i gradi di rotazione applicati (0/90/180/270). |
Compatibility matrix
| OS | Architecture | Status |
|---|---|---|
| Windows 10 / 11 | x86_64 | ✅ Produzione (Pseudo-Edge) |
| Windows 11 | ARM64 (Snapdragon) | ✅ Produzione (load-dynamic) |
| macOS 13+ | Intel | ✅ Testato |
| macOS 13+ | Apple Silicon | ✅ Testato (CoreML EP) |
| Linux | x86_64 | ✅ Testato (Ubuntu 22.04+) |
| Linux | aarch64 | 🟡 Non testato, atteso funzionante |
Roadmap
- ModelHub: checksum SHA-256 — verifica integrità post-download.
- ModelHub: progress callback — per GUI / progress bar.
- Per-word boxes su testo verticale — skip attuale su
crop_h >= crop_w. - Modelli CJK / non-Latin — routing per-lingua quando richiesto (PP-OCRv6
copre già CH/JP nello stesso modello; serve solo il routing in
OcrOptions.lang). -
detect_with_layout+ orientamento — pre-rotazione prima dilayout.analyze()per coerenza coordinate quandouse_doc_orientation=true. - ByT5 OCR post-correction — correzione errori OCR byte-level (O↔0, doppie consonanti, spazi mancanti) tra OCR e cache.
Sorgenti dei modelli
| Modello | Sorgente | Note |
|---|---|---|
| PP-OCRv6 tiny/small/medium det+rec ONNX | PaddlePaddle/PP-OCRv6_*_onnx su HuggingFace |
Apache-2.0. Scaricati via ModelHub. |
ppocrv6_dict.txt |
PaddleOCR v3.7.0 | 18 708 caratteri. Apache-2.0. |
| PP-OCRv5 Latin (legacy) | jingsongliujing/OnnxOCR |
6 EU languages. Apache-2.0. |
| PP-DocLayoutV3 ONNX | kreuzberg-dev/paddle-to-onnx |
125 MB. Apache-2.0. |
| RT-DETR-L wired/wireless cell det | kreuzberg-dev/paddle-to-onnx |
123 MB cad. Apache-2.0. |
| SLANet_plus ONNX + dict | PaddlePaddle/SLANet_plus_onnx su HuggingFace |
488×488 input. Scaricato via ModelHub. |
| PP-LCNet_x1_0_doc_ori_onnx | PaddlePaddle/PP-LCNet_x1_0_doc_ori_onnx su HuggingFace |
7 MB, 224×224 input. Scaricato via ModelHub. |
| PP-LCNet_x1_0_table_cls_onnx | PaddlePaddle/PP-LCNet_x1_0_table_cls_onnx su HuggingFace |
7 MB, 48×192 input. |
| Per-line orientation cls | jingsongliujing/OnnxOCR |
PP-OCRv2 cls, < 1 MB. |
Credits
mg-chao/paddle-ocr-rs— Rust port originale (Apache-2.0)meibel-ai/paddle-ocr-rs— branchort-rc11(Apache-2.0)- PaddlePaddle / PaddleOCR — modelli PP-OCRv5/v6
- PaddleX — PP-DocLayoutV3, SLANet_plus, PP-LCNet classifiers
kreuzberg-dev/paddle-to-onnx— ONNX pre-convertitijingsongliujing/OnnxOCR— ONNX alternativi + dizionari
License
Apache License 2.0 — vedi LICENSE. I file vendorati conservano
i loro header di copyright originali.