Geometrische Rektifizierung

TippGeometrie in einem Satz

Die geometrische Verarbeitung überführt das perspektivisch verzerrte Kamerabild — ein Trapez aus schrägem Aufnahmewinkel — in orthogonale, beschnittene Einzelseiten mit korrekter Leseausrichtung.

Jeder Scanlauf erzeugt ein HEIC-Master in voller Sensorauflösung. Dieses Master ist in aller Regel perspektivisch verzerrt (das Gerät hängt leicht schräg über dem Buch), enthält für Doppelseiten zwei Buchseiten mit einem Bundsteg in der Mitte und kann durch Geräteneigungs- und Papierverformungsartefakte leicht gedreht sein. Die geometrische Stufe (PageGeometry) löst alle diese Probleme in fünf aufeinanderfolgenden Schritten.

1 Schritt 1: Trapez-Erkennung auf dem Vollauflösungs-Master

Die Erkennung des Dokumentrahmens läuft auf dem vollen 48-MP-Master — nicht auf dem komprimierten 1080p-Vorschaustream, den das iPhone während der Aufnahme an den Mac überträgt. Das ist entscheidend: Der Vorschaustream liefert bereits einen vorläufigen Quad für das Live-Overlay, aber dieser wurde auf niedrig aufgelösten Daten gemessen und auf rechteckige Winkel gezwungen. Die endgültige Trapez-Erkennung braucht das echte Trapez, um eine korrekte Perspektivkorrektur rechnen zu können.

Auf macOS 26 und neuer wird DetectDocumentSegmentationRequest aus dem Vision-Framework verwendet. Die Erkennung arbeitet intern mit einem auf ca. 2048 px runtergesampelten Proxy (Lanczos-Filterung für Schärfeerhalt), liefert aber normierte Koordinaten, die auf den Vollauflösungs-Master zurückgerechnet werden. Das Ergebnis ist ein Trapez mit vier Eckpunkten (TL, TR, BR, BL), normiert in 0–1 mit Bottom-Left-Ursprung.

Falls die Erkennung fehlschlägt, fällt die Pipeline auf den Vorschau-Quad des iPhones zurück (iphone-live) und vermerkt dies im GeometryReport.

HinweisKonfidenz im GeometryReport

Der GeometryReport enthält das Feld quadSource ("mac-segmentation", "iphone-live" oder "none") und quadConfidence (Wert 0–1). Diese Felder gehören ausschließlich in die Diagnoseschicht (famulus-pipeline CLI oder das ausgeklappte “Erweitert”-Panel) — sie erscheinen nicht in der Primäroberfläche.

2 Schritt 2: Perspektivkorrektur

Der Trapez-Quad wird als Eingabe für CIPerspectiveCorrection verwendet. Der Filter rechnet die vier Eckpunkte auf ein achsenparalleles Rechteck um. Das Ergebnis ist ein Bild, das so aussieht, als wäre die Kamera senkrecht über dem Dokument gewesen.

Die Korrektheit der Perspektivkorrektur hängt direkt von der Qualität des Trapezes ab. Ein ungenauer Quad — zum Beispiel weil das Dokument auf einem gemusterten Untergrund liegt und nur schwer segmentierbar ist — erzeugt Kissenverzerrungen. Der schwarze Untergrund minimiert dieses Risiko, weil er das Segmentierungsnetz zuverlässig auf das Dokument fokussiert.

3 Schritt 3: Ausrichtung (Rotation)

Die App kennt drei Aufnahmeorientierungen, die der Nutzer vor der Aufnahme einstellt:

Orientierung Physische Lage Rotation
topOnRight Oberkante rechts (90° CW) 90° CCW
topOnLeft Oberkante links (270°) 90° CW
topOnTop Oberkante oben (0°) keine

Im Bucherfassungsmodus liegt das iPhone typischerweise quer über dem aufgeschlagenen Buch, damit die Kamera den gesamten Buchblock erfasst. topOnRight (Standard) entspricht der natürlichen Haltung des iPhones in Querformat mit Oberkante rechts.

