자세 추정 및 OBB
YOLO26에서 키포인트와 회전 박스를 읽고, 각 항목에 규칙을 적용해 자세나 각도가 실제 신호인지 파악합니다.
요양 시설에서는 누군가 넘어졌는지 알아야 하고, 항구에서는 드론 이미지에서 나란히 빽빽하게 정박한 선박 수를 세야 합니다. 일반 박스로는 두 문제를 모두 해결할 수 없습니다. 박스만으로는 사람이 똑바로 서 있는지 알 수 없고, 대각선으로 놓인 선박을 둘러싼 세로 박스에는 대부분 물이 포함되기 때문입니다. 자세 추정과 OBB는 바로 이 두 문제를 해결하기 위해 만들어졌습니다.
실습형
자세 추정: 자세가 신호인 경우#

자세 추정 모델은 사람마다 박스와 고정된 목록의 키포인트를 출력합니다. COCO-Pose로 학습한 모델은 사람마다 17개를 예측합니다. 코, 눈, 귀, 어깨, 팔꿈치, 손목, 엉덩이, 무릎, 발목이 항상 같은 순서로 출력되므로, 인덱스 0은 코, 11은 왼쪽 엉덩이, 15는 왼쪽 발목입니다. 각 키포인트에는 신뢰도도 함께 제공됩니다.
가장 간단하면서도 유용한 규칙은 다음과 같습니다. 서 있는 신체는 너비보다 높이가 크고, 바닥에 누운 신체는 높이보다 너비가 큽니다. 모델이 신뢰하는 키포인트만 사용해 버스 이미지에 적용하면 다음과 같습니다.
from ultralytics import YOLO
model = YOLO("yolo26n-pose.pt")
result = model("https://ultralytics.com/images/bus.jpg", verbose=False)[0]
for xy, conf in zip(result.keypoints.xy, result.keypoints.conf):
visible = xy[conf > 0.5] # drop keypoints the model couldn't see
width = float(visible[:, 0].max() - visible[:, 0].min())
height = float(visible[:, 1].max() - visible[:, 1].min())
posture = "unknown" if len(visible) < 5 else "lying" if width > height else "upright"
print(f"{len(visible):2d}/17 keypoints visible, body {width:3.0f} px wide x {height:3.0f} px tall -> {posture}")16/17 keypoints visible, body 117 px wide x 427 px tall -> upright
6/17 keypoints visible, body 80 px wide x 359 px tall -> upright
16/17 keypoints visible, body 91 px wide x 384 px tall -> upright
1/17 keypoints visible, body 0 px wide x 0 px tall -> unknown
2/17 keypoints visible, body 3 px wide x 6 px tall -> unknown세 사람은 분명히 똑바로 서 있습니다. 마지막 두 행은 왼쪽 가장자리에서 잘린 사람과 신뢰도가 낮은 중복 검출입니다. 보이는 키포인트가 한두 개뿐이면 판별할 수 없으므로, 이 규칙은 추측하는 대신 "알 수 없음"이라고 표시합니다. 실제 운영 환경의 낙상 감지기는 트래킹을 통해 시간 요소를 추가합니다. 빠르게 쓰러진 뒤 몇 초 이상 누워 있는지 확인합니다.
증상: 손목이 허공에 떠 있는 스켈레톤과 몸이 반쯤 보이는 사람에게 적용되는 자세 규칙입니다. 원인: keypoints.xy은 모델이 볼 수 없었던 키포인트까지 17개 모두의 위치를 반환합니다. 해결: 위에서처럼 keypoints.conf로 필터링하고, 판단을 내리기 전에 보이는 키포인트가 최소 개수 이상인지 확인합니다.
신체가 무엇을 하고 있는지에 따라 답이 달라지는 경우 자세 추정이 유용합니다. 예를 들어 안전을 위한 낙상이나 등을 굽힌 채 물건을 들어 올리는 동작, 운동 모니터링에서처럼 스포츠 동작의 관절 각도와 반복 횟수, 가축의 자세, 맞춤형 키포인트 레이아웃을 사용하는 부품의 고정 랜드마크 등이 있습니다. "여기에 사람이 있습니까?"라는 질문에는 일반 객체 감지를 사용합니다.
OBB: 각도가 중요한 경우#

