Multilingual OCR running CPU-only in 132 MB: extracting identifiers from real-world photos

Web API AI Automation OCR 2026
Multilingual OCR running CPU-only in 132 MB: extracting identifiers from real-world photos

Project Context

VAON built an OCR microservice extracting text and identifiers (serial, IMEI, part number, model number) from photographs of product labels. The hard requirement was not accuracy on clean scans, but working on real camera photos using CPU only. Verified: 0.997 confidence in English, 0.999 in Japanese, both fully correct, on a 132 MB model set with no GPU.

Challenges

1

Input images are real photographs, not clean scans

Labels are captured on camera under working conditions: tilted, blurred, unevenly lit, cluttered backgrounds. An engine that scores well on clean print can fail outright here. The evaluation set therefore had to be photographs, not rendered text.

2

The default engine reads Vietnamese diacritics wrong: it drops them entirely

Given Sản phẩm Việt Nam, the default engine returns San pham Viet Nam. The diacritics are gone, not degraded. English, Chinese and Japanese show no such problem. This is a single-language failure needing its own handling, not a global parameter change.

3

The service has to run CPU-only to control infrastructure cost

No GPU dependency was a constraint from the start. That rules out most heavyweight OCR models and forces selection on model size as well as accuracy. Every option was measured under identical CPU conditions.

4

Identifiers sit scattered across the label, so reading the text is not enough

A single label carries several numeric groups. Recognising characters does not solve the problem: the service must know which group is the serial and which the part number, and bind each label to the value beside it. This is a structural problem, not a character-recognition one.

VAON's Solution

Split the service into two fully independent OCR endpoints rather than one merged pipeline. Clients select the engine by language need instead of being forced through a single processing chain.

Chose PP-OCRv6_medium as the default engine because it covers Chinese, English, Japanese and Latin scripts within one 132 MB model, verified by real testing, with no additional training required.

Added VietOCR as a secondary engine for Vietnamese diacritics, toggled by VIETOCR_ENABLED and off by default. Why this rather than replacing the default engine: VietOCR weighs roughly 623 MB, close to five times the primary engine, so that cost should only be paid where Vietnamese is genuinely needed.

Built one shared identifier-extraction layer for both branches, rule-based: regex label matching, spatial nearest-neighbour association, and a Luhn checksum for IMEI. Rules were chosen over a dedicated model so every result stays transparent and auditable step by step.

Gave each engine its own concurrency control (asyncio.Semaphore with threading.Lock), so the engines cannot contend for each other's resources under load.

Tightened input handling at the middleware layer: an upload size limit, a 25-megapixel ceiling against decompression bombs, pillow==12.3.0 pinned for CVE-2023-4863, and SHA256 verification of downloaded weights.

Value Delivered

100% correct in English and Japanese, at 0.997-0.999 confidence

The string S/N: ABC123456 scored 0.997 confidence. The Japanese string 日本語のテスト製品番号123 scored 0.999. Both were read fully correctly by the default engine, with no per-language configuration.

Vietnamese diacritics: from entirely wrong to fully correct

The same string the default engine returned as Sn phm Vit Nam is read correctly by VietOCR at 0.913 confidence. The problem was isolated to one language and solved with an on-demand engine, rather than trading off accuracy system-wide.

132 MB, no GPU required

The default model set is 59 MB detection plus 73 MB recognition, running entirely on CPU. In the recorded demo, one identifier read completed at 1,800 ms. Infrastructure therefore sits on commodity machines, not GPU instances.

Identifier extraction that can be audited

Because extraction is rule-based rather than model-based, every identifier traces back to the rule that matched it: which label, which position, whether it passed the Luhn checksum. When a result is wrong, the cause is identifiable immediately instead of requiring retraining.

54 automated tests, and a published list of limitations

The service carries 42 unit tests and 12 integration tests against the real model, split into two runs. During development a new route was found missing its upload size limit; it was patched before release. An 11 MB upload now returns 413 as designed.

Technology Stack

PaddleOCR PP-OCRv6
PaddleOCR PP-OCRv6
VietOCR (PyTorch)
VietOCR (PyTorch)
FastAPI
FastAPI
Pydantic
Pydantic
Python
Python
pytest
pytest