OCR models for scanned archives: capture quality before recognition
Here's the decision rule: if the smallest character you care about doesn't survive as clean pixels in the raw capture, no model downstream fixes it. Not a bigger model, not a better prompt. Send the page back through the scanner or flag it and move on; everything else in this pipeline sits downstream of that gate.
That's a hard rule to sell to anyone who has watched a vision-language model turn a smudged, deteriorated page into three clean paragraphs of readable prose. It looks like magic. It's also the trap. A model trained to continue plausible text will, handed an unreadable region, continue it plausibly, whether or not that matches what was actually printed there. Nothing about the output looks broken, which is what makes it dangerous for archives: books, correspondence, reports, records off paper or microfilm, where the point is a searchable transcript tied to real pages in the right order, not a fluent guess dressed up as one.
Fix it before the model ever sees it
The real engineering happens before recognition starts. Define capture checks for blur, glare, clipping, skew, resolution, and compression, and reject or reacquire anything that fails them. Keep the original file untouched, and assign every page a stable identifier before processing runs. Track the chain each page travels: source hash, capture device, original dimensions, which crop, dewarp, and denoise steps ran and at what version, which model produced the output, and the raw text, region, and confidence it returned, kept separate from whatever a later validation rule proposed as correct.
That separation matters. If a recognizer sees something ambiguous, a zero versus a letter O, a decimal point, a unit, a diacritic, store what it saw and what a business rule guessed. Never let the guess overwrite the observation. Measure honestly: character and word error rates treat a dropped comma and a transposed account number as roughly the same mistake. They are not. Add exact-match checks for identifiers, dates, amounts, and units, and score table geometry apart from plain transcription accuracy.
Start with a conventional OCR engine and layout segmentation, the boring deterministic stack that already works on clean text. Bring a generative or multimodal model in only where it earns its place: hard handwriting, broken layouts, a label sitting far from its value. It has no business replacing a stage that already worked.
What happens when nobody's watching the page
The failure worth catching isn't garbled output. It's the opposite: clean, grammatical prose that quietly swapped in a plausible name, date, or word for one it couldn't read. Give the pipeline a way to say it doesn't know. Low-resolution regions, clipped pages, glare, unreadable handwriting, totals that don't add up: raise an exception, not a confident guess. Model confidence scores are inconsistent and often miscalibrated, so lean on image-quality checks, format validators, cross-field arithmetic, and disagreement between recognition passes instead of one number.
Store a page number and a bounding box for every field that matters, and show the reviewer the crop beside the proposed value. Someone can settle an ambiguous digit in seconds with the region visible; without it, they're re-scanning the page or hunting the physical original. If a vision-language model reasons over a document, treat text printed inside it as data, never instructions, and keep any database write behind schema validation the model doesn't control. Across multi-page sets, preserve order and header identity on purpose, split documents deliberately rather than let a model decide where one invoice ends, and version models, preprocessing, and rules together so a stratified replay catches regressions when any piece changes.
I'd rather run this slower and reject more pages than let a fast, fluent pipeline hand back a document nobody can trust, and that's the tradeoff I'm choosing: less throughput, more reacquisition, a reviewer queue that never quite empties. In exchange I get an archive where every value still points back to something a person can check, instead of a paragraph that merely reads well.