Ultralytics YOLO27: 대기자 명단에 등록하세요.
Get Started
컴퓨터 비전 기초·작업 선택하기·레슨 5/10
강의초급

자세 추정 및 OBB

YOLO26에서 키포인트와 회전 박스를 읽고, 각 항목에 규칙을 적용해 자세나 각도가 실제 신호인지 파악합니다.

요양 시설에서는 누군가 넘어졌는지 알아야 하고, 항구에서는 드론 이미지에서 나란히 빽빽하게 정박한 선박 수를 세야 합니다. 일반 박스로는 두 문제를 모두 해결할 수 없습니다. 박스만으로는 사람이 똑바로 서 있는지 알 수 없고, 대각선으로 놓인 선박을 둘러싼 세로 박스에는 대부분 물이 포함되기 때문입니다. 자세 추정과 OBB는 바로 이 두 문제를 해결하기 위해 만들어졌습니다.

학습 성과

YOLO26 키포인트를 이용해 자세 규칙을 작성하고, 항공 이미지에서 회전 박스가 얼마나 효과적인지 측정합니다.

속성 과정
이미 사용법을 알고 계신다면, 간단히 요약해 드리겠습니다.
  1. 자세 추정: 사람마다 박스 하나와 키포인트 17개(COCO 순서: 0 코, 5–6 어깨, 11–12 엉덩이, 15–16 발목)를 출력합니다.

  2. 키포인트를 사용하기 전에 신뢰도로 필터링합니다. 가려진 관절에도 좌표는 지정됩니다.

  3. OBB: 중심, 크기, 각도(xywhr, 각도 단위는 라디안)입니다.

  4. 자세가 신호라면 자세 추정을 사용하고, 하향식 뷰에서 객체가 비스듬히 놓여 있다면 OBB를 사용합니다. 그 외의 경우에는 객체 감지를 사용합니다.

실습형

자세 추정: 자세가 신호인 경우#

해안가 산책로를 달리는 세 명의 러너. 각 러너에 사람 박스와 Ultralytics YOLO 자세 추정으로 생성한 17개 키포인트 스켈레톤이 표시되어 있습니다

자세 추정 모델은 사람마다 박스와 고정된 목록의 키포인트를 출력합니다. 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: 각도가 중요한 경우#

항공 이미지에서 비스듬한 각도로 계류 중인 요트 세 척. 각 요트를 Ultralytics YOLO 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는 디텍션에 요소 하나를 추가한 것입니다

포즈는 각 박스에 키포인트를 추가하고, OBB는 각도를 추가합니다. 코드에서 키포인트나 각도를 전혀 읽지 않는다면 일반 디텍터가 라벨링과 실행이 더 빠르고 디버깅도 쉽습니다.

사용해 보기

마리나 이미지에서 솔루션의 비교를 실행합니다. COCO 사전 학습 객체 감지 모델과 OBB 모델을 비교합니다. 두 모델의 검출 수를 기록한 다음, 차이가 박스 기하 구조에서 비롯된 정도와 각 모델의 학습 데이터에서 비롯된 정도를 한 문장으로 작성합니다. 마지막으로 본인의 작업에서 일반 객체 감지보다 자세 추정이나 OBB가 더 적합한 문제를 하나 들고, 코드에 적용할 규칙을 작성합니다.

완료 조건
다음 조건을 모두 충족하면 레슨을 완료한 것입니다.
  • boats.jpg의 두 검출 수와 차이를 설명하는 한 문장을 작성했습니다.

  • 자세 규칙에서 가려진 키포인트를 필터링하는 줄을 짚고, 필터링이 필요한 이유를 설명할 수 있습니다.

  • 자세 추정 또는 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 문제가 발생합니다. 작업과 학습 데이터 모두 배포 환경의 시야에 맞아야 합니다.

다음 단계

마리나 사례에서 학습 데이터가 작업만큼 중요하다는 점을 확인했습니다. 다음으로, 데이터셋이 배포 환경을 반영하는지 확인합니다.