Files
breakpilot-lehrer/docs-src/services/klausur-service/OCR-Pipeline.md
Benjamin Admin 970ec1f548 docs: OCR-Pipeline v2.0.0 – alle Optimierungen 2026-03-03 dokumentiert
- Schritte 6–8 jetzt vollständig dokumentiert (nicht mehr "Geplant")
- Step 3: Full-Width-Scan, Phantom-Filter-Detail
- Step 4: Artefakt-Zeilen, Gap-Healing
- Step 6: Spell Checker, Char Confusion (_fix_character_confusion),
  SSE-Protokoll, Env-Vars (REVIEW_ENGINE, OLLAMA_REVIEW_*)
- Step 7: Rekonstruktions-Canvas, leere Zellen editierbar
- Dependencies-Tabelle mit pyspellchecker als neue Dependency
- Änderungshistorie mit allen 2026-03-03 Commits

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-03 18:42:25 +01:00

18 KiB
Raw Blame History

OCR Pipeline - Schrittweise Seitenrekonstruktion

Version: 2.0.0 Status: Produktiv (Schritte 18 implementiert) URL: https://macmini:3002/ai/ocr-pipeline

Uebersicht

Die OCR Pipeline zerlegt den OCR-Prozess in 8 einzelne Schritte, um eingescannte Vokabelseiten aus mehrspaltig gedruckten Schulbuechern Wort fuer Wort zu rekonstruieren. Jeder Schritt kann individuell geprueft, korrigiert und mit Ground-Truth-Daten versehen werden.

Ziel: 10 Vokabelseiten fehlerfrei rekonstruieren.

Pipeline-Schritte

Schritt Name Beschreibung Status
1 Begradigung (Deskew) Scan begradigen (Hough Lines + Word Alignment) Implementiert
2 Entzerrung (Dewarp) Buchwoelbung entzerren (Vertikalkanten-Analyse) Implementiert
3 Spaltenerkennung Unsichtbare Spalten finden (Projektionsprofile + Wortvalidierung) Implementiert
4 Zeilenerkennung Horizontale Zeilen + Kopf-/Fusszeilen-Klassifikation + Luecken-Heilung Implementiert
5 Worterkennung Grid aus Spalten x Zeilen, OCR pro Zelle, Post-Processing Implementiert
6 Korrektur Zeichenverwirrung + regel-basierte Rechtschreibkorrektur (SSE-Stream) Implementiert
7 Rekonstruktion Interaktive Zellenbearbeitung auf Bildhintergrund Implementiert
8 Validierung Ground-Truth-Vergleich und Qualitaetspruefung Implementiert

Architektur

Admin-Lehrer (Next.js)          klausur-service (FastAPI :8086)
┌────────────────────┐          ┌─────────────────────────────┐
│ /ai/ocr-pipeline   │          │ /api/v1/ocr-pipeline/       │
│                    │  REST    │                             │
│ PipelineStepper    │◄────────►│ Sessions CRUD               │
│ StepDeskew         │          │ Image Serving               │
│ StepDewarp         │   SSE    │ Deskew/Dewarp/Columns/Rows  │
│ StepColumnDetection│◄────────►│ Word Recognition            │
│ StepRowDetection   │          │ Correction (Spell-Checker)  │
│ StepWordRecognition│          │ Reconstruction              │
│ StepLlmReview      │          │ Ground Truth                │
│ StepReconstruction │          └─────────────────────────────┘
│ StepGroundTruth    │                    │
└────────────────────┘                    ▼
                               ┌─────────────────────┐
                               │ PostgreSQL           │
                               │ ocr_pipeline_sessions│
                               │ (Images + JSONB)     │
                               └─────────────────────┘

Dateistruktur

klausur-service/backend/
├── ocr_pipeline_api.py              # FastAPI Router (alle Endpoints)
├── ocr_pipeline_session_store.py    # PostgreSQL Persistence
├── cv_vocab_pipeline.py             # Computer Vision + NLP Algorithmen
└── migrations/
    ├── 002_ocr_pipeline_sessions.sql # Basis-Schema
    ├── 003_add_row_result.sql        # Row-Result Spalte
    └── 004_add_word_result.sql       # Word-Result Spalte

