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>
81 lines
3.4 KiB
JavaScript
81 lines
3.4 KiB
JavaScript
import { test } from 'node:test';
|
|
import assert from 'node:assert/strict';
|
|
import { preprocess } from '../src/ocr.js';
|
|
|
|
/** Minimaler ImageData-Ersatz, damit der Test ohne Browser laeuft. */
|
|
function image(pixels) {
|
|
const data = new Uint8ClampedArray(pixels.length * 4);
|
|
pixels.forEach((value, i) => {
|
|
data[i * 4] = value;
|
|
data[i * 4 + 1] = value;
|
|
data[i * 4 + 2] = value;
|
|
data[i * 4 + 3] = 255;
|
|
});
|
|
return { width: pixels.length, height: 1, data };
|
|
}
|
|
|
|
test('preprocess erzeugt ein reines Schwarz-Weiss-Bild', () => {
|
|
const result = preprocess(image([10, 40, 200, 250]));
|
|
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.equal(result.data[i + 1], value);
|
|
assert.equal(result.data[i + 2], value);
|
|
assert.equal(result.data[i + 3], 255);
|
|
}
|
|
});
|
|
|
|
test('preprocess trennt dunkle von hellen Pixeln', () => {
|
|
const result = preprocess(image([10, 40, 200, 250]));
|
|
assert.equal(result.data[0], 0, 'dunkelstes Pixel wird schwarz');
|
|
assert.equal(result.data[12], 255, 'hellstes Pixel wird weiss');
|
|
});
|
|
|
|
test('preprocess laesst Breite und Hoehe unveraendert', () => {
|
|
const result = preprocess(image([0, 128, 255]));
|
|
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));
|
|
}
|
|
});
|