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
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`, 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/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