admin-lehrer/
├── app/(admin)/ai/ocr-pipeline/
│   ├── page.tsx                      # Haupt-Page mit Session-Management
│   └── types.ts                      # TypeScript Interfaces
└── components/ocr-pipeline/
    ├── PipelineStepper.tsx            # Fortschritts-Stepper
    ├── StepDeskew.tsx                 # Schritt 1: Begradigung
    ├── StepDewarp.tsx                 # Schritt 2: Entzerrung
    ├── StepColumnDetection.tsx        # Schritt 3: Spaltenerkennung
    ├── StepRowDetection.tsx           # Schritt 4: Zeilenerkennung
    ├── StepWordRecognition.tsx        # Schritt 5: Worterkennung
    ├── StepLlmReview.tsx              # Schritt 6: Korrektur (SSE-Stream)
    ├── StepReconstruction.tsx         # Schritt 7: Rekonstruktion (Canvas)
    └── StepGroundTruth.tsx            # Schritt 8: Validierung

API-Referenz

Alle Endpoints unter /api/v1/ocr-pipeline/.

Sessions

Methode Pfad Beschreibung
POST /sessions Neue Session erstellen (Bild hochladen)
GET /sessions Alle Sessions auflisten
GET /sessions/{id} Session-Info mit allen Step-Results
PUT /sessions/{id} Session umbenennen
DELETE /sessions/{id} Session loeschen

Bilder

Methode Pfad Beschreibung
GET /sessions/{id}/image/original Originalbild
GET /sessions/{id}/image/deskewed Begradigtes Bild
GET /sessions/{id}/image/dewarped Entzerrtes Bild
GET /sessions/{id}/image/binarized Binarisiertes Bild
GET /sessions/{id}/image/columns-overlay Spalten-Overlay
GET /sessions/{id}/image/rows-overlay Zeilen-Overlay
GET /sessions/{id}/image/words-overlay Wort-Grid-Overlay

Schritt 1: Begradigung

Methode Pfad Beschreibung
POST /sessions/{id}/deskew Automatische Begradigung
POST /sessions/{id}/deskew/manual Manuelle Winkelkorrektur
POST /sessions/{id}/ground-truth/deskew Ground Truth speichern

Schritt 2: Entzerrung

Methode Pfad Beschreibung
POST /sessions/{id}/dewarp Automatische Entzerrung
POST /sessions/{id}/dewarp/manual Manueller Scherbungswinkel
POST /sessions/{id}/ground-truth/dewarp Ground Truth speichern

Schritt 3: Spalten

Methode Pfad Beschreibung
POST /sessions/{id}/columns Automatische Spaltenerkennung
POST /sessions/{id}/columns/manual Manuelle Spalten-Definition
POST /sessions/{id}/ground-truth/columns Ground Truth speichern

Schritt 4: Zeilen

Methode Pfad Beschreibung
POST /sessions/{id}/rows Automatische Zeilenerkennung
POST /sessions/{id}/rows/manual Manuelle Zeilen-Definition
POST /sessions/{id}/ground-truth/rows Ground Truth speichern
GET /sessions/{id}/ground-truth/rows Ground Truth abrufen

Schritt 5: Worterkennung

Methode Pfad Beschreibung
POST /sessions/{id}/words Wort-Grid aus Spalten x Zeilen erstellen
POST /sessions/{id}/ground-truth/words Ground Truth speichern
GET /sessions/{id}/ground-truth/words Ground Truth abrufen

Schritt 6: Korrektur

Methode Pfad Beschreibung
POST /sessions/{id}/llm-review?stream=true SSE-Stream Korrektur starten
POST /sessions/{id}/llm-review/apply Ausgewaehlte Korrekturen speichern

Schritt 7: Rekonstruktion

Methode Pfad Beschreibung
POST /sessions/{id}/reconstruction Zellaenderungen speichern

Schritt 3: Spaltenerkennung (Detail)

Algorithmus: detect_column_geometry()

Zweistufige Erkennung: vertikale Projektionsprofile finden Luecken, Wort-Bounding-Boxes validieren.

Bild → Binarisierung → Vertikalprofil → Lueckenerkennung → Wort-Validierung → ColumnGeometry

