A stress-test benchmark of 10 OCR systems on Hindi / Devanagari — synthetic + real scans, with a ByT5 post-corrector
Ten OCR systems — classical, open VLMs, specialised OCR-VLMs, and frontier closed LLMs — read the same Hindi text. On clean rendered images they are indistinguishable (chrF++ 91–98). On real printed scans nine of ten collapse, the field spreads across a 76-point range, and the English OCR ranking does not transfer to Devanagari.
[!IMPORTANT]
Scope of the claims — read this. This is an evaluation / benchmark, run honestly with its limits stated. The controlled set is 100 FLORES-Hindi sentences rendered with 5 fonts; the real set is 300 word/short-phrase printed scans (Sanskrit-typeset historical print), used as a real-image robustness probe, not a full document benchmark. The word-level format disadvantages page-oriented models — we report this confound and show the synthetic-to-real gap survives it. Numbers for the closed APIs (Gemini, Claude, GPT-5.5, Mistral) reflect the model versions at eval time and will drift. CER is code-point, not grapheme-cluster, level.
| Headline finding | Evidence |
|---|---|
| Synthetic text hides everything | all 10 within chrF++ 91–98 on clean |
| Real scans collapse most systems | EasyOCR chrF++ 93.6 → 58.3 |
| English ranking ≠ Indic ranking | GPT-5.5 58.5 (ties EasyOCR); open Qwen3-VL-8B 75.2 beats it |
| olmOCR-Bench's own model falls | olmOCR-7B → chrF++ 40.5 on real |
Real printed Devanagari (300 scans), sorted by chrF++. We report median CER and catastrophic-rate alongside the mean, because rare repetition blow-ups wreck the mean (see Method).
| Model | Family | mean CER ↓ | median CER ↓ | cat % ↓ | chrF++ ↑ |
|---|---|---|---|---|---|
| Gemini 2.5 Flash | frontier | 4.4 | 0.0 | 0.7 | 86.3 |
| Claude Opus 4.7 | frontier | 5.1 | 0.0 | 0.7 | 82.2 |
| Mistral OCR | frontier | 90.7 | 0.0 | 8.0 | 77.6 |
| Qwen3-VL-8B | open | 9.3 | 0.0 | 3.3 | 75.2 |
| GPT-5.5 | frontier | 18.4 | 12.5 | 7.7 | 58.5 |
| EasyOCR | classical | 34.3 | 16.7 | 26.3 | 58.3 |
| Qwen2.5-VL-3B | open | 26.4 | 20.3 | 14.7 | 45.4 |
| olmOCR-7B | open | 59.1 | 20.0 | 12.0 | 40.5 |
| Unlimited-OCR | specialised | 359.8 | 50.0 | 46.7 | 24.7 |
| DeepSeek-OCR | specialised | 112.0 | 100.0 | 89.0 | 10.4 |
[!NOTE] On clean rendered text all ten cluster at chrF++ 91–98 — synthetic-only benchmarks do not separate these systems. The differences only appear under degradation and on real scans. Full per-condition tables and figures are in the paper and
results/.

