Ultralytics YOLO27: Iscriviti alla lista d'attesa.
Get Started
Lezioneintermedio

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.

Esito

Esegui script che riportano l'area della maschera, il rapporto di riempimento, la dimensione del contorno e la sovrapposizione con un altro oggetto, e salvano un ritaglio trasparente per ogni oggetto, usando per ciascuna operazione la vista corretta della maschera.

Percorso rapido
Se sai già come muoverti, ecco la versione breve.
  1. Usa retina_masks=True per fare previsioni ogni volta che misuri o ritagli, in modo che le maschere corrispondano alle dimensioni dell'immagine originale.

  2. Usa masks.data (N × H × W) per aree, sovrapposizioni e ritagli; usa masks.xy (poligoni in pixel) per disegnare ed esportare i contorni.

  3. L'area è mask.sum(); il riempimento è l'area della maschera ÷ l'area della bounding box; la sovrapposizione è (mask_a & mask_b).sum().

  4. Controlla result.masks is None prima di leggere le maschere: se non viene trovato nulla, è None.

Pratico

Due viste della stessa maschera#

Una persona isolata con una maschera di segmentazione Ultralytics YOLO, nella posizione originale nell'immagine completa su sfondo nero

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.

AttributoFormaCoordinateUsala per
masks.dataTensore N × H × W di valori 0/1dimensioni di inferenza per impostazione predefinita; dimensioni originali con retina_masks=Truearee in pixel, sovrapposizioni tra maschere, ritagli
masks.xyelenco di N array, ciascuno P × 2pixel dell'immagine originaledisegnare contorni, esportare poligoni
masks.xynelenco di N array, ciascuno P × 2normalizzate 0–1riscrivere le etichette poligonali YOLO
masks.data usa per impostazione predefinita la dimensione di inferenza

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 pts

Cosa leggere nell'output:

  • masks.data: (5, 1080, 810) corrisponde a orig_shape, quindi retina_masks=True ha funzionato e ogni conteggio di pixel usa i pixel originali.
  • L'indice i collega le due viste. result.masks.data[i] appartiene a result.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:

La stessa persona ritagliata sulla sua bounding box, con ogni pixel esterno alla maschera impostato sul nero

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 mask

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

Regola decisionale

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#

DomandaCalcoloAttenzione 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 regioneDecidi 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#

Leggere le maschere quando non è stato rilevato nulla

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.

Area dei poligoni per oggetti divisi

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.

Mescolare risoluzioni

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().

Provalo

Esegui masks.py su una tua immagine con almeno tre oggetti e indica l'oggetto con il riempimento più basso, spiegando perché il suo riquadro ne sovrastima le dimensioni. Esegui di nuovo il codice senza retina_masks=True e annota come cambiano masks.data.shape e le aree. Poi adatta overlap.py per verificare se un oggetto qualsiasi nella tua immagine si sovrappone a un rettangolo che disegni sul terzo inferiore del fotogramma.

Criteri di completamento
La lezione è completata quando tutte le seguenti condizioni sono soddisfatte.
  • Hai un ritaglio PNG trasparente per ogni oggetto rilevato nella tua immagine.

  • Sai indicare il rapporto di riempimento più basso e spiegare la differenza tra masks.data con e senza retina_masks=True.

  • Il test della zona stampa una sovrapposizione basata sul riquadro e una basata sulla maschera per ogni oggetto, e sai spiegare i casi in cui i due risultati non coincidono.

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.

Prossimi passi

Ora eliminiamo del tutto le istanze ed etichettiamo ogni pixel con la segmentazione semantica.

Accedi per salvare i progressi