VLM-OCR: Scanned Corpora from Page Images to Research-Usable Text

SkillMedia

OCR scanned or image-only corpora with vision-language models, in three phases. Use `evaluate` to compare candidate OCR systems against stratified human ground truth and pick one on measured CER/WER, `run` to build the production pipeline (model selection, image handling, prompts, architecture, batching, accuracy evaluation, reproducibility), and `clean` to correct raw OCR text with LLM and rule-based passes, quality diagnostics, multilingual handling, and span-level provenance. Not for born-digital documents with a text layer — those go to $doc-to-markdown.

Available today. Use it from your connected AI after setup.

Connect ahel once, and every AI you use reads what you have installed.

Then ask your AI: use the VLM-OCR: Scanned Corpora from Page Images to Research-Usable Text skill

What this skill tells your AI

The instructions your AI receives, as published by scdenney/open-science-skills in codex/vlm-ocr/SKILL.md and read by ahel’s review.

Instructions

This skill covers the whole path from a corpus of page images to text an analysis can consume: choosing an OCR system by measurement (evaluate), running the chosen model across the corpus (run), and correcting and documenting its output (clean).

Use this skill only for scanned or image-only documents. Born-digital PDFs, Word files, and anything carrying an embedded text layer belong to $doc-to-markdown, which extracts the text directly and needs no model at all. A PDF yielding under ~300 extractable characters per page is a scan; that is the trigger for this skill.

Phase selection

Infer the phase from what the user brings:

What you are givenPhase
A corpus with no model chosen yet, or the question of which OCR system to use or how accurate one is on these pagesevaluate
A chosen model and a corpus to transcribe at scalerun
Raw OCR output needing correction, quality diagnostics, or a provenance recordclean

Invoking $vlm-ocr evaluate, $vlm-ocr run, or $vlm-ocr clean forces a phase. Otherwise infer it, and state which phase you are working in before you start.

Phase order and handoff

evaluaterunclean. Each phase produces exactly what the next one consumes:

  • evaluate emits a model registry, a declared normalization recipe, per-stratum CER/WER, and a documented model choice — possibly one model per script or era. run takes the chosen model and the registry.
  • run emits per-page structured records: transcription, uncertain spans, layout markers, page-type flags, and the full pipeline provenance block. clean uses these to route pages to a correction strategy and to anchor its own edit log.
  • clean emits corrected text plus a span-level provenance log anchored to the raw OCR revision, with the raw text preserved alongside every corrected variant.

Moving backwards is legitimate and expected: a run whose diagnostics fail returns to evaluate for a different model, and a clean pass that finds systematic transcription failure returns to run at higher DPI or without quantization.

Provenance rules that hold in every phase

  • Pin and date every model. Exact identifier plus revision or commit hash for open-weight models; the dated identifier for hosted APIs. Family-name reporting ("we used Qwen and Gemma") is not reproducible, and neither is an undated proprietary model name.
  • Record every parameter in machine-readable form alongside the output, not in prose notes: quantization, DPI, prompt text or prompt id, generation parameters (temperature, max tokens, sampling strategy, seed), software versions, and hardware.
  • Never overwrite the previous stage's text. Raw OCR survives every correction pass; each stage writes a new artifact and leaves its input intact, so a downstream researcher can choose a different normalization.
  • Declare normalization once and reuse it. The recipe declared in evaluate §4 is the same one run §6 reports against and clean §4 scores with. Two different recipes across phases make the numbers incomparable.
  • Prefer locally-hosted open-weight models pinned to a revision when publication-grade reproducibility is required; hosted APIs change underneath you (Barrie, Palmer & Spirling 2025).
  • For the methods-section disclosure of any phase, compose with $methods-reporting. For the hardest pages, where no single model is reliable, the multi-model voting logic in $model-council-voting applies to OCR transcriptions as well.

Phase evaluate: Comparing OCR Systems Before You Commit

Before running any OCR model across a whole corpus, run a controlled comparison on a small, human-transcribed sample and let the measured error rates pick the model. This is the selection gate: choose a model and document why, then move to run.

