Ultralytics YOLO27: 대기자 명단에 등록하세요.
Get Started
강의중급

사용자 정의 키포인트 및 OKS 지표

비인체 객체용 키포인트 스키마를 설계하고, 데이터셋 YAML을 작성 및 검사하며, 각 포인트의 배치 정밀도를 바탕으로 OKS 시그마를 설정합니다.

포즈는 사람에게만 적용되는 것이 아닙니다. 지게차의 포크 끝, 택배 라벨의 모서리, 동물의 코와 꼬리처럼 안정적이고 이름을 붙일 수 있는 포인트가 있는 객체라면 무엇이든 포즈 데이터셋이 될 수 있습니다. 첫날 작성한 스키마는 영구적으로 사용됩니다. 모든 라벨과 좌우 반전 쌍, 모든 지표가 스키마에 따라 달라지며, 나중에 변경하려면 모든 항목을 다시 라벨링해야 합니다. 이 강의에서는 천 장의 이미지에 라벨을 붙이기 전에 스키마를 올바르게 설계하고, 지표가 스키마를 정확히 어떻게 평가하는지 보여줍니다.

학습 성과

좌우 반전 쌍과 OKS 시그마를 포함한 사용자 정의 키포인트 스키마의 데이터셋 YAML, 실제 데이터에서 검사를 통과하는 라벨 검사기, 각 시그마가 허용하는 픽셀 오차 수의 예측값을 준비합니다.

속성 과정
이미 사용법을 알고 계신다면, 간단히 요약해 드리겠습니다.
  1. 모호하지 않고 모든 포즈에 존재하며 다운스트림 코드에서 사용하는 포인트를 3~12개 선택합니다.

  2. 포인트 순서를 정한 다음 좌우 대응 포인트를 맞바꾸고 중심 포인트는 자기 자신에 대응시켜 flip_idx을 작성합니다.

  3. 주석 작업자가 포인트를 배치하는 정밀도가 서로 다른 경우 kpt_oks_sigmas을 추가합니다.

  4. 학습 전에 열 수, 가시성 값, 자기 자신의 역변환인 flip_idx을 검사하는 라벨 검사기를 실행합니다.

실습형

키포인트 스키마 설계하기#

좋은 키포인트는 세 가지 속성을 공유합니다. 모호하지 않음(두 주석 작업자가 같은 위치에 포인트를 배치함), 안정적임(객체가 취하는 모든 포즈에 포인트가 존재함), 유용함(다운스트림 계산에서 포인트를 사용함)입니다. 천장 카메라로 촬영한 지게차에서 제품 관련 질문이 "트럭이 움직일 때 포크가 올라가 있는가?"라고 가정해 보겠습니다.

인덱스키포인트존재하는 이유대칭 포인트
0left_fork_tip포크의 범위와 방향1
1right_fork_tip포크의 범위와 방향0
2mast_top마스트를 기준으로 한 포크 높이2(자기 자신)
3mast_base차체의 기준점3(자기 자신)

모든 포인트는 계산에 기여하는 이유가 있어야 합니다. 포크 끝은 방향을 제공하고, 마스트 쌍은 높이를 제공합니다. 후보 포인트마다 다음 기준을 적용해 보세요:

"혹시 모르니" 포인트를 추가하지 마세요. 포인트 하나를 추가할 때마다 모든 인스턴스에 대한 라벨링 시간이 늘어나고, 아무도 사용하지 않는 포인트는 지표에 노이즈만 더합니다. 공개 데이터셋에서도 다양한 사례를 볼 수 있습니다. Tiger-Pose는 가시성 정보 없이 포인트 12개를 사용하고, Dog-Pose는 24개, Hand Keypoints는 21개를 사용합니다:

0번 손목부터 각 손가락의 관절을 거쳐 20번 새끼손가락 끝까지 21개 랜드마크에 번호를 매긴 손 키포인트 스키마

Dog-Pose 예시: 강아지 바운딩 박스와 얼굴, 다리, 몸에 표시된 색상 키포인트가 있는 강아지 이미지

데이터셋 YAML 작성하기#

path: forklift-pose
train: images/train
val: images/val

kpt_shape: [4, 3] # 4 keypoints, each x, y, visibility
flip_idx: [1, 0, 2, 3] # fork tips swap, mast points stay
kpt_oks_sigmas: [0.05, 0.05, 0.08, 0.08] # tips are sharp, mast ends are fuzzier

names:
  0: forklift

kpt_names:
  0:
    - left_fork_tip
    - right_fork_tip
    - mast_top
    - mast_base
  • kpt_names은 클래스별 사람 및 도구의 포인트 순서를 정의합니다.
  • flip_idx은 자기 자신의 역변환이어야 합니다. 두 번 적용하면 모든 인덱스가 원래 값으로 돌아옵니다.
  • kpt_oks_sigmas은 선택 사항이며 학습 중 키포인트 손실과 검증 중 OKS에 모두 가중치를 적용합니다. 이 값이 지정된 경우 키포인트마다 양수 값 하나가 있어야 하며, 그렇지 않으면 실행이 ValueError과 함께 중단됩니다.

