Zugeschnittene Fotos ohne Sucher bestimmen

Ein Handyfoto ist das Gegenteil einer Kameraaufnahme: Jemand zielt auf den
Vogel. Unterhalb von rund einem Viertel Bildflaeche findet SSD-MobileNet ihn
nicht mehr (12 % Flaeche: 38 % gefunden, 6 %: 10 %, 2 %: 0 %) — und auf einem
Zuschnitt, der nur noch aus Vogel besteht, meldet es sich ueberhaupt nicht.

run_zuschnitt() ueberspringt den Sucher. Gemessen an schwierigen Aufnahmen:
aus 'nichts gefunden' wird Haussperling 93 %.

Dabei faellt der Sucher als Torwaechter weg. Die erste Fassung liess
min_bird_score (0.05) entscheiden und gab fuer eine graue Wand Fischadler
20 % zurueck; ueber 148 Vogelbilder und 71 Hintergrundausschnitte kommen bei
dieser Schwelle 82 % der Hintergruende durch. Das Tor ist jetzt die
Sicherheit des besten Treffers (0.45: 96 % der Voegel, 14 % Hintergrund)
zusammen mit min_bird_score.

Alle Messungen stehen in docs/handyfotos.md, auch die dort als verworfen
dokumentierte Kachelsuche.

Die Pruefbilder liegen bewusst NICHT hier: Dieses Repo ist oeffentlich, die
Aufnahmen sind privat. Sie liegen in der Nextcloud unter
Projekte/Vogelalbum/Pruefbilder; 'pruefbilder/' steht in der .gitignore.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
christianandClaude Opus 5 committed 2026-10-24 17:34:52 +00:00
1 parent c11fe9ab04
commit dc7db4b603
4 files changed
+252

No files matched your search

+55
View File
@@ -266,6 +266,7 @@ class Pipeline:
restrict_to_germany: bool = True,
habitats: set[str] | None = None,
report_threshold: float | None = None,
min_zuschnitt_sicherheit: float = 0.45,
):
# 0.05 ist gemessen, nicht geraten: scripts/benchmark.py über die
# 44 Referenzfotos ergibt 43/44 richtig bei 0.02–0.05, aber nur 40/44
@@ -293,6 +294,10 @@ class Pipeline:
restrict_to_germany=restrict_to_germany, habitats=habitats
)
self.min_bird_score = min_bird_score
# Nur fuer `run_zuschnitt` — dort faellt der Sucher als Torwaechter
# weg und diese Schwelle nimmt seinen Platz ein. Begruendung und
# Messung stehen bei der Methode.
self.min_zuschnitt_sicherheit = min_zuschnitt_sicherheit
def zaehlt_als_vogel(self, box: Box | None) -> bool:
"""Reicht diese Box fuer eine Sichtung, oder ist sie nur Protokoll?"""
@@ -364,3 +369,53 @@ class Pipeline:
# eine falsche im Vogelalbum.
return box, None
return box, result
# ---- Weg fuer einzelne Fotos ---------------------------------------
def run_zuschnitt(self, img: Image.Image) -> Result | None:
"""Ein Bild bestimmen, das schon auf den Vogel gezogen ist.
🔴 **Der Sucher wird hier absichtlich uebersprungen.** Auf einem
Zuschnitt, der nur noch aus Vogel besteht, meldet SSD-MobileNet gar
nichts — es ist auf Objekte in einer Szene trainiert, nicht auf
formatfuellende Tiere. Gemessen am 24.10.2026: Ein Bild, bei dem die
Kette „nichts gefunden" sagte, wird so zu *Haussperling 93 %*.
Den Sucher trotzdem laufen zu lassen hiesse: Der Mensch hat den Vogel
gefunden und zeigt darauf — und die Maschine sagt, da sei keiner.
🔴 **Ohne Sucher fehlt aber auch dessen Torwaechterrolle**, und das
ist die Falle dieser Methode. Die erste Fassung liess
``min_bird_score`` (0.05) entscheiden — und gab fuer eine **graue
Wand** „Fischadler 20 %" zurueck. Gemessen ueber 148 Vogelbilder und
71 Hintergrundausschnitte: Bei 0.05 kommen 97 % der Voegel durch,
aber auch **82 % der Hintergruende**. Als Tor taugt der Wert hier
nicht; er misst „welche Art", nicht „ueberhaupt ein Vogel".
Was trennt, ist die **Sicherheit des besten Treffers nach der
Einschraenkung**: Ein echter Vogel konzentriert die Masse auf eine
Art, eine strukturlose Flaeche verteilt sie ueber viele. Gemessen::
Schwelle Voegel Hintergrund
0.33 100 % 34 %
0.40 97 % 17 %
0.45 96 % 14 % <- gewaehlt
0.50 93 % 8 %
Beide Bedingungen zusammen, weil sie Verschiedenes pruefen: die
Sicherheit, ob ueberhaupt etwas erkannt wurde, ``bird_score``, ob es
eine heimische Art ist.
⚠️ Die verbleibenden 14 % sind Laub und Geaest, das bei dieser
Aufloesung wirklich wie ein Vogel aussieht. Deshalb zeigt die App die
Alternativen mit an — die letzte Instanz ist der Mensch, der
hingesehen hat.
"""
ergebnis = self.classifier.classify(img)
if ergebnis.best is None:
return None
if ergebnis.best.confidence < self.min_zuschnitt_sicherheit:
return None
if ergebnis.bird_score < self.min_bird_score:
return None
return ergebnis