1. Run a Comparison Before You Commit

  • Treat model choice as an empirical question, not a default — test, do not guess. A model that tops a general vision-language leaderboard, or that read one language well, can still fail on your script, era, or page layout. The only trustworthy signal is its error rate on pages like yours.
  • The comparison is cheap insurance. Evaluating a 50–100 page sample once prevents discovering, after a 50,000-page bulk run, that the chosen model silently dropped every table or collapsed on degraded scans.
  • Scope the comparison to the decision. A worked comparison of this kind runs nine systems on 64 pages spanning two languages and seven decades before committing to a bulk pipeline — enough to rank the candidates on the strata that matter, not a full corpus.
  • The output is a go/no-go gate: keep the model whose accuracy clears your threshold on the strata you care about, or conclude that no single model does and split the corpus by script or era (§6).

2. Assemble the Candidate Set

  • Include three kinds of system: several open-weight VLMs, one or two proprietary APIs, and a traditional OCR baseline (Tesseract). In that worked comparison the runner's model registry holds six open-weight VLMs (Qwen3.5-35B, Qwen3-VL-32B, Qwen3.5-9B, Gemma, MiniCPM-V, DeepSeek-OCR) plus Tesseract, with two proprietary APIs (GPT-4.1, Claude) run by separate scripts — nine systems total.
  • Pick the VLM candidates from OCR benchmarks (e.g., OCRBench), not general multimodal leaderboards. General vision-language ability does not predict transcription fidelity on dense historical print; the run phase §1 covers benchmark-grounded model selection in detail.
  • Always keep a traditional baseline. Even when it loses, Tesseract anchors what "hard" means for your corpus and shows where a VLM actually earns its extra cost and latency.
  • Record an exact registry entry per system — name, HuggingFace id or dated API identifier, quantization, and loader — one record per model, keyed by id rather than family name.

3. Build a Stratified Ground-Truth Set

  • Human-transcribe a stratified sample, not a convenience sample. Stratify on the dimensions that actually drive OCR difficulty: language/script, era (a decade bracket), and content type — running body text, multi-column tables, illustrated or captioned pages, degraded or water-damaged print, and front matter / title pages. Keep a page manifest that tags each page with language, year, decade, subject, and content type, and sample across all of them.
  • Size: roughly 50–100 pages is a workable default for a handful of candidate systems (a house default that balances transcription effort against per-stratum cell sizes, not a cited figure). More strata require more pages so that each cell holds enough pages to mean something.
  • Store ground truth as one UTF-8 .txt per page, keyed by a stable page id (a flat ground_truth/<page_id>.txt directory the scorer reads). Transcribe faithfully — preserve the characters actually on the page (hanja alongside hangul, diacritics) — and decide up front how to render non-text regions (tables, figures) so the reference and the OCR output are scored on the same basis.
  • Transcribe before looking at any model output, so the reference is not anchored to a model's guesses. Two independent transcribers on a subset, with disagreements reconciled, guard against a single transcriber's systematic errors (the inter-coder logic in $text-classification applies here too).

4. Character and Word Error Rate

  • CER and WER are edit-distance metrics. CER = Levenshtein(reference, hypothesis) ÷ length(reference); WER is the same at the word level (a word-level dynamic-programming edit distance ÷ reference word count). Implement both directly — a character Levenshtein distance for CER and a word-level DP for WER (Levenshtein 1966).
  • Declare normalization before scoring; it changes the numbers. A workable normalizer applies Unicode NFC, strips markdown artifacts (headers, bold/italic, links — VLMs routinely emit markdown), and collapses whitespace, but is deliberately case-sensitive (no lowercasing) to preserve OCR fidelity. State each choice (NFC vs NFKC, case sensitivity, punctuation and markdown handling) and apply it identically to every system. Comparing a markdown-emitting VLM against a plain-text baseline without stripping markup unfairly penalizes the VLM.
  • Report both CER and WER, and report the distribution, not a single mean. Give mean and median with the page count (n) per cell. The median resists the blank-page and repetition-loop outliers that wreck a mean, while the mean exposes how bad the tail gets.
  • Interpretation bands (house defaults, consistent with the run phase §6): CER below ~5% is excellent, below ~10% is usable with cleanup, and above that the text needs heavy correction or a different model. These are planning guides, not cited cutoffs — set the operative threshold from what your downstream analysis tolerates.
  • Score every stratum, not just the overall mean. Aggregate CER/WER by model, by language × model, by decade bracket × model, and by content type × model — because a model can win overall and still fail on tables, on one script, or on the oldest decade.

