5.2. 🚀 Regelbasierte OCR-Nachbearbeitung#

Hinweise zur Ausführung des Notebooks

Dieses Notebook kann auf unterschiedlichen Levels erarbeitet werden (siehe Abschnitt “Technische Voraussetzungen”):

  1. Book-Only Mode: Sie lesen das Notebook hier im “Jupyter Book”, ohne den Code selbst auszuführen.

  2. Cloud Mode: Klicken Sie oben rechts in der Menüleiste auf das Raketen-Symbol 🚀 und wählen Sie “Colab”, um das Notebook auszuführen.

  3. Local Mode: Klicken Sie oben rechts in der Menüleiste auf das Download-Symbol und wählen Sie “.ipynb”, um das Notebook lokal auszuführen.

5.2.1. Übersicht#

In diesem Notebook wird eine regelbasierte OCR-Nachkorrektur entwickelt und angewendet. Ziel ist es, typische Fehler, die beim OCR-Prozess historischer Texte entstehen, automatisch zu beheben und die Verbesserung messbar zu machen.

Dafür werden folgende Schritte durchgeführt:

  1. Identifikation typischer OCR-Fehler im Korpus (z.B. < statt ch, fie statt sie, ſ statt s)

  2. Implementierung von Korrekturregeln mit regulären Ausdrücken

  3. Anwendung der Regeln auf ein Beispielbild mittels Tesseract-OCR

  4. Messung der Verbesserung anhand von Precision, Recall und F1-Score im Vergleich mit einem Ground-Truth-Text

  5. (Advanced) Anwendung der Korrekturregeln auf das gesamte Korpus

Hide code cell content
# 🚀 Install libraries
import sys
if 'google.colab' in sys.modules:
    !sudo apt install tesseract-ocr
    # Das Fraktur-Modell deu_latf (früher frk) ist nicht als apt-Paket verfügbar – Modelldatei direkt laden
    !sudo wget -q https://github.com/tesseract-ocr/tessdata/raw/main/deu_latf.traineddata -P $(find /usr/share/tesseract-ocr -name tessdata -type d | head -1)
!pip install pytesseract pillow Levenshtein requests
Requirement already satisfied: pytesseract in /opt/hostedtoolcache/Python/3.11.15/x64/lib/python3.11/site-packages (0.3.10)
Requirement already satisfied: pillow in /opt/hostedtoolcache/Python/3.11.15/x64/lib/python3.11/site-packages (10.3.0)
Requirement already satisfied: Levenshtein in /opt/hostedtoolcache/Python/3.11.15/x64/lib/python3.11/site-packages (0.25.0)
Requirement already satisfied: requests in /opt/hostedtoolcache/Python/3.11.15/x64/lib/python3.11/site-packages (2.31.0)
Requirement already satisfied: packaging>=21.3 in /opt/hostedtoolcache/Python/3.11.15/x64/lib/python3.11/site-packages (from pytesseract) (24.0)
Requirement already satisfied: rapidfuzz<4.0.0,>=3.1.0 in /opt/hostedtoolcache/Python/3.11.15/x64/lib/python3.11/site-packages (from Levenshtein) (3.7.0)
Requirement already satisfied: charset-normalizer<4,>=2 in /opt/hostedtoolcache/Python/3.11.15/x64/lib/python3.11/site-packages (from requests) (3.3.2)
Requirement already satisfied: idna<4,>=2.5 in /opt/hostedtoolcache/Python/3.11.15/x64/lib/python3.11/site-packages (from requests) (3.6)
Requirement already satisfied: urllib3<3,>=1.21.1 in /opt/hostedtoolcache/Python/3.11.15/x64/lib/python3.11/site-packages (from requests) (2.2.1)
Requirement already satisfied: certifi>=2017.4.17 in /opt/hostedtoolcache/Python/3.11.15/x64/lib/python3.11/site-packages (from requests) (2024.2.2)
Hide code cell content
import re
import pytesseract
from PIL import Image
from pathlib import Path
from tqdm import tqdm

