ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

YOLOv8-pose姿态估计实战:一份带数据集的关键点检测项目全解析

YOLOv8-pose姿态估计实战:一份带数据集的关键点检测项目全解析

简介:本资源是一套基于YOLOv8-pose的人体姿态关键点检测完整项目,面向计算机视觉开发者、研究人员及入门学习者,可用于运动分析、人机交互、视频监控等场景。项目将模型结构、训练与推理流程、标注数据集整合在一起,无需从零搭建即可直接运行和二次开发。压缩包共1369个文件,约89MB,主要包含Python源码与编译缓存、PyTorch模型权重、YOLO配置文件、数据标注与标签文件、训练日志及说明文档等,目录结构清晰,适合对照学习训练流程、损失函数设计与数据增强策略。目前已有6200余人学习使用。资源附带已标注的人体图像数据集与预训练权重,并提供了完整可复现的训练、验证和推理脚本,方便读者快速评估模型精度、微调参数并迁移至自有数据场景,是系统掌握姿态关键点检测的实用参考。

1. 姿态识别项目落地:一份能直接跑通的 YOLOv8-pose 关键点检测源码

如果你手上正好有一批带标注的人体姿态数据,或者只想先跑通一套关键点检测流程再谈二次开发,这个基于 YOLOv8-pose 的姿态关键点检测项目是最省事的起点。它不是那种只有模型没有数据的半成品,而是把训练、验证、推理、可视化整条链路都串好了,解压之后按顺序执行就能在本地看到骨骼点叠加在原图上的效果。

这套方案适合两类人:一类是做人体动作分析、健身计数、安防行为识别的开发者,需要先拿到一个可复现的基线模型;另一类是刚接触姿态估计的学生或转岗工程师,想通过完整代码理解 YOLOv8-pose 从数据标注到坐标输出的全过程。标题里强调“带数据集、可直接跑通”,意味着你不需要满网找权重文件和标注文件,项目内已经准备好了训练所需的最小数据集和配置。下面我从数据处理、模型结构、运行避坑、二次开发四个层面把这份源码拆开讲。

2. 技术选型与数据准备:COCO17 点标注怎么变成训练集

2.1 为什么选 YOLOv8-pose 而不是 OpenPose 或 MediaPipe

姿态估计的常见路线有三条:传统自顶向下(先检测人再回归关键点,典型代表是 OpenPose)、轻量级方案(MediaPipe)、以及单阶段检测器直接输出人体框和关键点(YOLOv8-pose)。YOLOv8-pose 属于第三种,它在特征提取阶段就把目标检测和姿态回归合并到同一个网络里,推理时一次前向同时给出人体边界框和 17 个关键点的坐标与置信度。

相比 OpenPose 的复杂后处理流程,YOLOv8-pose 的工程友好度高很多:模型文件就是一个标准 YAML,训练命令和普通目标检测完全一致,权重导出 ONNX 后还能用自带 NMS 处理多人的情况。MediaPipe 虽然轻,但它返回的是归一化坐标且不可训练,想落业务时很难针对特定场景(比如侧卧、遮挡)微调。这个项目选 YOLOv8-pose 的另一个好处是——ultralytics 库的接口足够简单,训练和预测各一个 Python 文件就能完成,适合做基线验证。

2.2 数据集目录结构与 keypoints 标签格式

拿到项目压缩包后先看数据目录,它的组织方式直接决定了后续改数据时踩不踩坑。常见做法是把数据集拆成 images 和 labels 两个并列目录,再按 train/val 划分,而不是把所有图片堆在一起然后靠脚本临时切分。这个项目的目录结构大致是这样:

datasets/ ├── images/ │ ├── train/ │ └── val/ └── labels/ ├── train/ └── val/

标注文件是 YOLO-pose 格式的 txt,每一行对应一张图里的一个人。第一个数字是类别 id,后面跟着归一化坐标。关键点部分从第 6 个数字开始,每三个数字一组表示一个点的 x、y 和可见性标记。可见性标记是 0、1、2 三档,含义分别是未标注、遮挡但可推断、可见。

以 COCO 17 点格式为例,一整行的结构是:

class_id x_center y_center width height x1 y1 vis1 x2 y2 vis2 ... x17 y17 vis17