5. Run the Comparison Efficiently

  • Load and unload models sequentially to fit one GPU. Import torch lazily and release each model (an explicit gc pass) before loading the next, so several large VLMs are scored on a single card without holding them all in memory at once. State the exact quantization per model (GPTQ-Int4, NF4, BF16) — it affects both fit and accuracy.
  • Serve via vLLM or Ollama for batched throughput where the model supports it (one driver per served model, pointed at a vLLM OpenAI-compatible endpoint); run proprietary APIs through their own rate-limited scripts.
  • Measure speed (seconds per page) alongside accuracy, but do not compare a traditional baseline's speed to a VLM's as if equal. Tesseract is fast because it does far less, and it fails on non-Latin script. Report speed as context for cost, never as a quality signal.
  • Make the run resumable and idempotent: write one output file per page per model and skip pages already done, so a crash partway through the comparison does not restart everything.

6. Interpret: There Is No Single Best Model

  • Expect no universal winner. The lesson of that worked comparison is fit-to-script-and-corpus: on Latin-script Polish most models cluster within a couple of percent CER, while on Korean the field splits by roughly 25% even among the best. When that happens, pick per script or per era rather than forcing one model.
  • Skill does not transfer across scripts. A model that reads Polish beautifully can collapse on Korean hanja–hangul. Never generalize a single-language result to a script the model was not measured on.
  • The "OCR specialist" is not guaranteed to win. In that run a document-only OCR model produced the most blank pages and repetition loops, and general-purpose VLMs beat it. Judge on measured CER for your pages, not on a system's category label.
  • Open can rival proprietary and also reproduce. The best open-weight models matched the proprietary APIs while remaining pinnable and re-runnable; the proprietary APIs are fast and capable but change underneath you between versions (the reproducibility argument the run phase §7 and $model-council-voting also make). Weigh accuracy against reproducibility, not accuracy alone.
  • Decide and document: the chosen model(s), the threshold they cleared, the strata where they win and lose, and any split (e.g., model A for Polish, model B for Korean).

7. Reproducibility and Reporting

  • Publish the model registry — exact ids, quantization, loaders or dated API identifiers, decoding settings, and seeds — and the ground-truth set (or a precise description of how it was built and transcribed).
  • Report the full results table: CER and WER, mean and median, n, broken down by model and by stratum (language, era, content type), with the normalization recipe stated alongside.
  • Report speed per system for cost context, carrying the traditional-baseline caveat.
  • Report per-model failure modes — blank pages, repetition loops, garbled or hallucinated script. These are decision-relevant and invisible in an averaged CER.
  • Carry the chosen model, the registry, and the normalization recipe forward into the run phase; for the methods-section disclosure of the comparison, compose with $methods-reporting.

Phase run: The Production VLM-OCR Pipeline

For a worked language-specific transcription prompt (pre-reform Cyrillic) and a per-page JSON output schema with uncertain_spans, layout_markers, and flags, see references/prompt-and-schema.md.

