Der Detektor erkennt Behördenstempel auf gescannten Dokumenten und Fotos. Er verwendet einen zweistufigen Ansatz: farbbasierte Erkennung als primäre Methode und einen Graustufen-Kreisdetektor als Fallback. Beide Stufen wenden Formfilter, Positionsheuristiken und Dichtebewertung an, um Kandidaten zu bewerten und den besten Treffer auszuwählen.
Eingabe (Bild oder PDF-Seite)
│
├─► Stufe 1: Farbbasierte Erkennung
│ │
│ ├─ 1. HSV-Farbmaskierung (rote, blaue, lila Tinte)
│ ├─ 2. Morphologische Bereinigung (Lücken schließen, Rauschen entfernen)
│ ├─ 3. Konturextraktion + Formfilter
│ ├─ 4. Überlappende Bounding-Boxen zusammenführen
│ ├─ 5. Bewertung: Farbdichte × Positionsgewicht
│ └─ 6. Besten Kandidaten auswählen
│
├─► Stufe 2: Graustufen-Kreis-Fallback (nur wenn Stufe 1 nichts findet)
│ │
│ ├─ 1. Gaußscher Weichzeichner + Canny-Kantenerkennung
│ ├─ 2. Hough-Kreiserkennung
│ ├─ 3. Tintendichte-Filterung
│ ├─ 4. Bogenkontinuität-Bewertung
│ ├─ 5. Bewertung: Dichtescore × Bogenkontinuität × Positionsgewicht
│ └─ 6. Besten Kandidaten auswählen (Score > 0,40)
│
└─► OCR (EasyOCR) auf erkannter Stempelregion
Das Bild wird in den HSV-Farbraum konvertiert. Eine Binärmaske wird erstellt, indem alle Pixel kombiniert werden, die in einen der folgenden Stempeltinten-Bereiche fallen:
| Farbe | Farbton (H) | Sättigung (S) | Hellwert (V) |
|---|---|---|---|
| Rot | 0–10 | 100–255 | 50–255 |
| Rot | 160–180 | 100–255 | 50–255 |
| Blau | 100–130 | 100–255 | 50–255 |
| Lila | 130–160 | 100–255 | 50–255 |
Die Mindestsättigung von 100 stellt sicher, dass schwarzer/grauer Text und blasse Druckfarben ausgeschlossen werden — nur kräftige Stempeltinten passieren den Filter.
Zwei morphologische Operationen mit einem 11×11 elliptischen Kernel:
- CLOSE (4 Iterationen) — füllt kleine Lücken in der Stempeltinte (z. B. verblasste Bereiche)
- OPEN (3 Iterationen) — entfernt kleine Sprenkel und Rauschen
Externe Konturen werden aus der bereinigten Maske extrahiert. Jede Kontur muss alle folgenden Filter bestehen:
| Filter | Schwellwert | Zweck |
|---|---|---|
| Fläche | 15 000–250 000 px² | Rauschen (zu klein) und große Farbflächen abweisen |
| Seitenverhältnis | < 2,5 | Langgestreckte Textzeilen abweisen |
| Rundheit | >= 0,30 | Nicht-kreisförmige Formen abweisen (1,0 = perfekter Kreis) |
| Farbdichte | >= 3 % | Bounding-Box muss ausreichend Tinte enthalten |
Die Rundheit ist definiert als:
Rundheit = 4π × Fläche / Umfang²
Ein perfekter Kreis ergibt 1,0; langgestreckte Textzeilen liegen deutlich unter 0,30.
Jede bestehende Kontur erhält eine gepolsterte Bounding-Box (+20 px auf jeder Seite, begrenzt auf die Bildränder) für zusätzlichen OCR-Kontext.
Überlappende Erkennungen werden mittels Fixpunkt-IoU-Iteration zusammengeführt:
- Wenn zwei Boxen mit IoU > 15 % überlappen, werden sie zur Vereinigung zusammengeführt
- Die Iteration wiederholt sich, bis keine weiteren Zusammenführungen stattfinden
- Der Fixpunkt-Ansatz verhindert „Schneeballeffekte" durch verkettete Zusammenführungen
Jeder Kandidat erhält einen Gesamtscore:
Gesamtscore = Farbdichte × Positionsgewicht
Dabei ist:
- Farbdichte = Anteil der Pixel innerhalb der Bounding-Box, die farbige Tinte sind
- Positionsgewicht = vertikaler Positionsfaktor (siehe Positionsheuristiken)
Kandidaten werden nach Gesamtscore sortiert (absteigend). Nur der beste Kandidat pro Dokument wird behalten:
- Einzelbilder: 1 bester Kandidat
- Mehrseitige PDFs: 1 bester Kandidat über alle Seiten hinweg
Wird nur aktiviert, wenn Stufe 1 keine farbigen Stempelkandidaten findet. Entwickelt für schwarze oder graue Stempel, die den Farbfilter nicht auslösen.
Bild → Graustufen → Gaußscher Weichzeichner (9×9, σ=2) → Canny (50, 150)
Parameter:
| Parameter | Wert |
|---|---|
| dp | 1,2 |
| minDist | 200 px |
| param1 | 100 |
| param2 | 80 |
| minRadius | 60 px |
| maxRadius | 200 px |
Erwartete Stempelgröße bei 150 DPI: Radius 60–200 px (Durchmesser ca. 2–7 cm).
Für jeden erkannten Kreis wird eine binarisierte Tintenmaske erstellt (Schwellwert 160, invertiert). Der Anteil dunkler Pixel innerhalb der Bounding-Box des Kreises muss folgende Bedingung erfüllen:
8 % <= Tintendichte <= 60 %
- Unter 8 %: zu blass, wahrscheinlich kein Stempel
- Über 60 %: zu dicht, wahrscheinlich ein Textblock oder eine Vollgrafik
Misst, wie viel des Kreisumfangs einen sichtbar gezeichneten Rand aufweist:
- 72 Punkte gleichmäßig entlang des Umfangs abtasten (alle 5 Grad)
- An jedem Punkt eine 5×5-Nachbarschaft auf Canny-Kantenpixel prüfen
- Den längsten aufeinanderfolgenden Lauf von Treffern finden (mit Umlauf)
- Score = längster_Lauf / 72
Typische Werte:
- Echte Stempel mit gezeichnetem Rand: 0,6–1,0
- Fehlerkennungen aus Text: 0,05–0,30
Dichtescore = max(0,0; 1,0 − |Tintendichte − 0,25| / 0,35)
Score = Dichtescore × Bogenkontinuität × Positionsgewicht
- Dichtescore erreicht sein Maximum bei Tintendichte = 0,25 (ideale Stempeldichte) und fällt an den Extremen auf null
- Bogenkontinuität belohnt Kreise mit sichtbar gezeichnetem Rand
- Positionsgewicht wendet dieselbe Positionsheuristik wie in Stufe 1 an
Nur Kandidaten mit Score > 0,40 werden zurückgegeben.
Behördenstempel werden fast immer im unteren Bereich eines Dokuments platziert, in der Nähe von Unterschriften. Logos und Briefköpfe befinden sich im Kopfbereich. Das Positionsgewicht nutzt dies, um Fehlerkennungen im Kopfbereich abzuweisen und Kandidaten im unteren Seitenbereich zu bevorzugen.
y_Verhältnis = BBox_Mittelpunkt_y / Bildhöhe (0,0 = oben, 1,0 = unten)
| Zone | y_Verhältnis | Gewicht |
|---|---|---|
| Kopfbereich (Abweisung) | 0,0–0,15 | 0,0 (verworfen) |
| Oberer Inhaltsbereich | 0,15–0,50 | 0,50–0,71 |
| Unterer Inhaltsbereich | 0,50–0,80 | 0,71–0,88 |
| Fußbereich (Unterschriften) | 0,80–1,0 | 0,88–1,0 |
Das Gewicht steigt linear von 0,5 (knapp unter dem Kopfbereich) auf 1,0 (Seitenende):
t = (y_Verhältnis − 0,15) / (1,0 − 0,15)
Positionsgewicht = 0,5 + 0,5 × t
Beispiel mit zwei konkurrierenden Kandidaten:
| Kandidat | Farbdichte | Position (y_Verhältnis) | Positionsgewicht | Gesamtscore |
|---|---|---|---|---|
| Kopfzeilen-Logo | 0,12 | 0,08 | 0,0 | verworfen |
| Stempel | 0,08 | 0,75 | 0,85 | 0,068 |
Das Logo wird trotz höherer Farbdichte abgewiesen. Der Stempel gewinnt.
Nachdem der beste Stempelkandidat ausgewählt wurde, wird seine Bounding-Box-Region zugeschnitten und an EasyOCR (nur CPU) zur Textextraktion übergeben:
- Sprachen: Deutsch + Englisch (konfigurierbar)
- Alle erkannten Textfragmente werden mit Leerzeichen verkettet
{
"filename": "dokument.jpg",
"stamp_count": 1,
"stamps": [
{
"bbox": [x1, y1, x2, y2],
"text": "IHK Elbe-Weser ...",
"image": "<Base64-kodiertes PNG>"
}
]
}{
"filename": "dokument.pdf",
"total_pages": 3,
"total_stamps": 1,
"pages": [
{ "page": 1, "stamp_count": 0, "stamps": [] },
{ "page": 2, "stamp_count": 0, "stamps": [] },
{
"page": 3,
"stamp_count": 1,
"stamps": [
{
"bbox": [x1, y1, x2, y2],
"text": "...",
"image": "<Base64>"
}
]
}
]
}| Konstante | Standard | Beschreibung |
|---|---|---|
MIN_STAMP_AREA |
15 000 | Minimale Konturfläche in px² |
MAX_STAMP_AREA |
250 000 | Maximale Konturfläche in px² |
MIN_CIRCULARITY |
0,30 | Mindest-Rundheit (4πA/P²) |
MAX_ASPECT_RATIO |
2,5 | Maximales Seitenverhältnis (Breite/Höhe) |
REGION_PADDING |
20 | Randpuffer der Bounding-Box in px |
IOU_MERGE_THRESH |
0,15 | IoU-Schwellwert zum Zusammenführen überlappender Boxen |
MIN_COLOUR_DENSITY |
0,03 | Mindestanteil farbiger Pixel in der BBox |
MAX_STAMPS_PER_PAGE |
1 | Maximale Anzahl zurückgegebener Stempelkandidaten |
HEADER_ZONE |
0,15 | Oberer Seitenanteil als Kopfbereich (Abweisung) |
POSITION_WEIGHT_MIN |
0,5 | Positionsgewicht am oberen Rand des Inhaltsbereichs |
POSITION_WEIGHT_MAX |
1,0 | Positionsgewicht am Seitenende |
HOUGH_MIN_RADIUS |
60 | Minimaler Kreisradius für Graustufen-Fallback |
HOUGH_MAX_RADIUS |
200 | Maximaler Kreisradius für Graustufen-Fallback |
MIN_INK_DENSITY |
0,08 | Mindestanteil dunkler Pixel (Graustufen) |
MAX_INK_DENSITY |
0,60 | Maximalanteil dunkler Pixel (Graustufen) |