前 5 个数是人体框的归一化中心坐标和宽高,后面 51 个数字是 17 个关键点的数据。之所以带可见性标记,是因为训练时 loss 计算要对不同状态的 keypoint 区别对待——完全不可见的点如果强行参与回归,会把模型带偏。

这个项目自带的数据集是某实验环境拍摄的动作样本,虽然规模不大,但胜在标注干净、场景单一,拿来验证训练链路非常合适。如果你要换自己的数据集,只需要把图片和标注按同样目录放好,再把数据配置文件里的路径改掉就行,不需要动任何训练代码。

2.3 数据校验:跑通前必须确认的几件事

解压之后别急着训练,先做三件事,能省掉后面大量排查时间。

第一,检查标签里的坐标是否归一化在 0~1 之间。有些标注工具导出的是像素坐标,直接喂给 YOLO 训练会导致 loss 极大且不收敛。用下面的脚本扫一遍最快:

import os label_dir = "datasets/labels/train" for name in os.listdir(label_dir): with open(os.path.join(label_dir, name)) as f: for line in f: parts = line.strip().split() nums = [float(v) for v in parts[2:]] if max(nums) > 1.0: print(f"{name} 存在非归一化坐标: {max(nums)}")

这段脚本把标签里所有数值取出来,如果发现大于 1 的值就打印文件名。逻辑很简单,但非常管用——标注尺寸填错、单位搞混是最常见的低级错误,而且报错信息往往要到训练中期才出现,那时候已经浪费了几小时。

第二,确认图片和标签的文件名一一对应。YOLO 系列训练要求同名不同后缀,比如frame_001.jpg对应frame_001.txt。如果命名对不上,ultralytics 会把这张图当成无标注样本跳过,训练集实际参与训练的图片数量和你以为的不一样。

第三,看每张图的人数。YOLO-pose 标注一行一个人,一张图里有多个人就有多行。如果你的数据集有大量多人场景,但标签文件大多只有一行,说明标注阶段漏了人,需要回到标注工具里补全,否则模型会学出“只检测一个人”的偏见。检查方法很简单,统计一下包含多行标签的文件占比即可。

3. 核心模块拆解:一个关键点在 YOLOv8-pose 里的完整流转

3.1 模型配置:从 pose yaml 看懂输出通道

YOLOv8-pose 的网络结构定义在ultralytics/cfg/models/v8-pose.yaml里,核心改动集中在检测头部分。相比普通的 YOLOv8 检测模型,pose 版本在输出层增加了 keypoint 分支。通道数的计算逻辑是:4 + num_cls + keypoint_channels * num_kpts * 2。这里容易困惑的是为什么乘 2——因为定位分支里每个关键点的 x、y 被分开存,flatten 之后输出通道翻倍,而可见性分支单独走 sigmoid。

以默认的 COCO 17 点配置为例,类别数一般是 1(person),那么输出通道就是4 + 1 + 1 * 17 * 2 = 39。如果你换了自定义数据集只保留 12 个关键点,那输出通道要相应改成4 + 1 + 1 * 12 * 2 = 29。改错这个数字,模型训练时 loss 直接 NaN,而且报错信息不会明确告诉你是通道不匹配,只会在某个 Conv 层抛出尺寸不一致的提示,新手很容易看懵。

3.2 训练入口:参数怎么传才不至于白跑

项目里训练脚本通常长这样:

from ultralytics import YOLO model = YOLO("yolov8n-pose.yaml") results = model.train( data="pose.yaml", epochs=100, imgsz=640, batch=8, device=0, project="runs/pose", name="exp1" )

model.train()会先按传入的 YAML 构建模型结构,然后读取数据配置。data="pose.yaml"指向一个描述数据集路径和类别信息的文件,epochs和batch按显存调节,device=0指定使用 GPU,没有 GPU 就改成device="cpu",但速度会有数量级差距,这个项目的数据集规模在 CPU 上也能勉强跑通,只是不建议拿它做完整训练。

3.3 推理与可视化:画出骨架不是调一个函数这么简单

训练完成后,推理入口代码非常短:

from ultralytics import YOLO model = YOLO("runs/pose/exp1/weights/best.pt") results = model.predict( source="datasets/images/val", save=True, save_txt=True )

