Fix: OCR-Schwellwert nach Otsu statt fester Schwellwert nach Kontrastspreizung

Ein einzelnes Reflexpixel (Normalfall bei glaenzenden Metalletiketten)
dominierte bisher Minimum/Maximum der Kontrastspreizung und drueckte
Schrift und Untergrund gemeinsam unter den festen Schwellwert 128 - die
Schrift verschwand. Der Schwellwert wird jetzt per Otsu-Verfahren aus dem
Helligkeits-Histogramm des Bildes selbst bestimmt und bleibt dadurch gegen
einzelne Ausreisser unempfindlich. Ausserdem: fehlendes Kleinbuchstaben-x
in der Tesseract-Zeichenliste ergaenzt (Bestueckungsangabe "4DRx4").

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
vchuser
2026-07-28 16:53:42 +02:00
co-authored by Claude Opus 5
parent 65f699a704
commit 78592d03a1
2 changed files with 105 additions and 16 deletions
+42
View File
@@ -36,3 +36,45 @@ test('preprocess laesst Breite und Hoehe unveraendert', () => {
assert.equal(result.width, 3);
assert.equal(result.height, 1);
});
test('preprocess trennt Schrift vom Untergrund trotz einzelnem Reflexpixel', () => {
// Untergrund 60, Schrift 80, dazu ein einzelnes Reflexpixel 255.
// Ein fester Schwellwert nach Kontrastspreizung reisst hier ab: das eine
// Reflexpixel dominiert Minimum/Maximum und drueckt Untergrund und Schrift
// gemeinsam unter den Schwellwert - die Schrift verschwindet.
//
// Untergrund und Schrift stehen hier zu gleichen Teilen im Bild (je 200
// Pixel), so wie auf einem echten Foto Untergrund- und Schriftflaechen
// die grosse Masse der Pixel stellen und ein einzelner Reflex nur ein
// einzelnes Pixel unter vielen ist. Bei einem Miniaturbild mit nur ein-
// oder zweistelliger Pixelzahl waere der Reflex dagegen ein spuerbarer
// Bruchteil des gesamten Histogramms; dann kann selbst Otsu ihn nicht
// von echten Bildmerkmalen unterscheiden. Das ist keine Schwaeche des
// Verfahrens, sondern der Grund, warum die Aussage "ein einzelnes Pixel
// verschiebt das Histogramm kaum" erst bei realistischer Pixelzahl gilt.
const background = new Array(200).fill(60);
const text = new Array(200).fill(80);
const reflex = [255];
const result = preprocess(image([...background, ...text, ...reflex]));
assert.equal(result.data[0], 0, 'Untergrund bleibt schwarz');
assert.equal(result.data[199 * 4], 0, 'letztes Untergrundpixel bleibt schwarz');
assert.equal(result.data[200 * 4], 255, 'erstes Schriftpixel wird weiss');
assert.equal(result.data[399 * 4], 255, 'letztes Schriftpixel wird weiss');
});
test('preprocess liefert bei einem Bild ohne Pixel kein NaN und wirft nicht', () => {
const result = preprocess(image([]));
assert.equal(result.width, 0);
assert.equal(result.height, 1);
assert.equal(result.data.length, 0);
});
test('preprocess wirft nicht, wenn alle Pixel dieselbe Helligkeit haben', () => {
const result = preprocess(image([128, 128, 128, 128]));
for (let i = 0; i < result.data.length; i += 4) {
const value = result.data[i];
assert.ok(value === 0 || value === 255, `Pixel ${i / 4} ist ${value}`);
assert.ok(!Number.isNaN(value));
}
});