Wichtige Implementierungsdetails:

  • Initialer Tesseract-Scan: Laeuft auf der vollen Bildbreite [left_x : w] (nicht nur bis zur Content-Grenze right_x), damit Woerter am rechten Rand der letzten Spalte nicht uebersehen werden.
  • Letzte Spalte: Wird immer bis zur vollen Bildbreite w ausgedehnt, nicht nur bis zur erkannten Content-Grenze.
  • Phantom-Spalten-Filter (Step 9): Spalten mit Breite < 3 % der Content-Breite UND < 3 Woerter werden als Artefakte entfernt; die angrenzenden Spalten schliessen die Luecke.
  • Spaltenzuweisung: Woerter werden anhand des groessten horizontalen Ueberlappungsbereichs einer Spalte zugeordnet.

Konfigurierbare Parameter

# Mindestbreite fuer echte Spalten (automatisch: max(20px, 3% content_w))
min_real_col_w = max(20, int(content_w * 0.03))

Schritt 4: Zeilenerkennung (Detail)

Algorithmus: detect_row_geometry()

Horizontale Projektionsprofile finden Zeilen-Luecken; word-level Validierung verhindert Fehlschnitte.

Zusaetzliche Post-Processing-Schritte:

  1. Artefakt-Zeilen entfernen (_is_artifact_row): Zeilen, in denen alle erkannten Tokens nur 1 Zeichen lang sind (Scan-Schatten, leere Zeilen), werden als Artefakte klassifiziert und aus dem Grid entfernt.

  2. Luecken-Heilung (_heal_row_gaps): Nach dem Entfernen leerer/Artefakt-Zeilen werden die verbleibenden Zeilen auf die Mitte der entstehenden Luecke ausgedehnt, damit kein Zeileninhalt durch schrumpfende Grenzen abgeschnitten wird.

def _is_artifact_row(row: RowGeometry) -> bool:
    """Zeile ist Artefakt wenn alle Tokens <= 1 Zeichen."""
    if row.word_count == 0: return True
    return all(len(w.get('text','').strip()) <= 1 for w in row.words)

def _heal_row_gaps(rows, top_bound, bottom_bound):
    """Verbleibende Zeilen auf Mitte der Luecken ausdehnen."""
    ...

Schritt 5: Worterkennung (Detail)

Algorithmus: build_cell_grid()

Schritt 5 nutzt die Ergebnisse von Schritt 3 (Spalten) und Schritt 4 (Zeilen), um ein Grid zu erstellen und jede Zelle per OCR auszulesen.

Spalten (Step 3):    column_en  |  column_de  |  column_example
                     ───────────┼─────────────┼────────────────
Zeilen (Step 4):  R0 │  hello   │  hallo      │  Hello, World!
                  R1 │  world   │  Welt       │  The whole world
                  R2 │  book    │  Buch       │  Read a book
                     ───────────┼─────────────┼────────────────

Ablauf:

  1. Initialer Scan: Ganzes Bild einmal per Tesseract/RapidOCR → alle Wort-Bboxes
  2. Zuweisung: Jedes Wort der Spalte mit groesstem horizontalem Ueberlapp zuordnen
  3. Zell-OCR Fallback: Leere Zellen bekommen eigenen Crop + erneuten OCR-Aufruf (PSM 6/7)
  4. Batch-Spalten-OCR: Bei vielen leeren Zellen in einer Spalte: gesamte Spalte einmal OCR-en
  5. Post-Processing: Continuation-Rows zusammenfuehren, Lautschrift erkennen, Komma-Eintraege splitten

Post-Processing Pipeline (in build_vocab_pipeline_streaming)

# Schritt Funktion Beschreibung
0a Lautschrift-Fortsetzung _merge_phonetic_continuation_rows IPA-only Folgezeilen zusammenfuehren
0b Zeilen-Fortsetzung _merge_continuation_rows Zeilen mit Kleinbuchstaben-Anfang zusammenfuehren
2 Lautschrift-Fix _fix_phonetic_brackets OCR-Lautschrift mit Woerterbuch-IPA ersetzen
3 Komma-Split _split_comma_entries break, broke, broken → 3 Eintraege
4 Beispielsaetze _attach_example_sentences Beispielsatz-Zeilen an vorangehenden Eintrag haengen

!!! info "Zeichenkorrektur in Schritt 6" Die Zeichenverwirrungskorrektur (|I, 1I, 8B) laeuft nicht in Schritt 5, sondern als erstes in Schritt 6 (Korrektur), damit die Aenderungen im UI sichtbar und rueckgaengig machbar sind.


