포즈 모델 레이블 지정 및 학습
가시성 정보와 함께 키포인트 레이블을 작성하고, kpt_shape와 flip_idx를 올바르게 설정한 다음, COCO8-pose로 학습하고, 키포인트를 신뢰도 기준을 적용한 관절 각도로 변환합니다.
포즈 추정은 사람 한 명을 번호가 지정된 점 17개로 표현하며, 이 점으로부터 제품에 활용되는 수치가 나옵니다. 인체공학을 위한 팔꿈치 각도, 재활을 위한 무릎 각도, 낙상 감지를 위한 엉덩이 높이 등이 그 예입니다. 데이터셋의 두 가지 키가 해당 점을 신뢰할 수 있는지 결정합니다. 둘 중 하나라도 잘못 설정하면 오류가 발생하지 않고, 모델이 좌우를 혼동하거나 가장 비용이 적게 드는 증강을 조용히 적용하지 않은 채 학습됩니다.
실습형
키포인트 레이블 행#

포즈 레이블 행은 키포인트를 추가한 검출 행입니다. 모든 좌표는 0–1 범위로 정규화되며, 모든 행에서 키포인트 순서는 동일하게 고정됩니다. 실제 COCO8-pose 행에서 키포인트 네 개 뒤를 생략한 예:
0 0.671279 0.617945 0.645759 0.726859 0.519751 0.381250 2.000000 0.550936 0.348438 2.000000 0.488565 0.367188 2.000000 0.642412 0.354687 2.000000 ...| 값 | 의미 |
|---|---|
0 | 클래스 인덱스(사람) |
0.671279 0.617945 0.645759 0.726859 | 박스 중심 x, 중심 y, 너비, 높이 |
0.519751 0.381250 2 | 키포인트 0(코): x, y, 가시성 |
| … | 키포인트 16개 추가, 동일한 패턴: 행마다 값 5 + 17 × 3 = 56개 |
가시성 플래그는 COCO 규칙을 따릅니다. 2는 보임, 1은 레이블이 지정되었지만 가려짐, 0은 레이블이 지정되지 않음을 의미합니다. Ultralytics는 손실 계산에서 v = 0인 키포인트를 마스킹하므로, 손목이 누락되어도 모델이 손목을 (0, 0)으로 예측하도록 학습되지 않습니다.

자신 있게 위치를 추정할 수 있는 가려진 관절(탁자 뒤의 엉덩이, 등 뒤의 손목)에는 1을 사용하세요. 이런 관절도 학습에 사용됩니다. 아무도 위치를 파악할 수 없는 관절에만 0을 사용하세요. 보이지 않는 관절을 모두 0으로 지정하면 모델에 필요한 어려운 사례를 정확히 버리게 됩니다.
포즈 데이터셋 가이드에서는 가시성 정보가 없는 데이터셋을 위한 두 값 형식(x y만 사용, kpt_shape: [K, 2])도 설명합니다.
kpt_shape 및 flip_idx#
데이터셋 YAML에는 일반적인 path, train, val, names 외에 키 두 개가 추가됩니다:
kpt_shape: [17, 3] # 17 keypoints, each x, y, visibility
flip_idx: [0, 2, 1, 4, 3, 6, 5, 8, 7, 10, 9, 12, 11, 14, 13, 16, 15]
flip_idx은 좌우 반전 증강 시 각 키포인트가 거울 이미지에서 어떤 키포인트로 대응하는지 지정합니다. 사람을 반전하면 왼쪽 손목(9)이 오른쪽 손목(10)이 있던 위치로 이동합니다. 따라서 flip_idx의 9번 위치에는 10이, 10번 위치에는 9가 들어가며 코(0)는 그대로 0에 대응합니다. 모든 증강은 픽셀과 함께 스켈레톤을 이동시키며, 인덱스 맵이 필요한 증강은 좌우 반전뿐입니다:

설치된 소스에서 확인한 결과, flip_idx이 잘못되면 다음과 같은 일이 발생합니다:
- 누락: 학습 시 경고가 기록되고
fliplr과flipud이 0으로 설정되므로, 가장 비용이 적게 드는 증강을 사용할 수 없게 됩니다. - 길이가 잘못됨: 불일치를 설명하는
ValueError과 함께 학습이 중단됩니다. 소리 내어 알리는 유일한 오류입니다. - 길이는 맞지만 쌍이 잘못됨: 아무런 경고도 표시되지 않습니다. 반전된 샘플의 절반은 모델에 왼쪽이 오른쪽이라고 학습시키고, 검증 이미지의 대칭 포즈에서는 좌우가 뒤바뀐 결과가 나타납니다.
COCO8-pose로 학습하기#

