코드에서 마스크 사용하기: 면적, 겹침, 윤곽, 분리 이미지
masks.data와 masks.xy를 올바르게 읽고, 객체 면적과 채움 비율을 측정하며, 박스 대신 마스크로 겹침을 테스트하고 프레임에서 객체를 분리합니다.
세그멘테이션 모델은 코드가 마스크를 의사 결정에 활용해야 비로소 가치를 발휘합니다. 버스 사진에서 보행자 각각의 박스는 버스 박스와 56~74% 겹치지만, 보행자 마스크와 버스 마스크는 전혀 겹치지 않습니다. 박스를 기반으로 "차량 구역 안의 사람" 알림을 만들면 보도에 서 있는 네 사람 모두에 대해 알림이 울립니다. 마스크는 이 질문에 올바르게 답하지만, 기본 마스크 뷰에는 그럴듯하지만 잘못된 수치를 만드는 크기 및 형식 관련 함정이 있습니다.
실습형
동일한 마스크를 나타내는 두 가지 뷰#

result.masks에는 각 인스턴스 마스크가 두 가지 표현으로 저장됩니다. 다음 코드 줄의 작업에 맞는 표현을 선택하세요. 모든 속성은 Results 참조 문서에 나와 있습니다.
| 속성 | 형태 | 좌표 | 용도 |
|---|---|---|---|
masks.data | 0/1로 구성된 N × H × W 텐서 | 기본값은 추론 크기이며, retina_masks=True을 사용하면 원본 크기입니다. | 픽셀 면적, 마스크 간 겹침, 분리 이미지 |
masks.xy | 각각 P × 2인 N개 배열의 목록 | 원본 이미지 픽셀 | 윤곽 그리기, 폴리곤 내보내기 |
masks.xyn | 각각 P × 2인 N개 배열의 목록 | 0–1 정규화 | YOLO 폴리곤 라벨을 다시 작성하기 |
1080 × 810 버스 이미지에서 masks.data은 (5, 640, 480)(레터박스 처리된 추론 크기)로 반환됩니다. 따라서 픽셀 개수가 약 2.85배 작고 마스크를 원본 이미지에 겹쳐 표시할 수 없습니다. retina_masks=True는 약간의 후처리 비용을 들여 마스크를 원본 해상도로 업샘플링합니다. 측정이나 크롭을 할 때마다 사용하세요.
모든 객체의 면적 측정 및 분리#
이 스크립트는 객체마다 두 뷰를 모두 읽습니다. masks.data에서 픽셀 면적을, masks.xy에서 윤곽 크기를 가져오고, 마스크를 알파 채널로 사용하는 투명 PNG 분리 이미지를 생성합니다. 더 다양한 분리 이미지 변형은 세그멘테이션 객체 분리 가이드를 참조하세요.
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출력에서 확인할 항목:
masks.data: (5, 1080, 810)은orig_shape과 일치하므로retina_masks=True가 제대로 적용되었고 모든 픽셀 수가 원본 픽셀 기준입니다.- 인덱스
i가 두 항목을 연결합니다.result.masks.data[i]은result.boxes[i]에 속하므로, 이름과 신뢰도는 박스에서 가져오고 픽셀은 마스크에서 가져옵니다. - 채움 비율은 감지기만으로는 알 수 없는 정보를 보여줍니다. 버스는 박스의 62%를 채우지만, 프레임 오른쪽 가장자리에서 잘리고 버스에 붙어 서 있는 두 번째 사람은 31%만 채웁니다.
- 분리 이미지는 사람이나 버스만 포함하고 다른 것은 포함하지 않는 BGRA PNG이며, 카탈로그, 두 번째 모델 또는 검토 대기열에서 바로 사용할 수 있습니다.
분리 가이드의 자르기 변형은 투명 배경 대신 검은색을 사용합니다.