Bevor wir Daten herunterladen, definieren wir eine kleine Hilfsfunktion download_file. Sie lädt eine Datei plattformunabhängig – also auch unter Windows – aus dem Internet in einen Zielordner herunter und ersetzt damit das Kommando wget, das nicht auf allen Systemen (z.B. Windows) nativ verfügbar ist.

# helper: download a single file (cross-platform replacement for `! wget -P`)
import requests

def download_file(url, target_dir):
    """Download the file at `url` into `target_dir`, keeping its original name."""
    target_dir = Path(target_dir)
    target_dir.mkdir(parents=True, exist_ok=True)
    target_path = target_dir / url.split("/")[-1]
    response = requests.get(url)
    response.raise_for_status()
    target_path.write_bytes(response.content)
    return target_path

5.2.2. Typische Fehler#

Im Folgenden listen wir einige typische Fehler in unserem Korpus auf:

  • „fie“ statt „sie“ (Bild und Ergebnis später hinzufügen)

  • „vm“, „vnd“ statt „um“, „und“

  • „<“ statt „ch“

Einige Dinge sind keine Fehler, sondern Merkmale der historischen Orthographie, die wir für die weitere Verarbeitung mit modernen NLP-Tools normalisieren möchten:

  • „ſ“ statt „s“

In vielen Fällen können wir dies mit einigen regulären Such- und Ersetzungsmustern beheben (z.B. jedes <, das nicht von Leerzeichen umgeben ist, in ch umwandeln).

Der Standardweg, solche Muster auf einem Computer auszudrücken und zu implementieren, sind reguläre Ausdrücke. Mehr über reguläre Ausdrücke erfahren Sie hier.

5.2.3. Implementierung von Regeln für typische Fehler mit regulären Ausdrücken#

def post_correct_text(ocr_output):
    cleaner_output = re.sub(r'(\w)<(\w)', '\\1ch\\2', ocr_output)
    cleaner_output = re.sub(r'(\w)5(\w)', '\\1s\\2', cleaner_output)
    cleaner_output = re.sub(r'\bv(m|nd)\b', 'u\\1', cleaner_output)
    cleaner_output = re.sub(r'\bfie\b', 'sie', cleaner_output)
    cleaner_output = cleaner_output.replace('ſ','s')
    #cleaner_output = cleaner_output.replace('\n',' ')
    return cleaner_output

5.2.4. Anwendung der Regeln auf die OCR-Ergebnisse #

Hide code cell content
if not Path("grippe.jpeg").exists():
    download_file("https://raw.githubusercontent.com/quadriga-dk/Text-Fallstudie-1/refs/heads/main/assets/images/grippe.jpeg", ".")
../_images/grippe1.jpeg
ocr_output = pytesseract.image_to_string(Image.open('grippe.jpeg'), lang='deu_latf')
print(ocr_output)
7ie Grippe wüfel weiter

Zunahme der ſ<weren Fälle in Berlin.

Die Zahl der Grippefälle iſt in den leßten
be:der Tagen auc<h in Groß-Berlin noH
erf>lig zeftiegen. Die Worenhäuſer und ſon-
haen aroßen GeſHöäfte, die Krirgs- unh die prie
n Betriebe lagen, daß übermäig viele An-

"Fz 5cben krep? melden müſſen,-und an<
; .*e* Vofſt und 5ei der Straßenbahn iſt der
ſo3 der Grippelranten bedeuter) gt&

? e .*;* 2 8 1
ocr_output_corr = post_correct_text(ocr_output)

Lass uns sehen, wie sich das Ganze verändert hat:

print(ocr_output_corr)
7ie Grippe wüfel weiter

Zunahme der schweren Fälle in Berlin.

Die Zahl der Grippefälle ist in den leßten
be:der Tagen aucchh in Groß-Berlin noH
erf>lig zeftiegen. Die Worenhäuser und son-
haen aroßen GesHöäfte, die Krirgs- unh die prie
n Betriebe lagen, daß übermäig viele An-

"Fz 5cben krep? melden müssen,-und an<
; .*e* Vofst und 5ei der Straßenbahn ist der
so3 der Grippelranten bedeuter) gt&