save=True会把渲染了骨骼点的图片保存到runs/pose/predict/目录,save_txt=True会把每个关键点的归一化坐标写到 txt 文件里。但你如果想把坐标拿回自己的业务逻辑里用,一般都会选择手动解析 results 对象:

for r in results: boxes = r.boxes.xyxy keypoints = r.keypoints.xy conf = r.keypoints.conf

r.keypoints.xy的形状是(num_detections, 17, 2),r.keypoints.conf是(num_detections, 17)。每个检测框对应一组关键点数据,拿到之后可以自行过滤置信度低的点,再按业务需求映射到原图坐标。

可视化部分项目里用的是 ultralytics 自带的plot()接口,它内部通过 OpenCV 画线段把骨骼关节连接起来。这里有个默认行为要注意:17 点骨骼的连线顺序写死在库的骨骼连接定义里,如果你自定义了关键点顺序,画出来的骨架会串线,需要在绘制前自己定义连接索引。

3.4 训练超参与日志解读

训练过程中的 loss 变化是判断模型是否学歪的重要依据。YOLOv8-pose 的训练日志里会同时出现多个 loss 分量:box_loss代表人体框回归误差,cls_loss代表分类误差,dfl_loss是分布焦点损失,pose_loss是关键点坐标回归误差。如果pose_loss下降缓慢但box_loss已经收敛,问题大概率出在关键点标注噪声上,而不是网络结构,别急着调学习率。

另外一个容易忽略的参数是kpt_shape,它在模型 YAML 里定义关键点数量和维度。默认 COCO 配置是[17, 3],第二个维度 3 代表(x, y, visible)。如果你只用二维标注,应该改成[17, 2],同时要保证标注文件里每行关键点部分只写 34 个数字,多写的可见性值会被解析器忽略,训练时不报错但数据已经读错了。

4. 常规运行步骤:把源码从压缩包变成可见的骨架图

4.1 环境安装:一套干净的依赖组合

不同机器的 Python 环境和 CUDA 版本差异很大,我一般建议先建一个干净的虚拟环境再装依赖。ultralytics 框架的版本敏感性比较高,装得太新可能踩 API 变更的坑,装得太旧又缺少部分功能。项目级的做法是直接用 requirements.txt 锁定版本:

conda create -n yolo-pose python=3.10 -y conda activate yolo-pose pip install -r requirements.txt

requirements.txt 里通常至少包含这几个核心依赖:ultralytics、torch、torchvision、opencv-python、numpy、pyyaml。其中 PyTorch 建议根据你的 CUDA 版本单独安装,不要直接用 pip 默认源,否则装出来的是 CPU 版本,训练速度会慢到怀疑人生。

装完后运行一条测试命令确认环境没问题,再开始跑项目:

from ultralytics import YOLO model = YOLO("yolov8n-pose.pt") print(model.names)

如果这条命令能打印出模型信息,说明依赖链完整。输出的类别名是 person,没有异常报错就继续。

4.2 训练与验证:用自己的数据复现一次完整流程

一切就绪后在项目根目录执行训练命令。为了快速验证链路通畅,第一次跑不需要完整训练,设置epochs=10就能在第 3 个 epoch 之后看到 loss 有意义的下降。

python train.py

项目里的 train.py 就是一个精简入口,内部读取pose.yaml数据集配置,然后调用上面提到的model.train()。训练过程中注意 watch 两个东西:一个是runs/pose/exp1/目录下的results.csv,另一个是每轮训练结束保存的val_batch_pred.jpg预览图。如果预览图里的骨骼点画在人的躯干外面,多半是标注文件坐标换算出了问题。

训练完成后,项目脚本也会自动做一次验证集评估,输出 mAP50 和 mAP50-95 两个关键指标。姿态任务的 mAP 计算方式和检测不一样——它允许预测关键点与 GT 在 OKS 阈值下匹配,如果 mAP 值低得离谱,优先检查标注而不是网络。

4.3 导出与部署:从 PyTorch 权重到 ONNX 推理

验证结果符合预期后,下一步是导出推理格式。Python 环境里直接跑 PyTorch 权重没问题,但真实项目要接入服务端推理时,更通用的做法是导出 ONNX:

yolo export model=runs/pose/exp1/weights/best.pt format=onnx imgsz=640