Die Rotation wird über die CIOrientation-Eigenschaft des Core-Image-Koordinatensystems angewendet.

4 Schritt 4: Falzerkennung und Seitentrennung (Buchmode)

Bei Doppelseiten enthält das rektifizierte Bild nach den Schritten 1–3 beide Seiten nebeneinander, getrennt durch den Bundsteg (Falz). GutterFinder (FamulusRepro/Shared/Services/GutterFinder.swift) fusioniert drei unabhängige Signale, um die Falzlinie zu lokalisieren:

Signal Quelle Beitrag
Tiefe (depth) LiDAR AVDepthData Falz = tiefste Stelle (vom Kameraobjektiv am weitesten)
Schatten (shadow) Luminanzfaltung Helligkeitsminimum mit beidseitigem Anstieg
Textlücke (textgap) Tinten-Dichteprofil Breiteste leere Textspalte = Bundsteg-Bereich

Das Textlücken-Signal liefert das Suchfenster; Tiefe und Schatten verfeinern darin. gutterMethod im GeometryReport nennt die tatsächlich beitragenden Signale (z. B. "shadow+textgap" wenn kein LiDAR-Frame vorhanden). Die Suche ist auf die mittleren 40–60 % der Spreitebreite beschränkt (bandMin=0.40, bandMax=0.60), damit Randschatten nicht als Falz erkannt werden.

Die Falz-Fraktion wird auf [0.05, 0.95] eingeklemmt, damit kein Seitenzuschnitt mit null Breite entsteht. Ein Konfidenzschwellenwert >= 0.18 muss erreicht sein, bevor die Separator-Linie im Sucher gezeichnet wird; darunter bleibt sie gestrichelt bei exakt 0,5.

WichtigFalz kommt nach der Seite, nie davor

Der Falz wird auf dem bereits rotierten Master gesucht, nachdem beide Seiten-Quads erkannt wurden — nie auf dem rohen Kamerabild. GutterFinder teilt nie das Rohfoto; das würde den unkorrigierten Spread als ganzes JPEG schreiben (einschließlich Stuhl, Hände und Matte). Die korrekte Reihenfolge: 1. Master rotieren, 2. zwei Seiten-Quads erkennen, 3. je Seite perspektivisch aus dem vollen rotierten Frame extrahieren.

Der Bundsteg wird als Bruchteil der Spreitebreite (gutterFraction) im GeometryReport festgehalten. Jede Seite erhält ein eigenes page_NNN/-Verzeichnis in der PDAI-Ablage.

4.1 Reprocessing ohne liveQuad (Buchmode)

Beim Neu-Verarbeiten eines bestehenden Masters (ohne Live-iPhone-Quad) läuft der Zwei-Quad-Pfad:

  1. Master um die Aufnahmeorientierung rotieren
  2. DetectDocumentSegmentationRequest auf dem rotierten Frame → Quad für Seite A
  3. Seite A maskieren; detectSecondQuad auf dem Restframe → Quad für Seite B
  4. Beide Seiten nach Zentroid-X sortieren (links/rechts)
  5. perspectiveCorrect jede Seite aus dem vollen rotierten Frame (extractPage)

Fallback wenn weniger als zwei Quads: Paper-Mask-basierter Teilbereich oder geometrische Hälfte nach grobem Union-Perspektiv.

4.2 Einzelblatt-Hülle (Einzelblattmode)

Im Einzelblattmode sucht PageGeometry das weiße Rechteck auf dem schwarzen Untergrund und berechnet die geometrische Hülle (konvexes Hull des Papier-Bereichs). Die App zoomt auf diese Hülle heran — sie erwartet explizit ein kleines Rechteck unbekannter Nicht-A4-Größe auf der schwarzen Matte. Die Belichtungsmessung (ArchivalToneEngine) wird auf die Pixel innerhalb dieser Hülle beschränkt, nie auf den Rahmenmittelwert (der würde bei einem kleinen Beleg auf dunklem Untergrund überbelichten).

5 Schritt 5: Entzerrung (Deskew) und Textblock-Beschnitt

