カスタムキーポイントとOKSメトリクス
人間以外のオブジェクト向けのキーポイントスキーマを設計し、データセットYAMLを作成して検証し、各点をどれほど正確に配置できるかに基づいてOKSシグマを設定します。
ポーズの対象は人物だけではありません。フォークリフトのフォーク先端、荷物のラベルの角、動物の鼻や尾など、安定していて名前を付けられる点があるオブジェクトなら、どれでもポーズデータセットにできます。初日に作成するスキーマは恒久的なものです。すべてのラベル、反転ペア、メトリクスがスキーマに依存し、後から変更するとすべてに再ラベル付けが必要になります。このレッスンでは、誰かが何千枚もの画像にラベルを付ける前にスキーマを適切に設計し、メトリクスによる評価方法を具体的に説明します。
実践的
キーポイントスキーマを設計する#
優れたキーポイントには、次の3つの性質があります。曖昧でない(2人のアノテーターが同じ場所に点を置く)、安定している(オブジェクトが取るすべてのポーズに点が存在する)、有用である(後続の計算で使用される)。天井カメラから見たフォークリフトを例にします。製品上の問いは「トラックが移動している間、フォークが上がっているか」です:
| インデックス | キーポイント | 存在する理由 | 反転時の対応点 |
|---|---|---|---|
| 0 | left_fork_tip | フォークの長さと向き | 1 |
| 1 | right_fork_tip | フォークの長さと向き | 0 |
| 2 | mast_top | マストに対するフォークの高さ | 2(自身) |
| 3 | mast_base | シャーシ上の基準点 | 3(自身) |
各点には計算に使われる役割があります。フォークの先端から向きがわかり、マストの2点から高さがわかります。候補となる各点を同じ基準で評価してください:
「念のため」の点を追加するのは避けてください。点を1つ追加するごとに、すべてのインスタンスでラベル付けの時間がかかり、誰も使わない点はメトリクスにノイズを加えるだけです。公開データセットを見ると、その範囲がわかります。Tiger-Poseは可視性情報なしで12点、Dog-Poseは24点、Hand Keypointsは21点を使用しています:


データセット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_basekpt_namesには、クラスごとの人物や器具のキーポイント順序を記述します。flip_idxは自己逆写像である必要があります。2回適用すると、すべてのインデックスが元に戻ります。kpt_oks_sigmasは任意の項目で、学習時のキーポイント損失と検証時のOKSの両方に重みを付けます。指定する場合は、キーポイントごとに正の値を1つ設定する必要があります。設定しないと、実行はValueErrorで停止します。
Ultralytics Platformでは、同じスキーマがスケルトンテンプレートになります。新しいテンプレートは既存のテンプレートから開始できる(既存のテンプレートから開始…)ため、キーポイントを1つずつ手作業で配置するのではなく、名前を変更して不要な点を削除できます。スキーマの表に記載したインデックス順を維持してください:

学習前にラベルを確認する#
ポーズデータセットの不具合は気づかれにくいものです。キーポイントが1つ欠けた行があると後続の値がすべてずれ、バグのあるエクスポーターが出力した可視性の値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 rowsvalues[7::3]は最初の可視性フラグ(クラス、ボックスの4つの値、続いてx y v)から始まり、その後は3つおきに値を取得します。
OKSによるキーポイントのスコアリング#
Pose mAPでは、ボックスのIoUがObject Keypoint Similarity (OKS)に置き換わります。ラベル付きの各キーポイントについて、OKSは予測とラベルのピクセル距離を、オブジェクトのサイズとそのキーポイントのシグマに応じてスケーリングし、0~1のスコアに変換した後、ラベル付きキーポイント全体で平均します。Pose mAP50-95は、IoUのしきい値に対するボックスmAPと同じように、OKSのしきい値0.50~0.95における適合率を平均します。共通の仕組みについてはパフォーマンスメトリクスガイドで説明しています。
シグマは実際に測定すると理解しやすくなります。このスクリプトは、ポーズバリデーターが呼び出すkpt_iou関数を使い、シグマを2通りに設定して同じフォークリフト予測を評価します:
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を使用 | まずはここから始めて、測定する |
シグマには、各点をアノテーターがどれだけ一貫して配置できるかを反映させてください。実際に測定しましょう。2人に同じ50枚の画像にラベルを付けてもらい、キーポイントごとの距離を比較します。意見が分かれる点には大きなシグマを設定します。そうしないと、メトリクスが人のばらつきをモデルの誤差として評価してしまいます。
上の表が示すように、kpt_oks_sigmas を変更するとモデル自体を変えなくてもpose mAPが変わります。最初のベースラインを取る前にsigmaを固定し、すべての結果とともに記録してください。異なる値で検証した実行結果は比較しないでください。
解答を表示
上から見たコンベヤー上の荷物を例にします。確認したいのは「ラベル面が上を向いているか、またどちら向きに回転しているか」です:
| インデックス | キーポイント | 目的 | 反転時の対応点 | シグマ |
|---|---|---|---|---|
| 0 | label_top_left | ラベルの向き | 1 | 0.04 |
| 1 | label_top_right | ラベルの向き | 0 | 0.04 |
| 2 | label_bottom_right | ラベルの向き | 3 | 0.04 |
| 3 | label_bottom_left | ラベルの向き | 2 | 0.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を下回るオフセットが、アノテーターの精度でモデルに許容される最大誤差です。