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:
1 parent
c11fe9ab04
commit
dc7db4b603
4 files changed
+252
No files matched your search
@@ -956,3 +956,73 @@ class AnzeigeAusschnittTests(unittest.TestCase):
|
||||
a = Box(x0=0.3, y0=0.3, x1=0.7, y1=0.7, score=0.9).anzeige_ausschnitt(im)
|
||||
self.assertLessEqual(a.width, 60)
|
||||
self.assertLessEqual(a.height, 40)
|
||||
|
||||
|
||||
class ZuschnittTests(unittest.TestCase):
|
||||
"""Das Tor fuer von Hand zugeschnittene Bilder.
|
||||
|
||||
🔑 Ohne Modelle gebaut: Geprueft wird die **Entscheidung**, nicht die
|
||||
Erkennung. Genau dort lag der Fehler — die erste Fassung liess
|
||||
``min_bird_score`` allein entscheiden und gab fuer eine graue Wand
|
||||
„Fischadler 20 %" zurueck.
|
||||
"""
|
||||
|
||||
def pipeline(self, sicherheit: float, bird_score: float):
|
||||
"""Eine Pipeline, die ohne LiteRT auskommt.
|
||||
|
||||
``Pipeline.__new__`` statt ``Pipeline(...)``: Der Konstruktor laedt
|
||||
beide Modelle, und die haben mit dieser Frage nichts zu tun.
|
||||
"""
|
||||
from detector.classify import Guess, Pipeline, Result
|
||||
from detector.species_de import BY_SCIENTIFIC
|
||||
|
||||
art = BY_SCIENTIFIC["Parus major"]
|
||||
|
||||
class FakeClassifier:
|
||||
def classify(self, img):
|
||||
return Result(
|
||||
guesses=[Guess(species=art, confidence=sicherheit)],
|
||||
raw_top=[],
|
||||
ms=1.0,
|
||||
bird_score=bird_score,
|
||||
)
|
||||
|
||||
pipe = Pipeline.__new__(Pipeline)
|
||||
pipe.classifier = FakeClassifier()
|
||||
pipe.min_bird_score = 0.05
|
||||
pipe.min_zuschnitt_sicherheit = 0.45
|
||||
return pipe
|
||||
|
||||
def test_sicherer_treffer_geht_durch(self):
|
||||
ergebnis = self.pipeline(0.93, 0.11).run_zuschnitt(Image.new("RGB", (80, 80)))
|
||||
self.assertIsNotNone(ergebnis)
|
||||
self.assertEqual(ergebnis.best.species.german, "Kohlmeise")
|
||||
|
||||
def test_unsicherer_treffer_wird_verworfen(self):
|
||||
"""Der Fall „graue Wand": bird_score reicht, Sicherheit nicht.
|
||||
|
||||
Gemessen ueber 71 Hintergrundausschnitte: Bei 0.05 bird_score kaemen
|
||||
82 Prozent davon durch. Die Sicherheit ist hier das Tor.
|
||||
"""
|
||||
self.assertIsNone(
|
||||
self.pipeline(0.20, 0.16).run_zuschnitt(Image.new("RGB", (80, 80)))
|
||||
)
|
||||
|
||||
def test_fremde_art_wird_verworfen(self):
|
||||
"""Sicher erkannt, aber keine heimische Art — dafuer bleibt
|
||||
bird_score zustaendig."""
|
||||
self.assertIsNone(
|
||||
self.pipeline(0.95, 0.01).run_zuschnitt(Image.new("RGB", (80, 80)))
|
||||
)
|
||||
|
||||
def test_beide_bedingungen_noetig(self):
|
||||
"""Keine der beiden Schwellen allein darf genuegen."""
|
||||
knapp_drueber = self.pipeline(0.46, 0.06)
|
||||
self.assertIsNotNone(knapp_drueber.run_zuschnitt(Image.new("RGB", (80, 80))))
|
||||
for sicherheit, bird_score in ((0.44, 0.06), (0.46, 0.04)):
|
||||
with self.subTest(sicherheit=sicherheit, bird_score=bird_score):
|
||||
self.assertIsNone(
|
||||
self.pipeline(sicherheit, bird_score).run_zuschnitt(
|
||||
Image.new("RGB", (80, 80))
|
||||
)
|
||||
)
|
||||
Reference in new issue
Block a user