Benchmark. FLORES-Hindi → images (5 Devanagari fonts) under 4 conditions: clean, Gaussian blur, sensor noise, low-DPI. Plus 300 real printed scans (Sanskrit-OCR-Typed). All scoring is Unicode NFC-normalised; metrics are CER, WER, and chrF++.
Robustness methodology. Corpus mean CER is dominated by a few catastrophic samples: under blur, DeepSeek-OCR has the best median (1.5) yet a mean of 73.7, because ~2% of outputs enter a repetition loop up to 71× the reference length. We therefore report median + catastrophic-rate, not just the mean.
Post-correction (ByT5). A byte-level corrector fine-tuned on an engine's own real OCR-noise distribution (6,000 pairs), with inference-time chunking to ≤90 chars. It improves EasyOCR (chrF++ +1.2 to +1.5 in every condition) but does not transfer to other engines — post-correction must be distribution-matched.
| Family | Models |
|---|---|
| Classical | EasyOCR (Hindi+English) |
| Open VLMs | Qwen2.5-VL-3B, Qwen3-VL-8B, olmOCR-7B |
| Specialised OCR-VLMs | DeepSeek-OCR, Unlimited-OCR |
| Frontier closed (API) | Gemini 2.5 Flash, Claude Opus 4.7, GPT-5.5, Mistral OCR |
[!NOTE] Omitted (honestly): PaddleOCR and GOT-OCR2 would not run reliably in our environment (PaddlePaddle segfault on Amazon Linux 2023; processor-instantiation error); LlamaParse returned non-Devanagari Latin output on every image. All three are excluded from the quantitative tables rather than reported with unreliable numbers.
.
├── scripts/ # full pipeline (run from repo root)
│ ├── render.py # FLORES-Hindi -> images (multi-font, 4 conditions)
│ ├── make_real.py # build the 300-image real printed-Devanagari set
│ ├── runner.py # unified runner for all engines
│ ├── robust.py # mean-vs-median / catastrophic analysis
│ ├── taxonomy.py # Devanagari error taxonomy
│ ├── report*.py # aggregate results
│ ├── pc_*.py # post-corrector: data, train, eval
│ └── plot_*.py # figures
├── results/ # results_matrix.json, pc_results.json (the numbers)
├── figs/ # paper figures
├── paper.tex · paper.pdf
└── README.md · SUBMISSION_PLAN.md · LICENSE
Run any engine on a folder of images and score it against ground truth:
# score EasyOCR on the clean synthetic split
python scripts/runner.py --model easyocr --imgdir images/clean --tag clean
# score a frontier API model on the real scans (keys in ~/.ocr_keys)
python scripts/runner.py --model gemini --imgdir real/images --gt real/gt.json --tag real
API keys (closed models) are read from ~/.ocr_keys:
GEMINI_API_KEY=...
ANTHROPIC_API_KEY=...
OPENAI_API_KEY=...
MISTRAL_API_KEY=...
[!TIP] Run all commands from the repo root. Local models need a CUDA GPU (a single 24 GB A10G suffices — one model resident at a time). Three uv venvs are used to satisfy conflicting
transformerspins: EasyOCR (4.40), Qwen/Unlimited/Qwen3-VL/olmOCR + APIs (4.57.1), DeepSeek-OCR (4.46.3).
# 1. build the benchmark
python scripts/render.py -n 100 # synthetic, 4 conditions
python scripts/make_real.py # 300 real printed scans (from HF)
# 2. run engines (per model x condition)
python scripts/runner.py --model <m> --imgdir images/<cond> --tag <cond>
# 3. analyse
python scripts/report_real.py # median + catastrophic stats
python scripts/robust.py # mean vs median
python scripts/taxonomy.py --cond clean # error taxonomy
# 4. post-corrector
python scripts/pc_data_real.py -n 6000 # real OCR-noise pairs
python scripts/pc_train.py --data out/pc_real.jsonl --save pc_model_real
python scripts/pc_eval.py easyocr@clean easyocr@blur # before vs after
chrF measures n-gram overlap with references, not human quality, and our findings are stated with their caveats:
Takeaway: synthetic-only Devanagari benchmarks overstate quality; real evaluation is indispensable; and the best "general" OCR for English may be far from the best for Indic.
@misc{singh2026devanagari,
title = {Can OCR-VLMs Read Devanagari? A Stress-Test Benchmark and Post-Correction Study},
author = {Aditya Pratap Singh},
year = {2026},
eprint = {arXiv:XXXX.XXXXX}, % update once arXiv assigns the ID
archivePrefix = {arXiv},
primaryClass = {cs.CL}
}
MIT © Aditya Pratap Singh
If this is useful, consider starring the repo ⭐ and citing the paper.
3 commits
Python
71.6%
TeX
26.4%
Shell
2.0%
A stress-test benchmark of 10 OCR systems on Hindi / Devanagari — synthetic + real scans, with a ByT5 post-corrector
Ten OCR systems — classical, open VLMs, specialised OCR-VLMs, and frontier closed LLMs — read the same Hindi text. On clean rendered images they are indistinguishable (chrF++ 91–98). On real printed scans nine of ten collapse, the field spreads across a 76-point range, and the English OCR ranking does not transfer to Devanagari.
[!IMPORTANT]
Scope of the claims — read this. This is an evaluation / benchmark, run honestly with its limits stated. The controlled set is 100 FLORES-Hindi sentences rendered with 5 fonts; the real set is 300 word/short-phrase printed scans (Sanskrit-typeset historical print), used as a real-image robustness probe, not a full document benchmark. The word-level format disadvantages page-oriented models — we report this confound and show the synthetic-to-real gap survives it. Numbers for the closed APIs (Gemini, Claude, GPT-5.5, Mistral) reflect the model versions at eval time and will drift. CER is code-point, not grapheme-cluster, level.
| Headline finding | Evidence |
|---|---|
| Synthetic text hides everything | all 10 within chrF++ 91–98 on clean |
| Real scans collapse most systems | EasyOCR chrF++ 93.6 → 58.3 |
| English ranking ≠ Indic ranking | GPT-5.5 58.5 (ties EasyOCR); open Qwen3-VL-8B 75.2 beats it |
| olmOCR-Bench's own model falls | olmOCR-7B → chrF++ 40.5 on real |
Real printed Devanagari (300 scans), sorted by chrF++. We report median CER and catastrophic-rate alongside the mean, because rare repetition blow-ups wreck the mean (see Method).
| Model | Family | mean CER ↓ | median CER ↓ | cat % ↓ | chrF++ ↑ |
|---|---|---|---|---|---|
| Gemini 2.5 Flash | frontier | 4.4 | 0.0 | 0.7 | 86.3 |
| Claude Opus 4.7 | frontier | 5.1 | 0.0 | 0.7 | 82.2 |
| Mistral OCR | frontier | 90.7 | 0.0 | 8.0 | 77.6 |
| Qwen3-VL-8B | open | 9.3 | 0.0 | 3.3 | 75.2 |
| GPT-5.5 | frontier | 18.4 | 12.5 | 7.7 | 58.5 |
| EasyOCR | classical | 34.3 | 16.7 | 26.3 | 58.3 |
| Qwen2.5-VL-3B | open | 26.4 | 20.3 | 14.7 | 45.4 |
| olmOCR-7B | open | 59.1 | 20.0 | 12.0 | 40.5 |
| Unlimited-OCR | specialised | 359.8 | 50.0 | 46.7 | 24.7 |
| DeepSeek-OCR | specialised | 112.0 | 100.0 | 89.0 | 10.4 |
[!NOTE] On clean rendered text all ten cluster at chrF++ 91–98 — synthetic-only benchmarks do not separate these systems. The differences only appear under degradation and on real scans. Full per-condition tables and figures are in the paper and
results/.

