Segmentation is the hidden tax in historical HTR
DEV Community

Segmentation is the hidden tax in historical HTR

When people talk about handwritten text recognition (HTR), they talk about the recogniser: the model that turns a strip of handwriting into text. That is the part with the leaderboard, the character error rate, the shiny numbers. But if you actually try to read a real historical page, the first thing that breaks is almost never the recogniser. It is the step nobody benchmarks: line segmentation, cutting the page into the individual text lines the recogniser expects. Get that wrong and it does not matter how good your recogniser is. A line detector that merges two rows, or slices one row in half, hands the recogniser an input it was never trained on. The output comes back fluent and confidently wrong. I started calling this the hidden tax: a cost you pay before recognition even begins, and one that does not show up in recogniser benchmarks at all. Why it is worse on historical documents Modern OCR mostly assumes clean, printed, rectangular lines. Historical pages break every one of those assumptions: - Skew, curvature, and warped paper. - Marginalia, insertions, and crossed-out text. - Bleed-through from the reverse side. - Dense tabular registers where a "line" is really a cell in a grid. - Wildly varying hands, ink, and contrast across a single volume. A generic line detector trained on modern documents degrades quietly here. It still returns boxes. They are just the wrong boxes, and you only notice when the transcription is garbage. What I built I trained a domain-adapted YOLOv8 text-line detector on historical layouts (18th to 20th century Nordic court and church records), plus a kraken baseline segmenter as a complementary, baseline-based approach. Box-based detection is fast and robust to the noise above; the baseline approach helps where reading order matters more than speed. Here is the box detector running on a real page it has never seen: an 1822 Nordic church register (public domain, via Wikimedia Commons). It finds 120 text lines, cell by cell, top to bottom: The detector reaches roughly mAP50 ~0.79 on held-out historical pages. That is not a solved-problem number, and I want to be honest about that: dense tables, heavy marginalia, and extreme skew still trip it up. But it is a large step up from a generic detector on this kind of material, and crucially it fails in visible ways (a missed or split box you can see) rather than the invisible failure of a good recogniser fed a bad crop. The part that surprised me While benchmarking a wider pipeline on unseen historical hands, the most striking failure mode was not the segmenter. It was the recognisers, specifically the general vision-language models. On truly unseen hands, a strong general VLM does not just score lower than a trained specialist. It fails silently: it produces fluent, plausible text that is simply wrong, with no signal that anything went wrong. It will invent a year, a place, a name. A trained specialist, paired with good segmentation and a period-aware lexicon, stays far more faithful to what is actually on the page, and when it is unsure, it looks unsure. That combination, honest segmentation plus a faithful specialist, is the whole game for reading material that no model has seen before. (More on the full benchmark in a paper that is in preparation.) Try it Everything here is open and runnable: - Line segmenter (runnable): danish-htr-line-segmenter - a thin wrapper that downloads the weights from the Hub and segments a page in a few lines of Python. - Model weights: yolov8m-historical-line-segmenter on the Hugging Face Hub. - Synthetic training data: danish-htr-synthetic - 160,000 synthetic 18th-century Danish lines with exact ground truth. - Live demo: the Space. from huggingface_hub import HfApi, hf_hub_download from ultralytics import YOLO repo = "abhishekjha1008/yolov8m-historical-line-segmenter" weights = hf_hub_download(repo, [f for f in HfApi().list_repo_files(repo) if f.endswith(".pt")][0]) model = YOLO(weights) result = model.predict("page.jpg")[0] # boxes for each detected text line If you work with historical documents, I would genuinely like to know what breaks on your material. The pages I cannot segment well are the most useful thing you can send me. I work on historical-document HTR and production ML. Models and data: hf.co/abhishekjha1008. Top comments (0)

Read on DEV Community ↗ ← Back to News

Comments

No comments yet. Start the discussion.