Ultralytics Platform에서는 동일한 스키마를 스켈레톤 템플릿으로 사용합니다. 새 템플릿은 기존 템플릿에서 시작할 수 있으므로(기존 템플릿에서 시작…) 모든 키포인트를 일일이 배치하는 대신 포인트 이름을 바꾸고 불필요한 포인트를 줄이면 됩니다. 스키마 표의 인덱스 순서를 유지하세요:

Ultralytics Platform의 새 스켈레톤 템플릿 대화상자: 기존 템플릿에서 시작한 17개 포인트의 사람 스켈레톤과 이름을 변경할 수 있도록 나열된 nose부터 left_wrist까지의 키포인트

학습 전에 라벨 검사하기#

포즈 데이터셋은 조용히 실패합니다. 행에 키포인트 하나가 빠지면 이후의 모든 값이 밀리고, 버그가 있는 내보내기 도구가 출력한 가시성 값 3은 다른 숫자와 마찬가지로 처리됩니다. 이 검사기는 Ultralytics 자체 로더로 YAML을 불러오고 모든 행을 검증합니다. COCO8-pose에서 그대로 실행할 수 있으며, YAML 경로를 바꾸면 됩니다:

from pathlib import Path

from ultralytics.data.utils import check_det_dataset

data = check_det_dataset("coco8-pose.yaml")  # resolves paths, downloads if needed
k, dims = data["kpt_shape"]
flip = data.get("flip_idx", [])
assert not flip or sorted(flip) == list(range(k)), "flip_idx must list every keypoint index once"
assert all(flip[flip[i]] == i for i in range(len(flip))), "flip_idx must undo itself: pairs swap, center points stay"

expected = 5 + k * dims  # class + 4 box values + k keypoints of `dims` values
bad = 0
for label in Path(data["path"], "labels").rglob("*.txt"):
    for n, row in enumerate(label.read_text().splitlines(), 1):
        values = row.split()
        if len(values) != expected:
            bad += 1
            print(f"{label.name}:{n} has {len(values)} values, expected {expected}")
        elif dims == 3 and not {float(v) for v in values[7::3]} <= {0, 1, 2}:
            bad += 1
            print(f"{label.name}:{n} has a visibility flag outside 0/1/2")
print(f"kpt_shape={data['kpt_shape']}, flip_idx ok, {bad} bad rows")
kpt_shape=[17, 3], flip_idx ok, 0 bad rows

values[7::3]은 첫 번째 가시성 플래그(클래스, 바운딩 박스 값 4개, 그 다음 x y v)부터 시작해 이후의 세 번째 값마다 가져옵니다.

OKS가 키포인트를 평가하는 방법#

Pose mAP는 바운딩 박스 IoU를 Object Keypoint Similarity(OKS)로 대체합니다. 각 라벨링된 키포인트에 대해 OKS는 예측과 라벨 사이의 픽셀 거리를 객체 크기와 해당 키포인트의 시그마에 따라 조정해 0~1 사이 점수로 변환한 다음, 라벨링된 키포인트 전체의 평균을 계산합니다. Pose mAP50-95는 IoU 임계값에 대한 box mAP와 같은 방식으로 OKS 임계값 0.50~0.95에서 정밀도를 평균합니다. 공통 계산 체계에 대한 자세한 내용은 성능 지표 가이드를 참조하세요.

시그마는 직접 측정해 보면 가장 쉽게 이해할 수 있습니다. 이 스크립트는 포즈 검증기가 호출하는 kpt_iou 함수를 사용해 동일한 지게차 예측을 두 가지 시그마로 평가합니다:

import torch

from ultralytics.utils.metrics import kpt_iou

# One labeled forklift, 4 keypoints (x, y, visibility) in pixels, inside a 200 x 120 px box
label = torch.tensor([[[40.0, 30, 2], [40, 90, 2], [170, 20, 2], [170, 100, 2]]])
area = torch.tensor([200 * 120 * 0.53])  # Ultralytics scales box area by 0.53 to approximate object area

for offset in (2, 5, 10, 20):
    pred = label.clone()
    pred[..., 0] += offset  # every predicted keypoint lands `offset` pixels to the right
    sharp = kpt_iou(label, pred, area, sigma=[0.025] * 4).item()
    loose = kpt_iou(label, pred, area, sigma=[0.10] * 4).item()
    print(f"{offset:2d} px off: OKS {sharp:.2f} with sigma 0.025, {loose:.2f} with sigma 0.10")
 2 px off: OKS 0.94 with sigma 0.025, 1.00 with sigma 0.10
 5 px off: OKS 0.67 with sigma 0.025, 0.98 with sigma 0.10
