Usar máscaras em código: área, sobreposição, contornos e recortes
Lê corretamente masks.data e masks.xy, mede a área e o preenchimento dos objetos, testa a sobreposição com máscaras em vez de caixas e recorta objetos da imagem.
Um modelo de segmentação só compensa quando o código transforma as máscaras numa decisão. Na fotografia do autocarro, a caixa de cada peão sobrepõe-se à caixa do autocarro em 56–74%, enquanto as respetivas máscaras têm 0% de sobreposição com a do autocarro — um alerta de «pessoa dentro da zona do veículo» baseado em caixas seria acionado para as quatro pessoas no passeio. As máscaras dão as respostas certas, mas as suas representações predefinidas têm armadilhas de tamanho e formato que produzem valores plausíveis, mas errados.
Prático
Duas representações da mesma máscara#

result.masks contém todas as máscaras de instâncias em duas representações. Escolhe com base no que a linha de código seguinte faz; a referência de Results lista todos os atributos.
| Atributo | Forma | Coordenadas | Utilização |
|---|---|---|---|
masks.data | tensor N × H × W de valores 0/1 | tamanho da inferência por predefinição; tamanho original com retina_masks=True | áreas em píxeis, sobreposições entre máscaras, recortes |
masks.xy | lista de N matrizes, cada uma com P × 2 | píxeis da imagem original | desenhar contornos, exportar polígonos |
masks.xyn | lista de N matrizes, cada uma com P × 2 | normalizado de 0 a 1 | escrever novamente rótulos poligonais YOLO |
Na imagem do autocarro com 1080 × 810, masks.data é devolvido como (5, 640, 480) — o tamanho da inferência com letterbox —, pelo que as contagens de píxeis ficam cerca de 2.85× abaixo do valor correto e não é possível sobrepor a máscara à imagem original. retina_masks=True aumenta as máscaras para a resolução original, com um pequeno custo de pós-processamento. Usa-o sempre que medires ou recortares.
Mede e recorta todos os objetos#
Este script lê as duas representações de cada objeto: a área em píxeis de masks.data, o tamanho do contorno de masks.xy e um recorte PNG com transparência que usa a máscara como canal alfa. O guia de isolamento de objetos de segmentação apresenta mais variantes de recorte.
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 ptsO que deves observar:
masks.data: (5, 1080, 810)corresponde aorig_shape, por issoretina_masks=Truefez o seu trabalho e todas as contagens de píxeis usam os píxeis originais.- O índice
iliga as duas representações.result.masks.data[i]pertence aresult.boxes[i], por isso os nomes e as confianças vêm da caixa e os píxeis vêm da máscara. - O preenchimento revela algo que um detetor não consegue: o autocarro ocupa 62% da sua caixa, enquanto a segunda pessoa, cortada pela margem direita da imagem e encostada ao autocarro, ocupa apenas 31%.
- Os recortes são imagens PNG BGRA que contêm apenas a pessoa ou o autocarro — prontas para um catálogo, um segundo modelo ou uma fila de revisão.
A variante recortada do guia de isolamento usa preto em vez de um fundo transparente:

Sobreposição: onde as caixas falham#
Regras como «uma pessoa está dentro da zona restrita» ou «o produto cobre a passadeira» são testes de sobreposição. Executa o mesmo teste com caixas e com máscaras:
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 maskAs pessoas estão à frente do autocarro, por isso a máscara do autocarro termina no contorno de cada pessoa e as máscaras não partilham píxeis. As caixas são retângulos desenhados à volta de ambos, por isso sobrepõem-se bastante. O mesmo cálculo funciona com qualquer região que desenhes: preenche um polígono numa matriz de zeros com cv2.fillPoly e usa-o no lugar da máscara do autocarro.
Qualquer regra formulada como dentro, a tocar, a cobrir ou a bloquear é um teste de sobreposição. Executa-a em máscaras. As caixas são adequadas para perto de e à esquerda de, situações em que uma margem de alguns píxeis não altera a resposta.
Dos píxeis às respostas do mundo real#
| Pergunta | Cálculo | Atenção a |
|---|---|---|
| Qual é o tamanho? | mask.sum() × (cm² por pixel) | Calibra cm² por pixel uma vez usando um objeto conhecido. Numa câmera inclinada, a área muda com a distância: calibra por zona ou mede apenas numa região. |
| Que parte da região está coberta? | pixels da máscara da classe ÷ pixels da região | Decide se as instâncias sobrepostas contam uma ou duas vezes. |
| Está dentro da zona? | (mask & zone).sum() / mask.sum() | Desenha a zona no tamanho original da imagem. |
| Qual é o formato do contorno? | masks.xy[i] | Consulta o aviso abaixo sobre objetos divididos antes de calcular a área com base nele. |
Armadilhas#
Sintoma: AttributeError: 'NoneType' object has no attribute 'xy' em fotogramas sem objetos. Causa: result.masks é None quando o modelo não encontra nada; não é um objeto vazio. Solução: percorre result.boxes (vazio, nunca None, para modelos de segmentação) e acede às máscaras dentro do loop, como faz masks.py, ou verifica primeiro result.masks is None.
Sintoma: áreas extremamente incorretas para objetos ocluídos. Causa: quando um objeto é dividido por um oclusor, masks.xy junta todos os contornos num único array, e cv2.contourArea nesse array mede uma forma que não existe. Solução: calcula as áreas a partir das contagens de píxeis de masks.data; usa os polígonos para desenhar.
Sintoma: as áreas de duas execuções diferem por um fator de cerca de 2.85. Causa: uma execução usou retina_masks=False, pelo que as áreas estão em píxeis de inferência. Solução: mede sempre com retina_masks=True e, em CUDA, chama .cpu() antes de .numpy().
Mostrar solução
Sem retina_masks=True, a imagem do ônibus retorna masks.data com formato (5, 640, 480): a área de cada pixel diminui por (1080 × 810) / (640 × 480) ≈ 2.85, e a máscara deixa de se alinhar com orig_img para os recortes. Quando a proporção da imagem difere do formato de inferência, o preenchimento letterbox adiciona um segundo desalinhamento.
O menor preenchimento na imagem do ônibus é o da segunda pessoa (0.31): cortada pela borda direita do quadro e de pé junto ao ônibus, a maior parte da caixa é fundo e ônibus.
Para a zona, cria-a uma vez no tamanho original e reutiliza o teste da máscara:
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%}")Na imagem do ônibus:
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%As pernas das pessoas estão mesmo no terço inferior, então caixa e máscara concordam com uma diferença de cerca de dez pontos. O ônibus é o falso positivo: a caixa entra 4% na zona, mas a máscara não entra. Com uma regra de «mais de 0% dentro», as caixas assinalariam o ônibus e as máscaras não.