Masken im Code verwenden: Flächen, Überlappungen, Umrisse, Freisteller
Lies masks.data und masks.xy korrekt aus, miss Objektfläche und Füllgrad, prüfe Überlappungen mit Masken statt mit Boxen und stelle Objekte aus dem Bild frei.
Ein Segmentierungsmodell macht sich erst bezahlt, wenn der Code seine Masken in eine Entscheidung umwandelt. Auf dem Busfoto überlappt die Box jedes Fußgängers die Busbox um 56–74 %, während sich ihre Masken mit der Busmaske zu 0 % überlappen. Ein mit Boxen ausgelöster Alarm „Person im Fahrzeugbereich“ würde bei allen vier Personen auf dem Gehweg anschlagen. Masken liefern hier die richtigen Ergebnisse, aber ihre Standardansichten bergen Stolperfallen bei Größe und Format, die zu plausiblen, aber falschen Werten führen.
Praxisnah
Zwei Ansichten derselben Maske#

result.masks enthält jede Instanzmaske in zwei Darstellungen. Wähle die passende Darstellung für den nächsten Codeabschnitt. Die Referenz zu Results führt alle Attribute auf.
| Attribut | Form | Koordinaten | Verwendung |
|---|---|---|---|
masks.data | N × H × W-Tensor aus 0/1 | standardmäßig die Inferenzgröße; Originalgröße mit retina_masks=True | Pixelbereiche, Überlappungen zwischen Masken, Freisteller |
masks.xy | Liste aus N Arrays mit jeweils P × 2 | Pixelkoordinaten des Originalbilds | Umrisse zeichnen, Polygone exportieren |
masks.xyn | Liste aus N Arrays mit jeweils P × 2 | normalisiert auf 0–1 | YOLO-Polygonbeschriftungen zurückschreiben |
Auf dem Busbild mit 1080 × 810 Pixeln hat masks.data die Form (5, 640, 480) – die Größe nach dem Letterboxing bei der Inferenz. Daher sind die Pixelanzahlen etwa 2.85× zu klein und die Maske lässt sich nicht über das Originalbild legen. retina_masks=True skaliert Masken bei geringem Nachbearbeitungsaufwand auf die ursprüngliche Auflösung hoch. Verwende es immer, wenn du misst oder zuschneidest.
Jedes Objekt messen und freistellen#
Dieses Skript liest für jedes Objekt beide Ansichten aus: die Pixelbereich aus masks.data, die Umrissgröße aus masks.xy und einen transparenten PNG-Freisteller, der die Maske als Alphakanal verwendet. Die Anleitung zum Isolieren von Segmentierungsobjekten behandelt weitere Varianten des Freistellens.
import cv2
import numpy as np
from ultralytics import YOLO
model = YOLO("yolo26n-seg.pt")
result = model("https://ultralytics.com/images/bus.jpg", retina_masks=True)[0] # masks at original resolution
print("masks.data:", tuple(result.masks.data.shape), "orig_shape:", result.orig_shape)
for i, box in enumerate(result.boxes):
name = result.names[int(box.cls)]
mask = result.masks.data[i].cpu().numpy().astype(np.uint8) # H x W, 1 = object
polygon = result.masks.xy[i] # P x 2 outline in original pixels
x1, y1, x2, y2 = box.xyxy[0].int().tolist()
mask_area = int(mask.sum())
box_area = (x2 - x1) * (y2 - y1)
print(f"{name:6s} conf={float(box.conf):.2f} mask={mask_area:7d}px box={box_area:7d}px fill={mask_area / box_area:.2f} polygon={len(polygon)} pts")
# Cut the object out on a transparent background
cutout = np.dstack([result.orig_img, mask * 255])[y1:y2, x1:x2]
cv2.imwrite(f"{i}_{name}.png", cutout)masks.data: (5, 1080, 810) orig_shape: (1080, 810)
person conf=0.88 mask= 49331px box= 97219px fill=0.51 polygon=575 pts
person conf=0.86 mask= 21316px box= 67760px fill=0.31 polygon=408 pts
bus conf=0.86 mask= 255265px box= 409822px fill=0.62 polygon=1172 pts
person conf=0.81 mask= 33524px box= 55842px fill=0.60 polygon=350 pts
person conf=0.64 mask= 11109px box= 23828px fill=0.47 polygon=211 ptsDarauf solltest du achten:
masks.data: (5, 1080, 810)entsprichtorig_shape.retina_masks=Truehat also funktioniert, und alle Pixelzahlen beziehen sich auf das Originalbild.- Der Index
iverknüpft die beiden.result.masks.data[i]gehört zuresult.boxes[i], daher stammen Namen und Konfidenzen aus der Box und die Pixel aus der Maske. - Der Füllgrad verrät etwas, was ein Objektdetektor nicht erkennen kann: Der Bus füllt 62 % seiner Box aus, während die zweite Person, die am rechten Bildrand abgeschnitten ist und vor dem Bus steht, nur 31 % ihrer Box ausfüllt.
- Die Freisteller sind BGRA-PNGs, die nur die Person oder den Bus enthalten – sonst nichts. Sie sind bereit für einen Katalog, ein zweites Modell oder eine Prüfschlange.
Die zugeschnittene Variante in der Anleitung zum Isolieren verwendet statt eines transparenten Hintergrunds einen schwarzen:

Überlappung: Wo Boxen liegen#
Regeln wie „Eine Person befindet sich innerhalb der Sperrzone“ oder „Das Produkt bedeckt das Förderband“ sind Überlappungstests. Führe denselben Test mit Boxen und Masken aus:
import numpy as np
from ultralytics import YOLO
result = YOLO("yolo26n-seg.pt")("https://ultralytics.com/images/bus.jpg", retina_masks=True, verbose=False)[0]
names = [result.names[int(c)] for c in result.boxes.cls]
masks = result.masks.data.cpu().numpy().astype(bool) # N x H x W at original resolution
boxes = np.zeros_like(masks)
for i, (x1, y1, x2, y2) in enumerate(result.boxes.xyxy.int().tolist()):
boxes[i, y1:y2, x1:x2] = True # the same objects as filled rectangles
bus = names.index("bus")
for i, name in enumerate(names):
if name == "person":
by_box = (boxes[i] & boxes[bus]).sum() / boxes[i].sum()
by_mask = (masks[i] & masks[bus]).sum() / masks[i].sum()
print(f"person {i}: {by_box:4.0%} of box overlaps the bus box, {by_mask:4.0%} of mask overlaps the bus mask")person 0: 67% of box overlaps the bus box, 0% of mask overlaps the bus mask
person 1: 69% of box overlaps the bus box, 0% of mask overlaps the bus mask
person 3: 74% of box overlaps the bus box, 0% of mask overlaps the bus mask
person 4: 56% of box overlaps the bus box, 0% of mask overlaps the bus maskDie Personen stehen vor dem Bus, deshalb endet die Busmaske am Umriss jeder Person und die Masken teilen keine Pixel. Die Boxen sind Rechtecke um beide, daher überlappen sie sich stark. Dieselbe Berechnung funktioniert für jede Region, die du selbst einzeichnest: Fülle mit cv2.fillPoly ein Polygon in ein Array aus Nullen und verwende es anstelle der Busmaske.
Jede Regel mit Formulierungen wie innerhalb, berührt, bedeckt oder blockiert prüft auf Überlappung. Führe sie auf Masken aus. Boxen eignen sich für in der Nähe und links von, wo wenige Pixel Spielraum das Ergebnis nicht ändern.
Von Pixeln zu Antworten aus der realen Welt#
| Frage | Berechnung | Achte auf |
|---|---|---|
| Wie groß ist es? | mask.sum() × (cm² pro Pixel) | Kalibriere cm² pro Pixel einmal anhand eines bekannten Objekts. Bei einer schräg ausgerichteten Kamera ändert sich die Fläche mit der Entfernung: Kalibriere jeden Bereich einzeln oder miss nur in einem Bereich. |
| Wie viel des Bereichs ist bedeckt? | Pixel der Klassenmaske ÷ Pixel des Bereichs | Entscheide, ob überlappende Instanzen einmal oder zweimal gezählt werden. |
| Liegt es innerhalb der Zone? | (mask & zone).sum() / mask.sum() | Zeichne die Zone in der Originalbildgröße ein. |
| Wie sieht seine Kontur aus? | masks.xy[i] | Lies die Warnung zu geteilten Objekten weiter unten, bevor du daraus die Fläche berechnest. |
Stolperfallen#
Symptom: AttributeError: 'NoneType' object has no attribute 'xy' bei Bildern ohne Objekte. Ursache: Wenn das Modell nichts findet, ist result.masks gleich None und kein leeres Objekt. Lösung: Durchlaufe result.boxes (bei Segmentierungsmodellen leer, niemals None) und greife innerhalb der Schleife auf die Masken zu, wie es masks.py macht, oder prüfe zuerst result.masks is None.
Symptom: Flächenwerte, die bei verdeckten Objekten völlig falsch sind. Ursache: Wenn ein verdeckendes Objekt ein Objekt aufteilt, fügt masks.xy alle Konturen zu einem Array zusammen, und cv2.contourArea berechnet für dieses Array die Fläche einer Form, die gar nicht existiert. Lösung: Berechne Flächen anhand der Pixelanzahl von masks.data; verwende Polygone zum Zeichnen.
Symptom: Die Flächenwerte aus zwei Läufen unterscheiden sich um etwa den Faktor 2.85. Ursache: Bei einem Lauf wurde retina_masks=False verwendet, sodass die Flächen in Inferenzpixeln angegeben sind. Lösung: Verwende überall retina_masks=True zum Messen und rufe unter CUDA .cpu() vor .numpy() auf.
Lösung anzeigen
Ohne retina_masks=True gibt das Busbild masks.data mit der Form (5, 640, 480) zurück: Die Pixelgröße jeder Fläche schrumpft um (1080 × 810) / (640 × 480) ≈ 2.85, und die Maske passt für die Ausschnitte nicht mehr zu orig_img. Wenn sich das Seitenverhältnis des Bildes von der Inferenzgröße unterscheidet, verursacht die Letterbox-Auffüllung eine weitere Verschiebung.
Den geringsten Flächenanteil im Busbild hat die zweite Person (0.31): Sie wird am rechten Bildrand abgeschnitten und steht am Bus, sodass der größte Teil der Box den Hintergrund und den Bus umfasst.
Erstelle die Zone einmal in der Originalgröße und verwende den Maskentest erneut:
import cv2
h, w = result.orig_shape
zone = np.zeros((h, w), np.uint8)
cv2.fillPoly(zone, [np.array([[0, 2 * h // 3], [w, 2 * h // 3], [w, h], [0, h]])], 1)
zone = zone.astype(bool)
for i, name in enumerate(names):
print(f"{name} {i}: box {(boxes[i] & zone).sum() / boxes[i].sum():.0%}, mask {(masks[i] & zone).sum() / masks[i].sum():.0%}")Im Busbild:
person 0: box 36%, mask 29%
person 1: box 33%, mask 37%
bus 2: box 4%, mask 0%
person 3: box 31%, mask 21%
person 4: box 47%, mask 53%Die Beine der Personen befinden sich tatsächlich im unteren Drittel, daher stimmen Box und Maske bis auf etwa zehn Prozentpunkte überein. Der Bus ist der Fehlalarm: Seine Box reicht zu 4 % in die Zone hinein, seine Maske gar nicht. Bei einer Regel „mehr als 0 % innerhalb der Zone“ würden Boxen den Bus markieren, Masken dagegen nicht.