Nach der Trennung sind die Einzelseiten im Ideal achsenparallel — in der Praxis enthält jede Seite oft eine kleine Restdrehung durch Papierwölbung, leichte Neigungsreste oder Druckausrichtungsfehler.

Deskew: Das Verfahren dreht die Seite in 0,25°-Schritten und berechnet für jeden Drehwinkel die Varianz des horizontalen Projektionsprofils. Gut ausgerichtete Textzeilen erzeugen ein Projektionsprofil mit ausgeprägten Spitzen (die Textzeilen) und tiefen Tälern (die Zeilenabstände) — hohe Varianz. Das Optimum (Maximum der Varianz) ist der Ausrichtungswinkel. Die gemessene Drehung in Grad (deskewDegrees) wird im GeometryReport protokolliert.

Tight Crop: Zum Abschluss wird der Satzspiegel freigestellt — der Bereich, der tatsächlich Text enthält, ohne die weißen Ränder. Die engste Bounding Box der Textpixel wird als textBlockCrop (normiert, Bottom-Left-Ursprung) im GeometryReport abgelegt.

6 Quality-Gates

Nach Deskew und Tight-Crop berechnet measureResidualSkew (Projektionsprofil-Scorer) einen Quality-Report:

Gate Bedingung Chip
pass Residualneigung ≤ 0,15° kein Chip
residual Deskew hat die Neigung nicht korrigiert (> 0,15° verbleibend) Nachbessern
scale-mismatch Geschwister-Höhenunterschied > 6 % oder Seitenverhältnis > 8:1 Nachbessern
not-a-page Papierabdeckung < 35 % Neu aufnehmen
unmeasurable Kein Text für Messung gefunden (kein Chip, aber "unmeasurable" im JSON)

Ein Deskew, das abgelehnt wurde (Winkel > 8° ohne Korroboration), schreibt nicht mehr stillschweigend 0°, sondern setzt das Gate auf residual — der Nutzer sieht den Nachbessern-Chip statt einer scheinbar sauberen, aber schiefen Seite.

7 Das GeometryReport-Artefakt

Alle Entscheidungen der geometrischen Pipeline sind im GeometryReport-Struct festgehalten und werden im page_NNN.json der PDAI-Ablage persistiert:

GeometryReport — Geometrie-Protokoll einer Buchseite

{
  "sourceWidth": 8064,
  "sourceHeight": 6048,
  "quad": [[0.10, 0.08], [0.92, 0.06], [0.94, 0.94], [0.08, 0.95]],
  "quadSource": "mac-segmentation",
  "quadConfidence": 0.97,
  "perspectiveApplied": true,
  "rotationDegrees": 90,
  "gutterFraction": 0.502,
  "gutterMethod": "projection-minimum",
  "deskewDegrees": 0.25,
  "textBlockCrop": {"x": 0.04, "y": 0.03, "width": 0.92, "height": 0.94},
  "textBlockMethod": "luminance-threshold",
  "outputWidth": 2680,
  "outputHeight": 3520
}

Die Ausgabegröße (outputWidth, outputHeight) richtet sich nach dem Satzspiegel nach dem Tight-Crop und ist proportional zum erkannten Textblock.

8 Zusammenfassung der Transformationskette

flowchart LR
    A["HEIC Master\n(volle Aufl.)"] --> B["Trapez-Erkennung\nDetectDocumentSegmentation"]
    B --> C["Perspektivkorrektur\nCIPerspectiveCorrection"]
    C --> D["Rotation\nlaut Orientierung"]
    D -->|"Buchmode"| E["GutterFinder\nTiefe + Schatten + Textluecke"]
    E --> F["Seite links\npage NNN"]
    E --> G["Seite rechts\npage NNN plus 1"]
    D -->|"Belegmode"| H["Papier-Huelle\nEinzelblatt-Hull"]
    F --> I["Deskew\nProjectionVarianzOptimum"]
    G --> I
    H --> I
    I --> J["Tight Crop + Quality-Gate\npass / residual / not-a-page"]