from ultralytics import YOLO
model = YOLO("yolo26n-pose.pt")
model.train(data="coco8-pose.yaml", epochs=1, imgsz=320, device="cpu")
metrics = model.val()
print(f"box mAP50-95: {metrics.box.map:.3f}")
print(f"pose mAP50-95: {metrics.pose.map:.3f}") Class Images Instances Box(P R mAP50 mAP50-95) Pose(P R mAP50 mAP50-95)
all 4 14 1 0.783 0.893 0.643 0.966 0.571 0.575 0.326
box mAP50-95: 0.643
pose mAP50-95: 0.326세그멘테이션과 마찬가지로 표는 두 블록으로 구성됩니다. Box는 사람 바운딩 박스를 평가하고, Pose는 동일한 mAP 계산 체계 안에서 Object Keypoint Similarity(OKS)를 사용해 키포인트 배치를 평가합니다. 포즈 제품의 출시 기준은 Pose mAP50-95이며, 다음 강의에서는 OKS가 키포인트의 정답 여부를 판정하는 방법을 살펴봅니다.
키포인트에서 관절 각도까지#
result.keypoints에는 COCO 순서에 따라 xy(N × K × 2, 픽셀 단위), xyn(정규화됨), conf(N × K)이 저장됩니다. 관절의 각도는 그 지점에서 만나는 두 팔다리 벡터 사이의 각도이며, 내적을 사용해 계산합니다:
import numpy as np
from ultralytics import YOLO
model = YOLO("yolo26n-pose.pt")
result = model("https://ultralytics.com/images/bus.jpg")[0]
kpts = result.keypoints.cpu().numpy() # people x 17 x (x, y, conf)
SHOULDER, ELBOW, WRIST = 5, 7, 9 # left arm, COCO keypoint order
for person, (xy, conf) in enumerate(zip(kpts.xy, kpts.conf)):
if conf[[SHOULDER, ELBOW, WRIST]].min() < 0.5:
print(f"person {person}: left arm not visible enough")
continue
a, b, c = xy[SHOULDER], xy[ELBOW], xy[WRIST]
cos = np.dot(a - b, c - b) / (np.linalg.norm(a - b) * np.linalg.norm(c - b))
print(f"person {person}: left elbow angle {np.degrees(np.arccos(cos)):.0f} deg")person 0: left elbow angle 71 deg
person 1: left elbow angle 96 deg
person 2: left elbow angle 59 deg
person 3: left arm not visible enough
person 4: left arm not visible enough한 줄씩 살펴보면, kpts.xy[p]은 사람 p의 픽셀 단위 17개 포인트이고, a - b와 c - b은 팔꿈치에서 뻗어 나온 위팔과 아래팔 벡터입니다. 두 벡터의 정규화된 내적은 두 벡터 사이 각도의 코사인입니다. 팔을 곧게 펴면 약 180°, 완전히 구부리면 약 30°로 표시됩니다. 사람 3과 4는 왼팔 관절 중 하나 이상이 0.5 미만의 신뢰도를 가지므로 스크립트가 측정을 거부합니다.
증상: 팔이 프레임 밖에 있는 사람의 각도가 확신에 찬 것처럼 보입니다. 원인: 모델은 가려졌는지 여부와 관계없이 모든 키포인트의 좌표를 반환합니다. 해결 방법: 관절의 신뢰도가 기준값(여기서는 0.5)보다 낮으면 해당 계산을 건너뛰고, 건너뛴 횟수를 기록하세요. 건너뛰는 비율이 증가하면 카메라나 조명 변화가 시작되었다는 초기 신호일 수 있습니다.
정답 보기
import numpy as np
from ultralytics import YOLO
ARMS = {"left": (5, 7, 9), "right": (6, 8, 10)} # shoulder, elbow, wrist
kpts = YOLO("yolo26n-pose.pt")("https://ultralytics.com/images/bus.jpg")[0].keypoints.cpu().numpy()
for person, (xy, conf) in enumerate(zip(kpts.xy, kpts.conf)):
for side, (s, e, w) in ARMS.items():
if conf[[s, e, w]].min() < 0.5:
continue
a, b, c = xy[s], xy[e], xy[w]
cos = np.dot(a - b, c - b) / (np.linalg.norm(a - b) * np.linalg.norm(c - b))
print(f"person {person}: {side} elbow {np.degrees(np.arccos(cos)):.0f} deg")person 0: left elbow 71 deg
person 0: right elbow 94 deg
person 1: left elbow 96 deg
person 2: left elbow 59 deg
person 2: right elbow 135 deg사람 0과 2는 양팔을 모두 측정할 수 있습니다. 사람 1의 오른팔과 사람 3 및 4의 양팔은 0.5 신뢰도 기준 미만이므로 스크립트는 추측하는 대신 해당 팔을 제외합니다.
flip_idx이 누락되면 경고와 함께 좌우 반전 증강이 비활성화됩니다. 길이가 잘못되면 ValueError과 함께 학습이 중단됩니다. 형식상 유효해 보이지만 쌍이 잘못된 flip_idx는 반전된 라벨로 조용히 학습되며 검증 시 좌우가 뒤바뀐 결과로 나타납니다.