겹침: 박스가 놓인 위치#
"사람이 제한 구역 안에 있다" 또는 "제품이 컨베이어를 덮는다"와 같은 규칙은 겹침 테스트입니다. 박스와 마스크를 사용해 같은 테스트를 실행하세요.
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사람들은 버스 앞에 서 있으므로 버스 마스크는 각 사람의 윤곽에서 끝나며 마스크는 픽셀을 공유하지 않습니다. 박스는 두 대상 모두를 둘러싼 직사각형이므로 크게 겹칩니다. 같은 계산은 직접 그린 모든 영역에도 적용됩니다. cv2.fillPoly을 사용해 0으로 채운 배열에 폴리곤을 채운 다음, 버스 마스크 대신 사용하세요.
내부에 있는지, 접촉하는지, 덮고 있는지, 가로막는지로 표현되는 규칙은 모두 중첩 검사입니다. 이 검사는 마스크로 실행하세요. 몇 픽셀의 여유가 결과를 바꾸지 않는 근처에 있는지나 왼쪽에 있는지를 판단할 때는 박스로 충분합니다.
픽셀에서 실제 답변으로#
| 질문 | 연산 | 주의할 점 |
|---|---|---|
| 크기는 얼마나 될까요? | mask.sum() × (픽셀당 cm²) | 알려진 물체 하나를 사용해 픽셀당 cm² 값을 한 번 보정합니다. 카메라가 기울어져 있으면 거리에 따라 면적이 달라집니다. 구역별로 보정하거나 한 영역에서만 측정하세요. |
| 영역에서 차지하는 비율은 얼마나 될까요? | 클래스 마스크 픽셀 수 ÷ 영역 픽셀 수 | 겹치는 인스턴스를 한 번 또는 두 번 계산할지 결정합니다. |
| 구역 안에 있나요? | (mask & zone).sum() / mask.sum() | 원본 이미지 크기에 맞춰 구역을 그립니다. |
| 윤곽은 어떤 모양인가요? | masks.xy[i] | 여기서 면적을 계산하기 전에 아래의 분할 객체 경고를 확인하세요. |
주의할 점#
증상: 객체가 없는 프레임에서 AttributeError: 'NoneType' object has no attribute 'xy'이 발생합니다. 원인: 모델이 아무것도 찾지 못하면 result.masks은 빈 객체가 아니라 None입니다. 해결 방법: result.boxes을 반복하고(세그멘테이션 모델에서는 비어 있지만 절대 None가 되지 않음), 반복문 안에서 마스크를 인덱싱하세요. masks.py가 예시이며, 또는 먼저 result.masks is None을 확인하세요.
증상: 가려진 객체의 면적이 크게 잘못 계산됩니다. 원인: 가림 요소가 객체를 분리하면 masks.xy은 모든 윤곽선을 하나의 배열로 합칩니다. 이 배열에 cv2.contourArea을 적용하면 실제로 존재하지 않는 형태의 면적을 측정하게 됩니다. 해결 방법: masks.data의 픽셀 개수로 면적을 계산하고, 폴리곤은 그리기에 사용하세요.
증상: 두 번의 실행에서 면적이 약 2.85배 차이 납니다. 원인: 한 번의 실행에서 retina_masks=False을 사용해 면적이 추론 픽셀 단위로 계산되었습니다. 해결 방법: 모든 경우에 retina_masks=True을 사용해 측정하고, CUDA에서는 .numpy() 전에 .cpu()를 호출하세요.
정답 보기
retina_masks=True 없이 버스 이미지에 실행하면 masks.data이 (5, 640, 480) 형태로 반환됩니다. 모든 픽셀 면적은 (1080 × 810) / (640 × 480) ≈ 2.85배 줄어들며, 크롭 이미지의 경우 마스크가 orig_img과 더 이상 정렬되지 않습니다. 이미지의 종횡비가 추론 크기와 다르면 레터박스 패딩으로 인해 정렬이 추가로 어긋납니다.
버스 이미지에서 채움 비율이 가장 낮은 것은 두 번째 사람(0.31)입니다. 프레임 오른쪽 가장자리에서 잘려 있고 버스에 붙어 서 있어 박스 대부분이 배경과 버스를 포함합니다.
구역은 원본 크기에 맞춰 한 번 생성한 뒤 마스크 테스트에 재사용합니다:
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%}")버스 이미지에서:
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%사람들의 다리는 실제로 아래쪽 3분의 1 영역에 있으므로 박스와 마스크의 값은 약 10%p 이내로 일치합니다. 버스는 오탐입니다. 박스는 구역에 4% 들어가지만 마스크는 전혀 들어가지 않습니다. "구역 안에 0% 초과" 규칙을 적용하면 박스는 버스를 표시하지만 마스크는 표시하지 않습니다.