导出成功后可以用onnxruntime做一次推理验证,确认导出前后坐标输出一致。这一步有个常见的坑:ONNX 的 NMS 输出顺序和 PyTorch 里可能不同,尤其是置信度阈值和 IoU 阈值的默认值不一样。拿同一张图分别跑 PyTorch 和 ONNX,对比检测框数量,如果差异大于一两个框,需要手动对齐阈值参数,否则后续接业务时会出现前后端结果不一致的怪问题。

5. 常见问题与避坑记录:姿态项目最容易翻车的几个现实场景

5.1 训练 loss 早停不下降,先查学习率和标注噪声

现象:pose_loss在前 5 个 epoch 降到 2 之后一直停滞在 1.8 附近,不再明显下降。

原因:多数情况下不是模型瓶颈,而是关键点标注一致性差。尤其是遮挡状态下的关键点,不同标注员之间的位置偏差本来就大,模型无法在歧义样本上继续逼近。其次是学习率设置得不合适,默认 0.01 在小数据集上容易反复震荡。

解决:先把lr0降到 0.001 试试,同时把weight_decay调到 0.0005。如果依然停滞,回到数据检查遮挡样本——把 17 个点的可见性标记统计一下,如果vis=0的点占比过高,说明标注阶段存在大量漏标,这部分样本造成的 loss 噪声远大于正常样本的梯度贡献。

5.2 导出 ONNX 后坐标偏移了半个图,问题出在预处理

现象:同一张测试图在 PyTorch 里画出来的框是准的,ONNX 导出后用onnxruntime推理画框,发现框偏了大约 30 像素。

原因:ultralytics 推理时默认会做 letterbox 缩放并记录原始图尺寸与缩放图尺寸的比例,坐标还原回原图时要乘这个比例。直接用 ONNX 时如果跳过了框架自带的预处理,或者拿了缩放后的输出坐标直接画,自然就偏了。

解决:导出 ONNX 后,推理脚本里必须保留 letterbox 参数,用(orig_w / new_w, orig_h / new_h)做坐标映射,不要只写一个简单的仿射变换完事。把还原后的坐标再画到原图上做二次校验,确认和 PyTorch 的结果误差在 1 像素以内再接服务。

5.3 多人场景下关键点经常串人,需要调整 NMS 参数

现象:两个人靠近时,A 的左手腕点被分到了 B 的人体框上,骨架看起来像是两个人手臂连在一起。

原因:YOLOv8-pose 的关键点回归是基于人体框的,两个检测框 IoU 高的时候,NMS 会把框保留下来但关键点分支的输出并没有做跨人约束。这是单阶段姿态方案的通病,不是 bug。

解决:调高conf_thres到 0.25 以上,减少低置信度检测框对 NMS 的干扰;另一个办法是后处理中按关键点可见性做一次跨框插值——当两个框的同类关键点距离小于某阈值时,保留置信度高的那个。这个项目里预留了后处理脚本的接口,改post_process()一个函数就行。

5.4 改了数据集之后训练报“shape mismatch”

现象:把自带数据集换成自己的 14 点标注后,训练在第 1 个 epoch 报错,提示某 Conv 层输出尺寸对不上。

原因:模型 YAML 里的kpt_shape没有同步改,网络输出通道还是按 17 点计算的,和数据的 14 点标签数量不一致。

解决:打开yolov8n-pose.yaml,把kpt_shape改成[14, 3],同时确认pose.yaml里的关键点连接顺序和数量匹配。这两个 YAML 一处没改都会在训练初期报错,属于改自定义数据集最容易踩的坑了。

5.5 验证集 mAP 很高但可视化结果很差,检查置信度处理

现象:训练日志里 mAP50 到 0.9,但预测画图时很多关键点消失了。

原因:mAP 统计时用了较低的置信度阈值,而可视化代码里默认过滤掉了置信度小于 0.5 的点。两者的阈值不一致,导致官方指标好看但实际视觉输出缺胳膊少腿。

解决:在可视化脚本里把keypoint_conf_thres调到 0.25 再画图,对齐评估和可视化的口径。从那以后我每次调完阈值都会强制跑一遍评估脚本和可视化脚本,确认两条链路的置信度逻辑一致才往下走,这个习惯帮我少踩了很多次坑,希望帮到你。

本文还有配套的精品资源,点击获取

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进