일반 박스의 변은 이미지와 평행합니다. 방향성 바운딩 박스는 회전 각도를 추가하며, 중심, 너비, 높이, 각도(xywhr)로 저장됩니다. 위성·드론 이미지나 컨베이어 상부 카메라처럼 하향식 이미지에서는 객체가 다양한 각도로 놓입니다. 위의 요트처럼 대각선으로 놓인 객체를 세로 박스로 둘러싸면 대부분 배경이 포함됩니다. 마리나의 드론 이미지에서 그 차이를 측정해 보겠습니다.
from ultralytics import YOLO
model = YOLO("yolo26n-obb.pt")
result = model("https://ultralytics.com/images/boats.jpg", verbose=False)[0]
obb = result.obb
rotated_area = obb.xywhr[:, 2] * obb.xywhr[:, 3] # width x height of each rotated box
x1, y1, x2, y2 = obb.xyxy.T # the axis-aligned box that would enclose the same object
upright_area = (x2 - x1) * (y2 - y1)
print(f"{len(obb)} objects, classes: {sorted({result.names[int(c)] for c in obb.cls})}")
cx, cy, w, h, angle = obb.xywhr[0].tolist()
print(f"first box: center=({cx:.0f}, {cy:.0f}) size={w:.0f}x{h:.0f} angle={angle:.2f} rad")
ratio = float((upright_area / rotated_area).median())
print(f"an axis-aligned box would be {ratio:.1f}x larger (median)")173 objects, classes: ['harbor', 'ship']
first box: center=(619, 850) size=33x108 angle=-0.25 rad
an axis-aligned box would be 2.1x larger (median)같은 보트를 둘러싸는 경우 세로 박스는 회전 박스보다 약 두 배 넓은 면적을 차지하며, 추가된 영역은 물입니다. 이로 인해 두 가지 문제가 발생합니다. NMS는 서로 이웃한 보트의 세로 박스가 겹친다고 판단해 실제 보트를 억제할 수 있고, 크기 추정값도 부풀려집니다. DOTA와 같은 항공 데이터셋으로 학습한 OBB 모델은 이러한 장면을 직접 처리합니다. 각도 단위는 라디안입니다. -0.25 rad는 약 -14°입니다.
객체 감지, 자세 추정, OBB 중 선택하기#
| 시나리오 | 적합한 작업 | 이유 |
|---|---|---|
| 인도 위의 사람 수 세기 | 검출 | 존재 여부와 위치가 답입니다 |
| 요양 시설에서 낙상 감지하기 | 자세 추정 | 신호는 신체 방향입니다 |
| 체육관에서 스쿼트 횟수 세기 | 자세 추정 | 신호는 무릎과 엉덩이의 각도입니다 |
| 드론으로 부두에 정박한 보트 수 세기 | OBB | 보트가 대각선으로 빽빽하게 놓여 있습니다 |
| 위에서 본 컨베이어 위의 패키지 | OBB | 방향이 다양하고 서로 맞닿아 있습니다 |
| 도로에서 보는 자동차 | 검출 | 자동차는 프레임 안에서 대체로 똑바로 보입니다 |
포즈는 각 박스에 키포인트를 추가하고, OBB는 각도를 추가합니다. 코드에서 키포인트나 각도를 전혀 읽지 않는다면 일반 디텍터가 라벨링과 실행이 더 빠르고 디버깅도 쉽습니다.
정답 보기
from ultralytics import YOLO
image = "https://ultralytics.com/images/boats.jpg"
upright = YOLO("yolo26n.pt")(image, verbose=False)[0]
rotated = YOLO("yolo26n-obb.pt")(image, verbose=False)[0]
print(f"axis-aligned detector: {len(upright.boxes)} boxes")
print(f"OBB model: {len(rotated.obb)} rotated boxes")axis-aligned detector: 0 boxes
OBB model: 173 rotated boxes차이의 대부분은 기하 구조가 아니라 학습 데이터에서 비롯됩니다. yolo26n.pt은 보트를 주로 측면에서 촬영한 COCO 데이터셋으로 학습했습니다. 기본 임계값에서는 위에서 촬영한 보트를 찾지 못합니다. conf을 0.05로 낮추면 일부를 병이라고 검출합니다. OBB 모델은 항공 이미지로 학습했습니다. 여기에 기하 구조도 영향을 미칩니다. 항공 이미지의 보트로 학습한 객체 감지 모델이라도 박스를 약 두 배 크게 그리며, 빽빽하게 늘어선 보트에서는 NMS 문제가 발생합니다. 작업과 학습 데이터 모두 배포 환경의 시야에 맞아야 합니다.