1. Model Selection

  • Run your own comparison before selecting — this is a gate, not a preference. Benchmarks and published studies narrow the candidate set; they do not decide it. Score the shortlist on a human-transcribed sample of your own pages and let the measured error rates pick the model. The evaluate phase holds the comparison protocol, the stratified ground-truth design, and the go/no-go gate. Everything below narrows the shortlist that comparison scores.
  • Start from OCR benchmarks, not general VLM leaderboards. OCRBench (Liu et al. 2024) tests across 29 document OCR dimensions; OCRBench v2 (Fu et al. 2025) extends to multilingual scripts and multi-page documents. General vision-language benchmarks (MMMU, VQAv2) do not predict OCR accuracy.
  • Verify language support explicitly. Confirm the target language appears in the model's training set with per-language accuracy data. Qwen3-VL (Bai et al. 2025) enumerates 39 languages with OCR benchmarks; models that claim "multilingual support" without language-specific evidence may fail on non-Latin scripts.
  • Compare across models for your script family. E-ARMOR (2025) compares five VLMs and two traditional OCR systems across 54 languages on a hand-annotated dataset. Use this or similar comparative studies rather than relying on a single model's self-reported scores.
  • Assess quantization tradeoffs before committing. Low-bit quantization (e.g., W3A16, W4A8) enables single-GPU deployment but can degrade VLM accuracy non-uniformly across modalities. Li et al. (2025) show that language tokens are an order of magnitude more sensitive to quantization than vision tokens; treating them equally during calibration over-weights the insensitive modality and hurts performance. Always compare quantized output against a full-precision baseline on a pilot sample before committing to a bulk run.
  • Test the instruct variant against reasoning variants. For OCR tasks, instruction-following models typically outperform chain-of-thought variants, which may add latency and fabricate content rather than improving transcription fidelity.

2. Image Handling and Preprocessing

  • Separate OCR input resolution from archival preservation. For archival capture, FADGI (2016) prescribes 300-400 ppi across books, journals, and manuscripts (with 400+ ppi for 4-star compliance), and Metamorfoze (van Dormolen 2012) applies similar European standards. For VLM OCR input, resolution requirements are lower but not well-pinned by the KB: calibrate on a pilot, do not drop below ~150-200 ppi, and raise DPI for small type, faded ink, or pre-industrial typography.
  • Prefer native image extraction over rasterization. Scanned PDFs store each page as an embedded image at the original scan resolution. Extract these byte-for-byte using PDF library methods rather than re-rasterizing, which downsamples and re-encodes (Pitt OCR Best Practices). Reserve rasterization for VLM input when a specific DPI is needed.
  • Test preprocessing on a sample before applying corpus-wide. Learned image restoration combined with neural post-correction can yield large CER reductions on degraded historical documents — Guan et al. (2025) report 63.9-70.3% CER reduction for the full PreP-OCR pipeline (ResShift image restoration + ByT5 post-correction), not for classical deskew/binarization/contrast alone. Classical preprocessing alone may not help and can actively hurt: Machidon & Machidon (2025) find that grayscale conversion, binarization, and dilation did not improve OCR on degraded folkloristic scans and in some cases introduced artifacts that worsened recognition. Always run with and without preprocessing on a diverse sample and compare.
  • Store archival images separately from OCR derivatives. Save native-resolution images for preservation and generate OCR-resolution images in memory for the VLM. Do not save OCR-resolution rasterizations as the archival copy.

3. Prompt Engineering

  • Build language-specific prompts that enumerate expected characters. For diacritics-heavy languages (Polish, Czech, Vietnamese, Turkish), list every expected diacritical character explicitly in the prompt. For CJK scripts, instruct the model to handle mixed-script content (e.g., Korean hangul with classical Chinese hanja).
  • Specify structured output format. Instruct the model to output markdown preserving headings, paragraphs, footnotes, and tables. Structured prompts with explicit output format significantly outperform generic "extract text" instructions.
  • Include negative instructions. "Do not translate," "do not interpret," "do not add content not present in the image." VLMs will summarize, translate, or describe images unless explicitly constrained.
  • Handle page-type edge cases in the prompt. Instruct the model on what to return for blank pages, illustration-only pages, and pages with only page numbers. Without this guidance, models may hallucinate content for non-text pages (Gbelidji 2026).
  • Guard against over-historicization on period documents. VLMs trained with broad historical exposure may project archaic orthography anachronistically. Levchenko (2025) documents GPT-4o inserting historical characters in 59% of 18th-century Russian files regardless of prompt. Validate on the target period and stratify ground-truth sampling by decade (see §6) to detect this failure mode; no prompt will fully suppress it.

