diff --git a/README.md b/README.md index 4d04027..38d1eed 100644 --- a/README.md +++ b/README.md @@ -182,21 +182,40 @@ keine Daten verlassen das Gerät. Stapel-Zuweisung; die Zahl wird nirgends gespeichert, nicht einmal flüchtig für die Dauer der Sitzung. - Kein Bilderkennungsmodell: Der Ausschnitt wird über denselben - **Otsu-Schwellwert** wie die Texterkennung in zwei Klassen geteilt - (`threshold.js`, aus `ocr.js` herausgezogen, damit beide Funktionen sich - dieselbe Schwellwertbestimmung teilen, ohne dass die reine Zählung an - `tesseract.js` hinge). Die flächenmäßig **kleinere** der beiden Klassen - gilt als Objekt — die ausgelegten Teile belegen so gut wie immer weniger - Fläche als der Untergrund, unabhängig davon, ob sie heller oder dunkler - sind als dieser; der Nutzer muss nichts einstellen. Anschließend werden - zusammenhängende Objektflächen ermittelt (Connected-Component-Labeling - mit einer eigenen Arbeitsliste statt Rekursion, damit ein rund eine - Million Bildpunkte großer Ausschnitt nicht in den Stapelüberlauf läuft), - winzige Flächen als Rauschen verworfen und auffällig große Flächen - (mehrere berührende Teile) auf ein Vielfaches der typischen - Einzelteilgröße hochgerechnet. Details und Begründung der verwendeten - Schwellen: `.superpowers/sdd/counting-report.md`. + Kein Bilderkennungsmodell: Der **Otsu-Schwellwert** (`threshold.js`), + den die Texterkennung weiterhin nutzt, taugt für „Zählen" nicht — er + setzt voraus, dass Objekt- und Untergrundfläche etwa gleich groß sind. + Am echten Foto des Auftraggebers (rund zwanzig silberne + Festplattenschrauben auf dunklem Leder) belegen die Schrauben unter + einem Prozent der Fläche; Otsu rutschte deshalb auf den höchsten + Helligkeitswert und erklärte nur die hellsten Punkte der Ledernarbung + zum Objekt (gemessenes Fehlverhalten: 76712 statt rund zwanzig). Über + die Helligkeit allein sind Schraube und Untergrund an diesem Foto gar + nicht trennbar. + + `countObjects` (`src/count-objects.js`) verwendet stattdessen + **örtlichen Kontrast**: Der Ausschnitt wird auf 800 Bildpunkte Breite + normiert (schmalere Ausschnitte bleiben unverändert — macht alle + folgenden Maße unabhängig von der Kameraauflösung), in Graustufen + gewandelt und mit einem Kastenfilter geglättet (unterdrückt feine + Untergrundnarbung, über ein Summenbild für radius-unabhängige + Laufzeit). Ein zweiter, deutlich größerer Kastenmittelwert liefert den + **örtlichen Hintergrund** an jeder Stelle — das fängt ungleichmäßige + Ausleuchtung ab. Ein Bildpunkt gilt als Objekt, wenn er um mehr als + einen festen Betrag heller **oder** dunkler ist als sein örtlicher + Hintergrund (beide Richtungen werden berechnet; es gewinnt die mit den + meisten gefundenen Einzelflächen — Details siehe unten). Anschließend + werden zusammenhängende Objektflächen ermittelt + (Connected-Component-Labeling mit einer eigenen Arbeitsliste statt + Rekursion, damit ein rund eine Viertelmillion Bildpunkte großer + Ausschnitt nicht in den Stapelüberlauf läuft), winzige Flächen als + Rauschen verworfen und nur Flächen ab einem festen **Anteil der + größten gefundenen Fläche** behalten — bewusst relativ, nicht absolut, + damit ein näher oder weiter weg gehaltenes Kamerabild die Zählung nicht + verstellt. Auffällig große Flächen (mehrere berührende Teile) werden + auf ein Vielfaches der typischen (Median-)Einzelteilgröße + hochgerechnet. Details, alle verwendeten Schwellen und die Messung am + echten Foto: `.superpowers/sdd/counting-rework-report.md`. 5. **Stapel-Zuweisung.** Grün (Barcode) und Gelb (OCR) laufen ohne Eingabe durch; nur bei roter Konfidenz (nichts Eindeutiges erkannt, widersprüchliche Barcodes oder widersprüchliche unbekannte Barcodes ohne jede OCR-Kapazität) @@ -334,7 +353,7 @@ worauf du bei der Veröffentlichung achten solltest. | `src/scan-modes.js` | Die vier Scan-Funktionen (Strichcode, QR-Code, Text erkennen, Zählen): Kennung, deutsche Beschriftung, gelesene Codearten, laufende Suche ja/nein, Texterkennung ja/nein, laufende Zählung ja/nein — einzige Quelle, sowohl für die Auswahl-Oberfläche als auch für `main.js` | | `src/scan-recognition.js` | Baut die an `recognize()` übergebenen Adapter anhand der gewählten Funktion: in „Strichcode"/„QR-Code" liefert die Texterkennung sofort leeren Text ohne Tesseract anzustoßen, in „Text erkennen" liefert die Barcode-Dekodierung sofort eine leere Liste ohne `zxing-wasm` anzustoßen, in „Zählen" liefern beide sofort leer (diese Funktion ruft `recognize()` im normalen Betrieb ohnehin nicht auf) | | `src/threshold.js` | Otsu-Schwellwertbestimmung (Graustufen, Histogramm, Schwellwert) — reines Modul ohne Browser-Zugriff, aus `ocr.js` herausgezogen; gemeinsame Grundlage für `preprocess()` (ocr.js) und `countObjects()` (count-objects.js) | -| `src/count-objects.js` | Reine Berechnung für Funktion „Zählen": zählt zusammenhängende Objektflächen in einem Bildausschnitt (Otsu-Schwellwert aus `threshold.js`, flächenmäßig kleinere Klasse gilt als Objekt, Connected-Component-Labeling mit eigener Arbeitsliste, Rauschfilter, Hochrechnung berührender Teile über den Flächen-Median) | +| `src/count-objects.js` | Reine Berechnung für Funktion „Zählen": zählt zusammenhängende Objektflächen in einem Bildausschnitt über örtlichen Kontrast (Normierung auf 800 Bildpunkte Breite, Glättung und örtlicher Hintergrund je über Kastenfilter/Summenbild, Maske aus geglättetem Bild vs. örtlichem Hintergrund ± Marge, beide Polaritäten, Connected-Component-Labeling mit eigener Arbeitsliste, Rauschfilter, Massstab relativ zur größten Fläche, Hochrechnung berührender Teile über den Flächen-Median) — nutzt `threshold.js`/Otsu bewusst **nicht** mehr (siehe „Funktionsweise" oben) | | `src/storage.js` | Absturzschutz der laufenden Sitzung: sichert und lädt aus `localStorage`, verwirft beim Laden jeden in sich unstimmigen Zustand vollständig (siehe unten); `entry.rawText` ist wie `entry.codes` optional und wird bei Fehlen als leerer Rohtext wiederhergestellt | | `src/camera.js` | Kamerastart, Einzelbildaufnahme (herunterskaliert, für die Dateiauswahl), Rahmenausschnitt in voller Auflösung (`grabFrameRegion`, für die laufende Barcode-Suche, „Modul scannen" in „Text erkennen" **und** die laufende Zählung in „Zählen"), Bilddatei-Ersatzweg | | `src/barcode.js` | Adapter zu `zxing-wasm`; `decodeBarcodes(imageData, formats)` bekommt die Codearten übergeben, ohne Angabe wie bisher (Code128 + DataMatrix) | @@ -433,35 +452,41 @@ Eintrag und fließt in keine Stapel-Berechnung ein. Modul einen eigenen Stapel erzeugen. - **Funktion „Zählen" ist eine Schätzung, keine exakte Zählung** — sie wird ungenau, wenn eine der folgenden Bedingungen zutrifft: - - **Berührende oder gestapelte Teile.** Mehrere Teile, die sich berühren, - bilden eine einzige zusammenhängende Fläche. Die App rechnet diese über - die typische Einzelteilgröße (Median der gefundenen Flächen) auf ein - Vielfaches hoch, aber je unregelmäßiger sich die Teile überlappen - (z. B. drei Schrauben, die sich gegenseitig teilweise verdecken statt - sich nur zu berühren), desto ungenauer wird die hochgerechnete Fläche. - Liegen Teile übereinander gestapelt statt nebeneinander ausgelegt, sieht - die Kamera von oben nur die Fläche des obersten Teils — verdeckte Teile - werden nicht mitgezählt. - - **Ungleichmäßige Ausleuchtung.** Der Otsu-Schwellwert ist ein einziger, - fester Wert für den gesamten Ausschnitt. Fällt Licht sehr einseitig ein - (z. B. ein Teil des Rahmens hell, der Rest im Schatten), kann derselbe - Untergrund an einer Stelle über und an einer anderen Stelle unter dem - Schwellwert liegen — Teile im schlechter ausgeleuchteten Bereich werden - dann leicht übersehen oder der Untergrund selbst fälschlich als - Objektfläche gewertet. - - **Zu geringer Kontrast zum Untergrund.** Je ähnlicher sich Objekt- und - Untergrundhelligkeit sind, desto unschärfer die Trennung durch Otsu — - bei sehr geringem Kontrast können Teile am Rand verloren gehen oder mit - dem Untergrund verschmelzen. - - **Stark unterschiedlich große Teile.** Die Hochrechnung setzt voraus, - dass die meisten Fundstücke ungefähr gleich groß sind (der Median dient - als Maßstab für „ein Teil"). Liegen im selben Rahmen sowohl deutlich - größere als auch deutlich kleinere Teile derselben Sorte (oder gar - unterschiedliche Teile gemischt), kann die Rauschschwelle ein - tatsächliches kleines Teil verwerfen oder die Hochrechnung ein einzelnes - großes Teil als mehrere zählen. - - Details, die verwendeten Schwellenwerte und ihre Begründung stehen in - `.superpowers/sdd/counting-report.md`. + - **Der Untergrund muss sich abheben.** Das Verfahren erkennt Fundstücke + über den örtlichen Kontrast zu ihrer unmittelbaren Umgebung (siehe + „Funktionsweise" oben) — es braucht also eine Stelle, an der sich das + Fundstück *lokal* von dem unterscheidet, was um es herum liegt. Ein + Fundstück, das nahezu dieselbe Helligkeit wie der Tisch/das Leder + darunter hat, wird nicht zuverlässig gefunden. + - **Gleichmäßige Ausleuchtung hilft.** Der örtliche Hintergrund wird aus + einem Fenster um jeden Bildpunkt gebildet und fängt einseitigen + Lichteinfall dadurch besser ab als ein einzelner, fester Schwellwert + für das ganze Bild. Sehr harte Schlagschatten oder Reflexe können + trotzdem eigene, dem Fundstück ähnliche Kontrastflächen erzeugen (siehe + „mehrere gefundene Flächen" unten) und die Zählung verfälschen. + - **Auseinandergelegt zählt besser als aufgehäuft/berührend.** Mehrere + Teile, die sich berühren, bilden eine einzige zusammenhängende Fläche. + Die App rechnet diese über die typische Einzelteilgröße (Median der + gefundenen Flächen) auf ein Vielfaches hoch, aber je unregelmäßiger sich + die Teile überlappen (z. B. mehrere Schrauben, die sich gegenseitig + teilweise verdecken statt sich nur zu berühren), desto ungenauer wird + die hochgerechnete Fläche — und je dichter die Teile gedrängt liegen, + desto eher sieht das Umgebungsfenster (siehe oben) an ihrer eigenen + Stelle vor allem Nachbarteile statt echten Untergrund. Liegen Teile + übereinander gestapelt statt nebeneinander ausgelegt, sieht die Kamera + von oben nur die Fläche des obersten Teils — verdeckte Teile werden + nicht mitgezählt. + - **Stark unterschiedlich große Teile verfälschen die Hochrechnung.** Die + Hochrechnung setzt voraus, dass die meisten Fundstücke ungefähr gleich + groß sind (der Median dient als Maßstab für „ein Teil"). Liegen im + selben Rahmen sowohl deutlich größere als auch deutlich kleinere Teile + derselben Sorte (oder gar unterschiedliche Teile gemischt), kann die + Rauschschwelle ein tatsächliches kleines Teil verwerfen oder die + Hochrechnung ein einzelnes großes Teil als mehrere zählen. + - Details, die verwendeten Schwellenwerte, ihre Begründung und die + Messung am echten Foto (~20 Festplattenschrauben auf Leder, für das + das alte Otsu-basierte Verfahren 76712 statt rund zwanzig meldete) + stehen in `.superpowers/sdd/counting-rework-report.md`. ## Herstellertabellen erweitern