Ultralytics YOLO27: Trag dich in die Warteliste ein.
Get Started
LektionFortgeschrittene

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.

Lernergebnis

Führe Skripte aus, die für jedes Objekt die Maskenfläche, den Füllgrad, die Umrissgröße und die Überlappung mit einem anderen Objekt ausgeben und einen transparenten Freisteller pro Objekt speichern – jeweils mit der passenden Maskenansicht.

Schnellkurs
Wenn du dich bereits auskennst, findest du hier die Kurzfassung.
  1. Verwende bei jeder Messung und jedem Zuschnitt retina_masks=True für die Vorhersage, damit die Masken dieselbe Größe wie das Originalbild haben.

  2. Verwende masks.data (N × H × W) für Flächen, Überlappungen und Freisteller; verwende masks.xy (Pixelpolygone) zum Zeichnen und Exportieren von Umrissen.

  3. Die Fläche ist mask.sum(); der Füllgrad ist Maskenfläche ÷ Boxfläche; die Überlappung ist (mask_a & mask_b).sum().

  4. Prüfe result.masks is None, bevor du Masken ausliest: Wenn nichts gefunden wird, ist es None.

Praxisnah

Zwei Ansichten derselben Maske#

Eine Person, mit einer Ultralytics-YOLO-Segmentierungsmaske isoliert und vor schwarzem Hintergrund an ihrer ursprünglichen Position im Gesamtbild belassen

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.

AttributFormKoordinatenVerwendung
masks.dataN × H × W-Tensor aus 0/1standardmäßig die Inferenzgröße; Originalgröße mit retina_masks=TruePixelbereiche, Überlappungen zwischen Masken, Freisteller
masks.xyListe aus N Arrays mit jeweils P × 2Pixelkoordinaten des OriginalbildsUmrisse zeichnen, Polygone exportieren
masks.xynListe aus N Arrays mit jeweils P × 2normalisiert auf 0–1YOLO-Polygonbeschriftungen zurückschreiben
masks.data verwendet standardmäßig die Inferenzgröße

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 pts

Darauf solltest du achten:

  • masks.data: (5, 1080, 810) entspricht orig_shape. retina_masks=True hat also funktioniert, und alle Pixelzahlen beziehen sich auf das Originalbild.
  • Der Index i verknüpft die beiden. result.masks.data[i] gehört zu result.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:

Dieselbe Person auf ihre Begrenzungsbox zugeschnitten, wobei jedes Pixel außerhalb der Maske auf Schwarz gesetzt ist

Ü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 mask

Die 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.

Entscheidungsregel

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#

FrageBerechnungAchte 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 BereichsEntscheide, 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#

Masken auslesen, wenn nichts erkannt wurde

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.

Polygonfläche bei geteilten Objekten

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.

Auflösungen mischen

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.

Ausprobieren

Führe masks.py auf einem eigenen Bild mit mindestens drei Objekten aus und nenne das Objekt mit dem geringsten Flächenanteil sowie den Grund, warum seine Box seine Größe überschätzt. Führe den Vorgang ohne retina_masks=True erneut aus und notiere, wie sich masks.data.shape und die Flächen ändern. Passe dann overlap.py so an, dass du testest, ob sich ein Objekt in deinem Bild mit einem Rechteck überschneidet, das du über dem unteren Drittel des Bildes einzeichnest.

Abgeschlossen, wenn
Du hast die Lektion abgeschlossen, wenn alle folgenden Punkte zutreffen.
  • Für jedes erkannte Objekt in deinem Bild hast du einen freigestellten PNG-Ausschnitt mit Transparenz.

  • Du kannst den geringsten Flächenanteil nennen und den Unterschied zwischen masks.data mit und ohne retina_masks=True erklären.

  • Dein Zonentest gibt für jedes Objekt eine boxbasierte und eine maskenbasierte Überschneidung aus, und du kannst jedes Objekt erklären, bei dem die Werte voneinander abweichen.

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.

Wie es weitergeht

Als Nächstes lassen wir Instanzen ganz weg und kennzeichnen jeden Pixel mit semantischer Segmentierung.