4. Pipeline Architecture

  • Separate GPU-intensive OCR from CPU-only post-processing. The minimum viable pipeline has three stages: (1) VLM OCR producing per-page raw text, (2) quality diagnostics classifying problem pages, (3) assembly into combined document-level output. olmOCR (Poznanski et al. 2025) demonstrates this extract-describe-assemble pattern at scale.
  • Integrate diagnostics as automated gates. Diagnostics should classify each page into action categories (OK, rule-fixable, LLM-fixable, manual review) using language-aware detection: diacritic-to-Latin ratios for European scripts, CJK character ratios for East Asian scripts, repetition density, symbol density, and page length anomalies. These categories are exactly the routing input the clean phase consumes.
  • Store all results as structured JSON with full metadata. Every pipeline stage should output structured data (model name, quantization, DPI, timestamps, per-page results) rather than flat text files. This enables automated aggregation and corpus-level quality dashboards.
  • Design for resumability. The pipeline should detect already-processed documents and skip them on re-run. Store a completion marker (e.g., results_raw.json) per document so partial runs can resume without re-processing.

5. Batch Strategy and Resource Planning

  • Calibrate GPU-hour estimates from a measured run. Process 10-20 pages, measure per-page time, multiply by corpus page count, and add 30% buffer for variance and failed retries. Do not rely on model documentation or theoretical throughput.
  • Structure batch processing as one job per document. This minimizes job scheduling overhead, simplifies failure recovery (re-run one document, not one page), and produces self-contained output directories.
  • Use the tranche/gate pattern. Split the corpus into a small test tranche (3-5 documents per language) and bulk tranches. Complete accuracy evaluation on the test tranche before committing GPU-hours to bulk runs. This is the single most important resource management decision.
  • Handle large documents explicitly. Books exceeding the job wall-clock limit should be split into page-range chunks submitted as separate jobs writing to the same output directory. The assembly stage must merge chunks.
  • Track all jobs in a manifest. Maintain a CSV or database mapping each document to its tranche, job ID, status, GPU-hours consumed, and quality metrics. This supports both operational monitoring and post-hoc reporting.

6. Accuracy Evaluation

  • Combine ground-truth sampling with automated proxies. Human-transcribed ground truth with CER/WER computation gives a rigorous accuracy number; dictionary-based hit rates give a scalable quality signal across every page. Neither alone is sufficient.
  • Stratify the ground-truth sample. Select pages spanning languages, decades (older print is harder), print quality (faded vs. clean), and content type (body text, tables, captions). A sample of 20-30 pages per language is a practical minimum for a reliable CER estimate (Levchenko 2025) — that figure is the ongoing-monitoring minimum for a pipeline already in production, not the size of the up-front selection comparison, which the evaluate phase §3 sizes separately. The stratification scheme, the normalization declaration, and the CER/WER implementation are specified once in the evaluate phase; reuse them here rather than defining a second set.
  • Document the evaluation tool. CER/WER scores differ across evaluation tools due to normalization differences (whitespace handling, Unicode normalization, punctuation treatment). Neudecker et al. (2021) demonstrate that tool choice changes reported accuracy. Specify which tool you used, which normalization was applied, and whether the comparison was case-sensitive.
  • Acknowledge CER/WER limitations. Character-level edit distance does not capture layout errors, semantic correctness, or structural fidelity. A model can scramble column order while achieving low CER (Beyene & Dancy 2026). Supplement with dictionary hit rates, manual inspection of representative pages, and downstream task performance.
  • Set go/no-go thresholds relative to downstream needs. A mean CER below 5% is excellent; below 10% is acceptable for most computational text analysis. Calibrate against what your analysis pipeline can tolerate rather than pursuing absolute accuracy — van Strien et al. (2020) show that the impact of OCR quality on downstream NLP tasks is task-specific (topic modeling, NER, dependency parsing, and retrieval degrade at different thresholds), so the right threshold depends on which downstream task consumes the text.

7. Reproducibility and Documentation

Shortened here. Read the whole file on GitHub.

Signals

GitHub stars
55
Forks
3
Last commit
Sep 2026
Advanced
Catalog kind
skill
Gateway key
vlm-ocr
Source
github.com/scdenney/open-science-skills