ポーズモデルにラベルを付けて学習する
可視性を含むキーポイントラベルを作成し、kpt_shapeとflip_idxを正しく設定し、COCO8-poseで学習して、信頼度で判定した関節角度をキーポイントから算出します。
ポーズ推定では、人物を番号付きの17個の点に変換します。点からは、製品で提供される数値、たとえば人間工学向けの肘の角度、リハビリ向けの膝の角度、転倒検出向けの腰の高さを算出できます。2つのデータセットキーによって、これらの点を信頼できるかどうかが決まります。どちらか一方でも間違えるとエラーは発生せず、左右を取り違えるモデルになったり、最も低コストのデータ拡張を使わずに学習したりします。
実践的
キーポイントのラベル行#

ポーズのラベル行は、キーポイントを追加した検出ラベル行です。すべての座標は0~1に正規化され、キーポイントはすべての行で同じ固定順序で並びます。実際のCOCO8-poseの行を、最初の4つのキーポイントまで抜粋します。
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 | クラスインデックス(person) |
0.671279 0.617945 0.645759 0.726859 | バウンディングボックスの中心x、中心y、幅、高さ |
0.519751 0.381250 2 | キーポイント0(鼻):x、y、可視性 |
| … | 残り16個のキーポイントも同じ形式:1行あたり5 + 17 × 3 = 56個の値 |
可視性フラグはCOCOの規則に従います。2は可視、1はラベル付け済みだが遮蔽、0はラベルなしです。Ultralyticsは損失で v = 0 のキーポイントをマスクするため、手首が欠損していても、(0, 0)に手首を予測するようモデルが学習することはありません。

テーブルの後ろに隠れた腰や、背中の後ろに隠れた手首など、確信を持って位置を特定できる遮蔽された関節には1を使ってください。これらもトレーニングに使われます。誰にも位置を特定できない関節に限り0を使ってください。隠れた関節すべてに0を付けると、モデルが必要とする難しいケースをまさに捨てることになります。
ポーズデータセットガイドでは、可視性のないデータセット向けの2値形式(x yのみ、kpt_shape: [K, 2])についても説明しています。
kpt_shapeとflip_idx#
データセットのYAMLには、通常の path、train、val、names に加えて、2つのキーを追加します。
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)は自分自身に対応します。すべてのデータ拡張はピクセルに合わせて骨格を移動させますが、左右反転だけはインデックスマップも必要です。

インストール済みのソースで確認した、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セグメンテーションと同様に、テーブルは2つのブロックに分かれています。Boxは人物ボックスを評価し、Poseは同じmAPの仕組みを使って、Object Keypoint Similarity (OKS)でキーポイントの配置を評価します。Pose mAP50-95はポーズ製品のリリース判定基準です。次のレッスンでは、OKSがキーポイントの正しさをどのように判定するかを説明します。
キーポイントから関節角度へ#
result.keypointsには、COCOの順序で、xy(N × K × 2、ピクセル単位)、xyn(正規化済み)、conf(N × K)が格納されています。関節の角度は、そこで交わる2つの肢ベクトル間の角度です。内積を使って求めます:
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は、少なくとも1つの左腕の関節の信頼度が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でも対応関係が誤っていると、ミラー反転したラベルで警告なく学習し、検証時に左右の入れ替わりとして現れます。