Skip to content

OCR for Historical German Fraktur Typefaces

9 min read · updated August 11, 2026

Run a German newspaper page from 1890 through a general-purpose OCR engine and the result is not degraded German. It is a plausible-looking stream of the wrong letters, at an error rate that no amount of image cleanup improves, because the engine is confidently matching shapes it was never shown.

Not difficult Latin — a different inventory

Fraktur is a blackletter typeface family that was the normal way to set German for around four centuries. Its use ended abruptly: the Normalschrifterlass of January 1941 ordered German printing to switch to Antiqua, the roman type used for the rest of Latin-script Europe. So almost everything printed in German before the Second World War is in a letterform a modern recogniser has no training coverage for.

The relevant point is not that the letters are ornate. It is that the feature distribution barely overlaps. Fraktur is built from broken strokes and dense verticals; the distinguishing information between two letters is often a single small terminal or a hairline, in places where Antiqua puts a curve. The classifier is not making a hard call and getting it wrong — it is being asked about a distribution it has no density in. The characteristic confusions follow from the shapes:

  • k and t — both have a vertical with a small arm; the arm attaches at a different height and nothing else differs.
  • n and u — near-identical in many cuts. Historical practice was to draw a small bow or breve over the u to disambiguate; if the scan loses it, the letters are the same object.
  • B and V, S and G, C and E — capitals are the most decorated and the least discriminable.
  • I and J — frequently the same sort. Many Fraktur fonts do not distinguish them at all, so no amount of image quality recovers the difference; it has to come from the word.
  • r and x, v and o — pairs whose separation depends on a stroke a few pixels long.

The long s, and why it is a policy decision

Fraktur distinguishes the long s, ſ, from the round s, and the rule is positional: long s within a word and syllable, round s at the end. Visually ſ differs from f only by the crossbar — f has a full bar crossing the stem, ſ has a nub on the left side only. That one detail is the highest-frequency confusion in Fraktur OCR.

The long s has its own code point, U+017F LATIN SMALL LETTER LONG S, and you have to decide up front whether your transcription preserves it or normalises it to s. Both are defensible: a diplomatic transcription keeps it, a searchable corpus does not. What is not defensible is mixing the two in ground truth. If half your training lines write ſ and half write s for the same glyph, the model is being trained on a coin flip for the most common letter in German, and accuracy on that class collapses.

Do not target the Mathematical Alphanumeric Symbols block, U+1D504 onwards. Those code points are for mathematical notation, where the blackletter shape carries semantic meaning. German text set in Fraktur is ordinary Latin text and transcribes to ordinary Latin code points; using the maths block makes the output unsearchable and unsortable.

The same era brings ligatures set as single sorts — ch, ck, tz, ſt, ſi — which a connected-component segmenter will hand to the classifier as one unit that must decode to two characters. And ß itself is historically a ligature of ſ with z or s, which is why eszett normalisation is a live question downstream rather than a settled one.

Letterspacing breaks word segmentation

Fraktur has no italic. Emphasis was set by spacing the letters of a word apart — Sperrsatz. To a layout engine this is indistinguishable from word spacing: the inter-letter gaps in an emphasised word are wider than the ordinary word gaps on the same line, so a threshold-based word segmenter splits the emphasised word into individual letters and joins nothing.

The consequence is that emphasised words — which are, by construction, the words the author thought mattered — are the ones your search index will not contain. The fix is to compute the gap threshold per line from the distribution of gaps rather than from a constant, and to treat a run of single-character tokens with uniform spacing as one word to be rejoined. It is not solvable by better recognition, because the recognition is correct; the segmentation is what is wrong.

Why a modern dictionary hurts

Most OCR stacks apply lexicon-based post-correction, and on historical German it makes the output worse rather than better. German orthography was standardised in 1901 and reformed again in 1996, so correct nineteenth-century spellings are not in a modern word list: Theil for Teil, giebt for gibt, seyn, Thür, Cultur, and the older capitalisation and hyphenation conventions throughout. A corrector trained on contemporary German will confidently rewrite every one of them, converting an accurate transcription into an inaccurate one, and it will do so silently because from its point of view it is fixing errors.

The same applies to a language model used as a post-corrector. It has seen far more modern German than 1880s German, so its prior pulls in the same direction. If you want post-correction on historical material, the lexicon has to be historical — built from the corpus itself or from a period dictionary — and it is often better to ship the raw recognition and record the uncertainty than to normalise the language of the source.

Compounding is the other thing that surprises people downstream. German builds long single-token compounds, so a single misrecognised character destroys a word that would otherwise be a strong search key, and the tokeniser costs behave differently from English — German compounds and token counts covers what that does to a downstream bill.

What actually works

  1. Use a Fraktur-specific model. Tesseract ships a Fraktur model separately from its modern German one, and the OCR-D and GT4HistOCR projects publish models and ground truth for exactly this material. Selecting the right model is worth more than every image-processing change combined.
  2. Fix your transcription convention in writing before any ground truth is produced: long s preserved or normalised, ligatures decomposed or not, historical spellings kept verbatim. Put it in the repository next to the data.
  3. Segment lines first and train on line images with line-level ground truth, rather than on segmented characters. Fraktur ligatures and tight fitting make character segmentation unreliable in a way that a sequence model over a whole line simply avoids.
  4. Handle the paper. Nineteenth-century newsprint shows through from the reverse; the ink bleeds. Background estimation and local thresholding matter more here than sharpening does.
  5. Evaluate with character error rate, not word error rate, and hold out pages by publication rather than at random — two pages from one issue share a font, a scanner setting and a compositor, so a random split reports the accuracy of memorising that issue.
  6. Keep a per-class error table. If ſ/f and n/u dominate it, the problem is resolution and model coverage. If capitals dominate, the problem is that display type differs from body type and needs its own training lines.