Ausschnitt fuer die Sammlung weiter fassen

Christian ueber seine erste Amsel: "Der Zuschnitt der Fotos ist auch etwas
merkwuerdig." Er hatte recht -- in der Kachel war nur Gefieder zu sehen,
Kopf und Schwanz lagen ausserhalb.

URSACHE: Derselbe enge Ausschnitt diente zwei Zwecken. Fuer den
Klassifikator ist eng richtig, jedes Stueck Hintergrund verwaessert die
Bestimmung. Fuer die Sammlung ist eng falsch: Dort steht das Bild in einer
fast quadratischen Kachel, und die schneidet weg, was ueber das Quadrat
hinausragt. Bei einem Vogel im Hochformat -- und so sitzt fast jeder --
bleibt die Bauchmitte uebrig, formatfuellend und unscharf.

Neu: Box.anzeige_ausschnitt, quadratisch und mit mehr Rand. Was die Kachel
abschneidet, ist jetzt Umgebung statt Vogel. `crop` bleibt unveraendert --
die Artbestimmung ist gemessen und soll so bleiben.

An Christians echter Amsel geprueft: vorher 446x857 (Verhaeltnis 0,52),
nachher 1176x1175. Im Vergleichsbild sitzt der ganze Vogel auf dem First
statt einer Nahaufnahme des Bauchs.

5 Tests, auch fuer Voegel am Bildrand (sonst sitzt der Vogel ausserhalb
seiner eigenen Kachel). 136 Detector-Tests gruen.
This commit is contained in:
Artemis committed 2026-10-14 18:11:11 +00:00
1 parent f7184d193c
commit 93f209ca12
4 files changed
+124 -2

No files matched your search

+44
View File
@@ -68,6 +68,50 @@ class Box:
return img
return img.crop((x0, y0, x1, y1))
def anzeige_ausschnitt(self, img: Image.Image, pad: float = 0.35) -> Image.Image:
"""Derselbe Vogel, aber als Bild zum Ansehen statt zum Bestimmen.
🔑 **Der Klassifikator und das Auge wollen Verschiedenes.** ``crop``
liegt eng am Vogel, weil jedes Stück Hintergrund die Bestimmung
verwässert. Für die Sammlung ist genau das falsch: Dort steht der
Ausschnitt in einer fast quadratischen Kachel, und die schneidet links
und rechts weg, was über das Quadrat hinausragt.
Bei einem Vogel im Hochformat — und so sitzt fast jeder — bleibt davon
die Bauchmitte übrig, formatfüllend und unscharf. Christian am
14.10.2026 über seine erste Amsel: *„Der Zuschnitt der Fotos ist auch
etwas merkwürdig."* Er hatte recht: Vom Vogel war nur Gefieder zu
sehen, Kopf und Schwanz lagen außerhalb.
Deshalb hier mehr Rand **und** ein Ausschnitt in Richtung Quadrat: Was
die Kachel abschneidet, ist dann Umgebung statt Vogel.
⚠️ Absichtlich eine eigene Methode. ``crop`` hier zu ändern hieße, die
Artbestimmung zu ändern — und die ist gemessen und soll so bleiben.
"""
w, h = img.size
bx0, by0 = self.x0 * w, self.y0 * h
bx1, by1 = self.x1 * w, self.y1 * h
bw, bh = bx1 - bx0, by1 - by0
if bw <= 0 or bh <= 0:
return img
# Erst Rand, dann die kürzere Seite auf die längere ziehen.
seite = max(bw, bh) * (1 + 2 * pad)
mx, my = (bx0 + bx1) / 2, (by0 + by1) / 2
# ⚠️ Nicht über das Bild hinaus, und die Mitte nachführen statt den
# Ausschnitt zu stauchen — sonst sitzt ein Vogel am Bildrand
# außermittig in seiner eigenen Kachel.
seite = min(seite, float(w), float(h))
mx = min(max(mx, seite / 2), w - seite / 2)
my = min(max(my, seite / 2), h - seite / 2)
return img.crop((
int(mx - seite / 2), int(my - seite / 2),
int(mx + seite / 2), int(my + seite / 2),
))
@property
def area(self) -> float:
return max(0.0, self.x1 - self.x0) * max(0.0, self.y1 - self.y0)
+4 -1
View File
@@ -185,7 +185,10 @@ class Warteschlange:
box, result = self.pipeline.run(bild)
# Den Ausschnitt mitschicken, damit die Sammlung dasselbe Bild
# zeigt wie bei einer Sichtung von der Kamera.
ausschnitt = box.crop(bild) if box is not None else None
# ⚠️ `anzeige_ausschnitt`, nicht `crop`: Der enge Schnitt ist
# fuer den Klassifikator gedacht; in der fast quadratischen
# Kachel der Sammlung bleibt davon nur die Bauchmitte uebrig.
ausschnitt = box.anzeige_ausschnitt(bild) if box is not None else None
self._melden(foto["id"], ausschnitt, result, box)
fertig += 1
except Exception as exc: # absichtlich breit, siehe Docstring
+4 -1
View File
@@ -221,12 +221,15 @@ class Watcher:
# Bestes Foto: Schärfe, gewichtet mit der Detektor-Sicherheit. Ein
# gestochen scharfes Bild von einem halb verdeckten Vogel nützt weniger
# als ein leicht weicheres, auf dem er ganz zu sehen ist.
# Schaerfe am ENGEN Ausschnitt messen: Umgebung soll die Bewertung
# nicht verwaessern. Gespeichert wird dann der weitere -- siehe
# Box.anzeige_ausschnitt.
crop = box.crop(frame)
score = sharpness(crop) * (0.5 + 0.5 * box.score)
if score > self._best_sharp:
self._best_sharp = score
self._best_image = frame.copy()
self._best_crop = crop
self._best_crop = box.anzeige_ausschnitt(frame)
def _vote(self, box: Box, result: Result) -> None:
# Gewicht: wie sicher ist der Detektor, wie groß ist der Vogel im Bild,