10 px off: OKS 0.21 with sigma 0.025, 0.91 with sigma 0.10
20 px off: OKS 0.00 with sigma 0.025, 0.67 with sigma 0.10

크기가 200 × 120픽셀인 지게차에서 예측이 10픽셀 벗어나면 시그마 0.025일 때 OKS 임계값 0.50을 통과하지 못하지만, 시그마 0.10일 때는 0.90까지 모든 임계값을 통과합니다. 같은 모델과 같은 오차인데도 Pose mAP는 크게 달라집니다.

시그마효과용도
작음(≈ 0.025–0.05)몇 픽셀만 벗어나도 점수가 감소합니다눈, 포크 끝, 모서리와 같은 선명한 포인트
큼(≈ 0.08–0.10)넉넉한 허용 오차엉덩이, 마스트 중앙과 같은 모호한 포인트
기본값kpt_shape: [17, 3]의 COCO 시그마를 사용하고, 그렇지 않으면 모든 포인트에 1/K를 적용합니다여기서 시작한 다음 측정하세요

시그마에는 각 포인트를 주석 작업자가 얼마나 일관되게 배치하는지 반영해야 합니다. 이를 측정하려면 두 사람이 동일한 이미지 50장에 라벨을 붙이고 키포인트별 거리를 비교하세요. 의견 차이가 큰 포인트에는 더 큰 시그마를 부여해야 합니다. 그렇지 않으면 지표가 사람의 노이즈에 대해 모델을 부당하게 평가합니다.

지표는 하나의 스키마 안에서만 비교 가능

위 표에 나온 것처럼 kpt_oks_sigmas을 변경하면 모델은 그대로여도 포즈 mAP가 달라집니다. 첫 기준선을 만들기 전에 시그마를 고정하고, 모든 결과에 시그마를 기록하며, 서로 다른 값으로 검증한 실행 결과를 비교하지 마세요.

사용해 보기

자신의 분야에서 사용하는 객체의 키포인트 스키마를 설계하세요. 키포인트 3~8개와 각 키포인트의 한 줄 설명, 대칭 포인트, 시그마를 나열합니다. YAML을 작성하고 COCO8-pose 및 직접 라벨링한 데이터 몇 개를 대상으로 check_pose_labels.py을 실행한 다음, oks.py을 사용해 가장 선명한 포인트의 OKS가 0.5 미만으로 떨어지기 전까지 허용되는 픽셀 오차 수를 확인하세요.

완료 조건
다음 조건을 모두 충족하면 레슨을 완료한 것입니다.
  • 스키마 표에 모든 키포인트의 이름, 목적, 대칭 포인트, 시그마가 명시되어 있습니다.

  • YAML에는 kpt_shape, 자기 자신의 역변환인 flip_idx, 그리고 사용하는 경우 키포인트마다 양수 시그마 하나가 포함되어 있으며, 검사기는 샘플에서 잘못된 행이 0개라고 보고합니다.

  • 가장 선명한 키포인트가 OKS 0.5에서 더 이상 유효한 것으로 계산되지 않는 픽셀 오차를 말할 수 있습니다.

정답 보기

컨베이어 위의 택배를 위에서 촬영한 예시입니다. 확인하려는 사항은 "라벨 면이 위를 향하고 있는가, 그리고 어느 방향으로 회전되어 있는가?"입니다.

인덱스키포인트목적대칭 포인트시그마
0label_top_left라벨 방향10.04
1label_top_right라벨 방향00.04
2label_bottom_right라벨 방향30.04
3label_bottom_left라벨 방향20.04
kpt_shape: [4, 3]
flip_idx: [1, 0, 3, 2]
kpt_oks_sigmas: [0.04, 0.04, 0.04, 0.04]

flip_idx은 자기 자신의 역변환 검사를 통과합니다. 0 → 1 → 0 및 2 → 3 → 2입니다. 라벨을 좌우 반전하면 좌우는 바뀌지만 위아래는 유지되므로, 왼쪽 위는 오른쪽 아래가 아니라 오른쪽 위와 짝을 이룹니다.

허용 오차를 구하려면 oks.py에서 sigma=[0.04] * 4을 설정하고 오프셋을 늘리세요. 라벨 바운딩 박스가 스케일을 결정하므로 이미지에서 일반적인 택배 라벨 크기의 바운딩 박스를 사용합니다. OKS가 처음으로 0.5 미만으로 떨어지는 오프셋이 주석 작업자의 정밀도를 고려할 때 모델이 허용할 수 있는 최대 오차입니다.

다음 단계

다음으로 방향은 중요하지만 모양이 단순한 직사각형인 객체를 다룹니다. 바로 회전된 객체의 방향성 바운딩 박스입니다.