Doku: Zaehlfunktion und ihre Grenzen auf das neue Verfahren aktualisiert

Beschreibt oertlichen Kontrast statt Otsu (Normierung, Glaettung,
oertlicher Hintergrund, Massstab relativ zur groessten Flaeche) und die
neuen Grenzen: Untergrund muss sich abheben, gleichmaessige Ausleuchtung
hilft, auseinandergelegt zaehlt besser als aufgehaeuft, stark
unterschiedlich grosse Teile verfaelschen die Hochrechnung.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-07-29 16:21:35 +02:00
co-authored by Claude Opus 5
parent 089d47a1ec
commit f0705a010c
+70 -45
View File
@@ -182,21 +182,40 @@ keine Daten verlassen das Gerät.
Stapel-Zuweisung; die Zahl wird nirgends gespeichert, nicht einmal Stapel-Zuweisung; die Zahl wird nirgends gespeichert, nicht einmal
flüchtig für die Dauer der Sitzung. flüchtig für die Dauer der Sitzung.
Kein Bilderkennungsmodell: Der Ausschnitt wird über denselben Kein Bilderkennungsmodell: Der **Otsu-Schwellwert** (`threshold.js`),
**Otsu-Schwellwert** wie die Texterkennung in zwei Klassen geteilt den die Texterkennung weiterhin nutzt, taugt für „Zählen" nicht — er
(`threshold.js`, aus `ocr.js` herausgezogen, damit beide Funktionen sich setzt voraus, dass Objekt- und Untergrundfläche etwa gleich groß sind.
dieselbe Schwellwertbestimmung teilen, ohne dass die reine Zählung an Am echten Foto des Auftraggebers (rund zwanzig silberne
`tesseract.js` hinge). Die flächenmäßig **kleinere** der beiden Klassen Festplattenschrauben auf dunklem Leder) belegen die Schrauben unter
gilt als Objekt — die ausgelegten Teile belegen so gut wie immer weniger einem Prozent der Fläche; Otsu rutschte deshalb auf den höchsten
Fläche als der Untergrund, unabhängig davon, ob sie heller oder dunkler Helligkeitswert und erklärte nur die hellsten Punkte der Ledernarbung
sind als dieser; der Nutzer muss nichts einstellen. Anschließend werden zum Objekt (gemessenes Fehlverhalten: 76712 statt rund zwanzig). Über
zusammenhängende Objektflächen ermittelt (Connected-Component-Labeling die Helligkeit allein sind Schraube und Untergrund an diesem Foto gar
mit einer eigenen Arbeitsliste statt Rekursion, damit ein rund eine nicht trennbar.
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 `countObjects` (`src/count-objects.js`) verwendet stattdessen
(mehrere berührende Teile) auf ein Vielfaches der typischen **örtlichen Kontrast**: Der Ausschnitt wird auf 800 Bildpunkte Breite
Einzelteilgröße hochgerechnet. Details und Begründung der verwendeten normiert (schmalere Ausschnitte bleiben unverändert — macht alle
Schwellen: `.superpowers/sdd/counting-report.md`. 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 5. **Stapel-Zuweisung.** Grün (Barcode) und Gelb (OCR) laufen ohne Eingabe
durch; nur bei roter Konfidenz (nichts Eindeutiges erkannt, widersprüchliche durch; nur bei roter Konfidenz (nichts Eindeutiges erkannt, widersprüchliche
Barcodes oder widersprüchliche unbekannte Barcodes ohne jede OCR-Kapazität) 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-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/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/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`, fchenmäß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, Gttung 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/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/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) | | `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. Modul einen eigenen Stapel erzeugen.
- **Funktion „Zählen" ist eine Schätzung, keine exakte Zählung** — sie wird - **Funktion „Zählen" ist eine Schätzung, keine exakte Zählung** — sie wird
ungenau, wenn eine der folgenden Bedingungen zutrifft: ungenau, wenn eine der folgenden Bedingungen zutrifft:
- **Berührende oder gestapelte Teile.** Mehrere Teile, die sich berühren, - **Der Untergrund muss sich abheben.** Das Verfahren erkennt Fundstücke
bilden eine einzige zusammenhängende Fläche. Die App rechnet diese über über den örtlichen Kontrast zu ihrer unmittelbaren Umgebung (siehe
die typische Einzelteilgröße (Median der gefundenen Flächen) auf ein „Funktionsweise" oben) — es braucht also eine Stelle, an der sich das
Vielfaches hoch, aber je unregelmäßiger sich die Teile überlappen Fundstück *lokal* von dem unterscheidet, was um es herum liegt. Ein
(z. B. drei Schrauben, die sich gegenseitig teilweise verdecken statt Fundstück, das nahezu dieselbe Helligkeit wie der Tisch/das Leder
sich nur zu berühren), desto ungenauer wird die hochgerechnete Fläche. darunter hat, wird nicht zuverlässig gefunden.
Liegen Teile übereinander gestapelt statt nebeneinander ausgelegt, sieht - **Gleichmäßige Ausleuchtung hilft.** Der örtliche Hintergrund wird aus
die Kamera von oben nur die Fläche des obersten Teils — verdeckte Teile einem Fenster um jeden Bildpunkt gebildet und fängt einseitigen
werden nicht mitgezählt. Lichteinfall dadurch besser ab als ein einzelner, fester Schwellwert
- **Ungleichmäßige Ausleuchtung.** Der Otsu-Schwellwert ist ein einziger, für das ganze Bild. Sehr harte Schlagschatten oder Reflexe können
fester Wert für den gesamten Ausschnitt. Fällt Licht sehr einseitig ein trotzdem eigene, dem Fundstück ähnliche Kontrastflächen erzeugen (siehe
(z. B. ein Teil des Rahmens hell, der Rest im Schatten), kann derselbe „mehrere gefundene Flächen" unten) und die Zählung verfälschen.
Untergrund an einer Stelle über und an einer anderen Stelle unter dem - **Auseinandergelegt zählt besser als aufgehäuft/berührend.** Mehrere
Schwellwert liegen — Teile im schlechter ausgeleuchteten Bereich werden Teile, die sich berühren, bilden eine einzige zusammenhängende Fläche.
dann leicht übersehen oder der Untergrund selbst fälschlich als Die App rechnet diese über die typische Einzelteilgröße (Median der
Objektfläche gewertet. gefundenen Flächen) auf ein Vielfaches hoch, aber je unregelmäßiger sich
- **Zu geringer Kontrast zum Untergrund.** Je ähnlicher sich Objekt- und die Teile überlappen (z. B. mehrere Schrauben, die sich gegenseitig
Untergrundhelligkeit sind, desto unschärfer die Trennung durch Otsu — teilweise verdecken statt sich nur zu berühren), desto ungenauer wird
bei sehr geringem Kontrast können Teile am Rand verloren gehen oder mit die hochgerechnete Fläche — und je dichter die Teile gedrängt liegen,
dem Untergrund verschmelzen. desto eher sieht das Umgebungsfenster (siehe oben) an ihrer eigenen
- **Stark unterschiedlich große Teile.** Die Hochrechnung setzt voraus, Stelle vor allem Nachbarteile statt echten Untergrund. Liegen Teile
dass die meisten Fundstücke ungefähr gleich groß sind (der Median dient übereinander gestapelt statt nebeneinander ausgelegt, sieht die Kamera
als Maßstab für „ein Teil"). Liegen im selben Rahmen sowohl deutlich von oben nur die Fläche des obersten Teils — verdeckte Teile werden
größere als auch deutlich kleinere Teile derselben Sorte (oder gar nicht mitgezählt.
unterschiedliche Teile gemischt), kann die Rauschschwelle ein - **Stark unterschiedlich große Teile verfälschen die Hochrechnung.** Die
tatsächliches kleines Teil verwerfen oder die Hochrechnung ein einzelnes Hochrechnung setzt voraus, dass die meisten Fundstücke ungefähr gleich
großes Teil als mehrere zählen. groß sind (der Median dient als Maßstab für „ein Teil"). Liegen im
- Details, die verwendeten Schwellenwerte und ihre Begründung stehen in selben Rahmen sowohl deutlich größere als auch deutlich kleinere Teile
`.superpowers/sdd/counting-report.md`. 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 ## Herstellertabellen erweitern