Usa le maschere nel codice: area, sovrapposizione, contorni e ritagli
Leggi correttamente masks.data e masks.xy, misura l'area e il riempimento degli oggetti, verifica le sovrapposizioni usando le maschere anziché le bounding box e ritaglia gli oggetti dall'immagine.
Un modello di segmentazione è utile solo se il codice trasforma le sue maschere in una decisione. Nella foto dell'autobus, la bounding box di ogni pedone si sovrappone a quella dell'autobus del 56–74%, mentre le maschere dei pedoni si sovrappongono a quella dell'autobus dello 0%: un avviso «persona all'interno della zona del veicolo» basato sulle bounding box si attiverebbe per tutte e quattro le persone sul marciapiede. Le maschere forniscono risposte corrette, ma le loro viste predefinite presentano insidie legate a dimensioni e formato che possono produrre numeri plausibili ma sbagliati.
Pratico
Due viste della stessa maschera#

result.masks contiene ogni maschera di istanza in due rappresentazioni. Scegli in base all'operazione che esegue la riga di codice successiva; il riferimento Results elenca tutti gli attributi.
| Attributo | Forma | Coordinate | Usala per |
|---|---|---|---|
masks.data | Tensore N × H × W di valori 0/1 | dimensioni di inferenza per impostazione predefinita; dimensioni originali con retina_masks=True | aree in pixel, sovrapposizioni tra maschere, ritagli |
masks.xy | elenco di N array, ciascuno P × 2 | pixel dell'immagine originale | disegnare contorni, esportare poligoni |
masks.xyn | elenco di N array, ciascuno P × 2 | normalizzate 0–1 | riscrivere le etichette poligonali YOLO |
Sull'immagine dell'autobus da 1080 × 810, masks.data restituisce (5, 640, 480), la dimensione dell'inferenza con letterboxing: il conteggio dei pixel è quindi circa 2.85 volte troppo basso e la maschera non può essere sovrapposta all'immagine originale. retina_masks=True porta le maschere alla risoluzione originale con un costo di post-elaborazione ridotto. Usalo ogni volta che misuri o ritagli.
Misura e ritaglia ogni oggetto#
Questo script legge entrambe le viste per ogni oggetto: l'area in pixel da masks.data, la dimensione del contorno da masks.xy e un ritaglio PNG trasparente che usa la maschera come canale alfa. La guida per isolare gli oggetti segmentati illustra altre varianti di ritaglio.
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 ptsCosa leggere nell'output:
masks.data: (5, 1080, 810)corrisponde aorig_shape, quindiretina_masks=Trueha funzionato e ogni conteggio di pixel usa i pixel originali.- L'indice
icollega le due viste.result.masks.data[i]appartiene aresult.boxes[i], quindi nomi e confidenze provengono dalla bounding box, mentre i pixel provengono dalla maschera. - Il riempimento racconta ciò che un rilevatore non può: l'autobus occupa il 62% della sua bounding box, mentre la seconda persona, tagliata dal bordo destro dell'immagine e in piedi contro l'autobus, ne occupa solo il 31%.
- I ritagli sono immagini PNG BGRA che contengono solo la persona o l'autobus, pronte per un catalogo, un secondo modello o una coda di revisione.
La variante ritagliata della guida all'isolamento usa il nero anziché uno sfondo trasparente:

Sovrapposizione: dove si trovano le bounding box#
Regole come «una persona si trova nella zona riservata» o «il prodotto copre il nastro trasportatore» sono test di sovrapposizione. Esegui lo stesso test con le bounding box e con le maschere:
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 maskLe persone si trovano davanti all'autobus, quindi la maschera dell'autobus si arresta sul contorno di ciascuna persona e le maschere non condividono alcun pixel. Le bounding box sono rettangoli tracciati intorno a entrambi e si sovrappongono ampiamente. Lo stesso calcolo funziona con qualsiasi regione disegnata da te: riempi un poligono in un array di zeri con cv2.fillPoly e usalo al posto della maschera dell'autobus.
Qualsiasi regola formulata con dentro, a contatto con, che copre o che blocca richiede un test di sovrapposizione. Eseguilo sulle maschere. I riquadri vanno bene per vicino a e a sinistra di, dove qualche pixel di tolleranza non cambia la risposta.
Dai pixel alle risposte sul mondo reale#
| Domanda | Calcolo | Attenzione a |
|---|---|---|
| Quanto è grande? | mask.sum() × (cm² per pixel) | Calibra una volta i cm² per pixel usando un oggetto di dimensioni note. Con una telecamera inclinata, l'area cambia in base alla distanza: calibra ogni zona oppure misura solo in una regione. |
| Quale percentuale della regione è coperta? | pixel della maschera della classe ÷ pixel della regione | Decidi se le istanze sovrapposte vanno conteggiate una o due volte. |
| Si trova all'interno della zona? | (mask & zone).sum() / mask.sum() | Disegna la zona usando le dimensioni originali dell'immagine. |
| Che forma ha il suo contorno? | masks.xy[i] | Prima di calcolare l'area, consulta l'avviso sugli oggetti suddivisi qui sotto. |
Insidie#
Sintomo: AttributeError: 'NoneType' object has no attribute 'xy' sui frame senza oggetti. Causa: quando il modello non trova nulla, result.masks è None, non un oggetto vuoto. Soluzione: itera su result.boxes (vuoto, mai None, per i modelli di segmentazione) e accedi alle maschere all'interno del ciclo, come fa masks.py, oppure controlla prima result.masks is None.
Sintomo: aree del tutto errate per gli oggetti occlusi. Causa: quando un occludente divide un oggetto, masks.xy unisce tutti i contorni in un unico array e cv2.contourArea su quell'array misura una forma inesistente. Soluzione: calcola le aree dal conteggio dei pixel di masks.data; usa i poligoni per disegnare.
Sintomo: le aree di due esecuzioni differiscono di un fattore pari a circa 2.85. Causa: una delle esecuzioni usava retina_masks=False, quindi le aree sono espresse in pixel di inferenza. Soluzione: esegui tutte le misurazioni con retina_masks=True e, su CUDA, chiama .cpu() prima di .numpy().
Mostra la soluzione
Senza retina_masks=True, l'immagine dell'autobus restituisce masks.data con forma (5, 640, 480): l'area di ogni pixel si riduce di (1080 × 810) / (640 × 480) ≈ 2.85 e la maschera non è più allineata con orig_img per i ritagli. Quando le proporzioni dell'immagine differiscono dalla forma usata per l'inferenza, il padding letterbox causa un secondo disallineamento.
Nell'immagine dell'autobus, il riempimento più basso è quello della seconda persona (0.31): è tagliata dal bordo destro del fotogramma e si trova davanti all'autobus, quindi la maggior parte del riquadro contiene lo sfondo e l'autobus.
Per la zona, creala una volta usando le dimensioni originali e riutilizza il test della maschera:
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%}")Nell'immagine dell'autobus:
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%Le gambe delle persone si trovano davvero nel terzo inferiore, quindi il riquadro e la maschera concordano con uno scarto di circa dieci punti. L'autobus è il falso positivo: il suo riquadro entra per il 4% nella zona, mentre la maschera non vi entra affatto. Con una regola "più dello 0% all'interno", i riquadri segnalerebbero l'autobus, mentre le maschere no.