Benchmark. FLORES-Hindi → images (5 Devanagari fonts) under 4 conditions: clean, Gaussian blur, sensor noise, low-DPI. Plus 300 real printed scans (Sanskrit-OCR-Typed). All scoring is Unicode NFC-normalised; metrics are CER, WER, and chrF++.
Robustness methodology. Corpus mean CER is dominated by a few catastrophic samples: under blur, DeepSeek-OCR has the best median (1.5) yet a mean of 73.7, because ~2% of outputs enter a repetition loop up to 71× the reference length. We therefore report median + catastrophic-rate, not just the mean.
Post-correction (ByT5). A byte-level corrector fine-tuned on an engine's own real OCR-noise distribution (6,000 pairs), with inference-time chunking to ≤90 chars. It improves EasyOCR (chrF++ +1.2 to +1.5 in every condition) but does not transfer to other engines — post-correction must be distribution-matched.
| Family | Models |
|---|---|
| Classical | EasyOCR (Hindi+English) |
| Open VLMs | Qwen2.5-VL-3B, Qwen3-VL-8B, olmOCR-7B |
| Specialised OCR-VLMs | DeepSeek-OCR, Unlimited-OCR |
| Frontier closed (API) | Gemini 2.5 Flash, Claude Opus 4.7, GPT-5.5, Mistral OCR |
[!NOTE] Omitted (honestly): PaddleOCR and GOT-OCR2 would not run reliably in our environment (PaddlePaddle segfault on Amazon Linux 2023; processor-instantiation error); LlamaParse returned non-Devanagari Latin output on every image. All three are excluded from the quantitative tables rather than reported with unreliable numbers.
.
├── scripts/ # full pipeline (run from repo root)
│ ├── render.py # FLORES-Hindi -> images (multi-font, 4 conditions)
│ ├── make_real.py # build the 300-image real printed-Devanagari set
│ ├── runner.py # unified runner for all engines
│ ├── robust.py # mean-vs-median / catastrophic analysis
│ ├── taxonomy.py # Devanagari error taxonomy
│ ├── report*.py # aggregate results
│ ├── pc_*.py # post-corrector: data, train, eval
│ └── plot_*.py # figures
├── results/ # results_matrix.json, pc_results.json (the numbers)
├── figs/ # paper figures
├── paper.tex · paper.pdf
└── README.md · SUBMISSION_PLAN.md · LICENSE
Run any engine on a folder of images and score it against ground truth:
# score EasyOCR on the clean synthetic split
python scripts/runner.py --model easyocr --imgdir images/clean --tag clean
# score a frontier API model on the real scans (keys in ~/.ocr_keys)
python scripts/runner.py --model gemini --imgdir real/images --gt real/gt.json --tag real
API keys (closed models) are read from ~/.ocr_keys:
GEMINI_API_KEY=...
ANTHROPIC_API_KEY=...
OPENAI_API_KEY=...
MISTRAL_API_KEY=...
[!TIP] Run all commands from the repo root. Local models need a CUDA GPU (a single 24 GB A10G suffices — one model resident at a time). Three uv venvs are used to satisfy conflicting
transformerspins: EasyOCR (4.40), Qwen/Unlimited/Qwen3-VL/olmOCR + APIs (4.57.1), DeepSeek-OCR (4.46.3).
# 1. build the benchmark
python scripts/render.py -n 100 # synthetic, 4 conditions
python scripts/make_real.py # 300 real printed scans (from HF)
# 2. run engines (per model x condition)
python scripts/runner.py --model <m> --imgdir images/<cond> --tag <cond>
# 3. analyse
python scripts/report_real.py # median + catastrophic stats
python scripts/robust.py # mean vs median
python scripts/taxonomy.py --cond clean # error taxonomy
# 4. post-corrector
python scripts/pc_data_real.py -n 6000 # real OCR-noise pairs
python scripts/pc_train.py --data out/pc_real.jsonl --save pc_model_real
python scripts/pc_eval.py easyocr@clean easyocr@blur # before vs after
chrF measures n-gram overlap with references, not human quality, and our findings are stated with their caveats:
Takeaway: synthetic-only Devanagari benchmarks overstate quality; real evaluation is indispensable; and the best "general" OCR for English may be far from the best for Indic.
@misc{singh2026devanagari,
title = {Can OCR-VLMs Read Devanagari? A Stress-Test Benchmark and Post-Correction Study},
author = {Aditya Pratap Singh},
year = {2026},
eprint = {arXiv:XXXX.XXXXX}, % update once arXiv assigns the ID
archivePrefix = {arXiv},
primaryClass = {cs.CL}
}
MIT © Aditya Pratap Singh
If this is useful, consider starring the repo ⭐ and citing the paper.
3 commits
Python
71.6%
TeX
26.4%
Shell
2.0%