标注并训练姿态模型
编写带有可见性信息的关键点标签,正确设置 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 | 类别索引(person) |
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)映射到自身。每种增强都会让骨架随像素移动,而翻转是唯一还需要索引映射的增强:

如果 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 机制,通过对象关键点相似度(OKS)对关键点位置进行评分。对于姿势产品,Pose mAP50-95 是发布门槛——下一课会介绍 OKS 如何判断关键点是否正确。
从关键点到关节角度#
result.keypoints 包含 xy(N × K × 2,像素坐标)、xyn(归一化坐标)和 conf(N × K),顺序与上方所示的 COCO 顺序一致。关节角度是两条在该处相交的肢体向量之间的夹角——可用点积计算:
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 会导致模型在镜像标签上静默训练,并在验证时表现为左右互换。