Schritt 6: Korrektur (Detail)

Korrektur-Engine

Schritt 6 kombiniert zwei Korrektur-Stufen, beide als SSE-Stream:

Stufe 1 — Zeichenverwirrungskorrektur (_fix_character_confusion):

OCR-Fehler Korrektur Regel
|ch Ich | am Wortanfang vor Kleinbuchstaben → I
| want I want Alleinstehendes |I
8en Ben 8 am Wortanfang vor enB
1 want I want Alleinstehendes 1I (NICHT vor . oder ,)
1. Kreuz unveraendert 1. = Listennummer, wird nicht korrigiert

Stufe 2 — Regel-basierte Rechtschreibkorrektur (spell_review_entries_streaming):

Nutzt pyspellchecker (MIT-Lizenz) mit EN+DE-Woerterbuch. Pro Token mit verdaechtigem Zeichen (0, 1, 5, 6, 8, |) werden Kandidaten geprueft:

_SPELL_SUBS = {
    '0': ['O', 'o'], '1': ['l', 'I'], '5': ['S', 's'],
    '6': ['G', 'g'], '8': ['B', 'b'], '|': ['I', 'l', '1'],
}

Logik: Kandidaten werden durch Woerterbuch-Lookup validiert. Strukturregel: Verdaechtiges Zeichen an Position 0 + Rest klein → erstes Substitut (z.B. 8enBen).

Umgebungsvariablen

Variable Default Beschreibung
REVIEW_ENGINE spell Korrektur-Engine: spell oder llm
OLLAMA_REVIEW_MODEL qwen3:0.6b Ollama-Modell (nur wenn REVIEW_ENGINE=llm)
OLLAMA_REVIEW_BATCH_SIZE 20 Eintraege pro LLM-Aufruf

SSE-Protokoll

POST /sessions/{id}/llm-review?stream=true

Events:
  data: {"type": "meta", "total_entries": 96, "to_review": 80, "skipped": 16, "model": "spell"}
  data: {"type": "batch", "changes": [...], "entries_reviewed": [0,1,2,...], "progress": {...}}
  data: {"type": "complete", "duration_ms": 234}
  data: {"type": "error", "detail": "..."}

Change-Format:
  {"row_index": 5, "field": "english", "old": "| want", "new": "I want"}

Schritt 7: Rekonstruktion (Detail)

Interaktiver Canvas-Editor: Das entzerrte Originalbild wird mit 30 % Opazitaet als Hintergrund angezeigt, alle Grid-Zellen (auch leere!) werden als editierbare Textfelder darueber gelegt.

Features:

  • Alle Zellen editierbar — auch leere Zellen (kein Filter mehr)
  • Farbkodierung nach Spaltentyp (Blau=EN, Gruen=DE, Orange=Beispiel)
  • Leere Pflichtfelder (EN/DE) rot gestrichelt markiert
  • Undo/Redo (Ctrl+Z / Ctrl+Shift+Z)
  • Tab-Navigation durch alle Zellen (inkl. leerer)
  • Zoom 50200 %
  • Per-Zell-Reset-Button bei geaenderten Zellen
POST /sessions/{id}/reconstruction
Body: {"cells": [{"cell_id": "r5_c2", "text": "corrected text"}]}

Datenbank-Schema

CREATE TABLE ocr_pipeline_sessions (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    name VARCHAR(255),
    filename VARCHAR(255),
    status VARCHAR(50) DEFAULT 'active',
    current_step INT DEFAULT 1,

    -- Bilder (BYTEA)
    original_png BYTEA,
    deskewed_png BYTEA,
    binarized_png BYTEA,
    dewarped_png BYTEA,

    -- Step-Results (JSONB)
    deskew_result JSONB,
    dewarp_result JSONB,
    column_result JSONB,
    row_result JSONB,
    word_result JSONB,   -- enthaelt vocab_entries, cells, llm_review

    -- Ground Truth + Meta
    ground_truth JSONB,
    auto_shear_degrees REAL,
    created_at TIMESTAMP DEFAULT NOW(),
    updated_at TIMESTAMP DEFAULT NOW()
);

word_result JSONB-Struktur:

{
  "vocab_entries": [...],
  "cells": [{"cell_id": "r0_c0", "text": "hello", "bbox_pct": {...}, ...}],
  "columns_used": [...],
  "llm_review": {
    "changes": [{"row_index": 5, "field": "english", "old": "...", "new": "..."}],
    "model_used": "spell",
    "duration_ms": 234
  }
}

Abhaengigkeiten

Python (klausur-service)

Paket Version Lizenz Zweck
pytesseract ≥0.3.10 Apache-2.0 Haupt-OCR (Schritt 35)
opencv-python-headless ≥4.8.0 Apache-2.0 Bildverarbeitung, Projektionsprofile
Pillow ≥10.0.0 HPND (MIT-kompatibel) Bildkonvertierung
rapidocr latest Apache-2.0 Schnelles OCR (ARM64 via ONNX)
onnxruntime latest MIT ONNX-Inferenz fuer RapidOCR
pyspellchecker ≥0.8.1 MIT Regel-basierte OCR-Korrektur (Schritt 6)
eng-to-ipa latest MIT IPA-Lautschrift-Lookup (Schritt 5)

!!! info "pyspellchecker (neu seit 2026-03)" pyspellchecker (MIT-Lizenz) ersetzt die LLM-basierte Korrektur als Standard-Engine. EN+DE-Woerterbuch, ~134k Woerter. Kein Ollama notig. Umschaltbar via REVIEW_ENGINE=llm fuer den LLM-Pfad.


Bekannte Einschraenkungen

Problem Ursache Workaround
Schraeg gedruckte Seiten Deskew erkennt Text-Rotation, nicht Seiten-Rotation Manueller Winkel
Sehr kleine Schrift (< 8pt) Tesseract PSM 7 braucht min. Zeichengroesse Vorher zoomen
Handgeschriebene Eintraege Tesseract/RapidOCR sind fuer Druckschrift optimiert TrOCR-Engine (geplant)
Mehr als 4 Spalten Projektionsprofil kann verschmelzen Manuelle Spalten

Deployment

# 1. Git push
git push origin main

# 2. Mac Mini pull + build
ssh macmini "cd /Users/benjaminadmin/Projekte/breakpilot-lehrer && git pull --no-rebase origin main"

# klausur-service (Backend) — bei requirements.txt Aenderungen: klausur-base neu bauen
ssh macmini "cd /Users/benjaminadmin/Projekte/breakpilot-lehrer && \
  /usr/local/bin/docker compose build klausur-service && \
  /usr/local/bin/docker compose up -d klausur-service"

# admin-lehrer (Frontend)
ssh macmini "cd /Users/benjaminadmin/Projekte/breakpilot-lehrer && \
  /usr/local/bin/docker compose build admin-lehrer && \
  /usr/local/bin/docker compose up -d admin-lehrer"

# 3. Testen unter:
# https://macmini:3002/ai/ocr-pipeline

!!! warning "Base-Image bei neuen Python-Paketen" Wenn requirements.txt geaendert wird (z.B. neues Paket hinzugefuegt), muss zuerst das Base-Image neu gebaut werden: bash ssh macmini "cd ~/Projekte/breakpilot-lehrer && \ /usr/local/bin/docker build -f klausur-service/Dockerfile.base \ -t klausur-base:latest klausur-service/"


Aenderungshistorie

Datum Version Aenderung
2026-03-03 2.0.0 Schritte 67 implementiert; Spell-Checker, Rekonstruktions-Canvas
2026-03-03 1.5.0 Spaltenerkennung: volle Bildbreite fuer initialen Scan, Phantom-Filter
2026-03-03 1.4.0 Zeilenerkennung: Artefakt-Zeilen entfernen + Luecken-Heilung
2026-03-03 1.3.0 Zeichenkorrektur: 1./|. Listenpraefixe werden nicht zu I.
2026-03-03 1.2.0 LLM-Engine durch Spell-Checker ersetzt (REVIEW_ENGINE=spell)
2026-02-28 1.0.0 Schritt 5 (Worterkennung) implementiert
2026-02-22 0.4.0 Schritt 4 (Zeilenerkennung) implementiert
2026-02-20 0.3.0 Schritt 3 (Spaltenerkennung) mit Typ-Klassifikation
2026-02-15 0.2.0 Schritt 2 (Entzerrung/Dewarp)
2026-02-12 0.1.0 Schritt 1 (Begradigung/Deskew) + Session-Management