? e .*;* 2 8 1

5.2.5. Messung der Verbesserung#

Hide code cell content
# 🚀 get the auxiliary file with the function to measure the quality of the OCR output
if not Path("auxiliary/measure_ocr_quality.py").exists():
    download_file("https://raw.githubusercontent.com/quadriga-dk/Text-Fallstudie-1/main/ocr_post_correction/auxiliary/measure_ocr_quality.py", "auxiliary")
Hide code cell content
# 🚀 create folder and get the auxiliary python script to run in Colab
aux_dir = Path("auxiliary")
if not aux_dir.exists():
    aux_dir.mkdir(parents=True)
Hide code cell content
from auxiliary.measure_ocr_quality import measure_ocr_quality

Lass uns sehen, wie sich die regelbasierte Nachkorrektur auf die OCR-Qualitätsmetriken ausgewirkt hat

ground_truth = """Die Grippe wütet weiter 
Zunahme der schweren Fälle in Berlin. 
Die Zahl der Grippefälle ist in den letzten 
beiden Tagen auch in Groß-Berlin noch 
erheblich gestiegen. Die Warenhäuser und son-
stigen großen Geschäfte, die Kriegs- und die pri-
vaten Betriebe klagen, daß übermäßig viele An-
gestellte sich haben krank melden müssen und auch 
bei der Post und bei der Straßenbahn ist der 
Prozentsatz der Grippekranken bedeutend ge-
stiegen."""

Originales (unkorrigiertes) OCR-Ergebnis#

precision, recall, f_score = measure_ocr_quality(ocr_output, ground_truth)
print(f'Precision: {round(precision, 4)}\nRecall: {round(recall, 4)}\nF1-score: {round(f_score, 4)}')
Precision: 0.7682
Recall: 0.8164
F1-score: 0.7916

Korrigiertes OCR-Ergebnis#

precision, recall, f_score = measure_ocr_quality(ocr_output_corr, ground_truth)
print(f'Precision: {round(precision, 4)}\nRecall: {round(recall, 4)}\nF1-score: {round(f_score, 4)}')
Precision: 0.7932
Recall: 0.8389
F1-score: 0.8154

Also, unser F-Score hat sich etwas verbessert, gut!

5.2.6. 🚀 Your turn: Interaktives Beispiel#

The two interactive widgets below demonstrate how OCR post-processing rules directly impacts quality metrics. Furthermore, you can build your own custom rule pipeline and see exactly how your choices effect the final F1-Score!

Note: This is just a simulation to show how applying different rules changes the metrics.

🎯 Mini Demo: Trying out the predefined Rules

In the interactive dashboard below, you can play with rule-based post-processing. Toggle the checkboxes to apply specific search-and-replace rules to the raw OCR text. Watch how the text changes in real-time, and notice how each rule directly impacts the Precision, Recall, and F1-Score metrics!

Precision
0.7977
Recall
0.8819
F1-Score
0.8377
Reguläre Ausdrücke (Rules) anwenden:

Live OCR-Output:

🎯 Mini Demo: The Custom Rule Builder

Now you are in control! Build your own rule-based correction pipeline. Enter a character or word to find, and what to replace it with. Your applied rules will stack up as tags below. Click the "✖" on any tag to remove it and see how the text and F1-score instantly adjust!

Hint: Try a good rule like finding ſ and replacing it with s. Then, try a dangerous rule like finding e and replacing it with i to see what happens to your F1-Score when a rule is too broad!

Current F1-Score
0.8377
Find: Replace with:
Live OCR-Output:

5.2.7. (Advanced) Ausführung des regelbasierten OCR-Nachkorrekturverfahrens auf dem gesamten Korpus#

pathtxt = Path('../data/txt')
if not pathtxt.exists():
    pathtxt.mkdir(parents=True)
for file in tqdm(pathtxt.iterdir()):
    if file.suffix == '.txt':
        text = file.read_text(encoding="utf-8")
        corrected = post_correct_text(text)
        file.write_text(corrected, encoding="utf-8")