☰
YOLOv8-pose人体17关键点检测与视频流部署实战
2026/10/25 12:08:28 网站建设 项目流程

简介:这是一份面向计算机视觉学习者与姿态识别开发者的YOLOv8-pose人体关键点检测实战项目,可直接跑通源码并配套数据集,适用于运动分析、人机交互、视频监控等场景的实验与二次开发。压缩包共1369个文件,约89.04MB,包含jpg图像与标注数据、py训练推理脚本、md说明文档、yaml配置文件,以及pt预训练权重和日志、缓存等训练产物,覆盖从数据准备到模型评估的主要环节。已有6223人学习,说明其可复现性和参考价值较高。项目围绕头部、肩肘腕、髋膝踝等关键点展开,提供完整目录与运行说明,读者可据此快速搭建训练与推理流程,理解多任务损失、数据增强和预训练迁移等实现细节,并在此基础上做精度调优或场景迁移。

1. 从一张多人合影到 17 个关键点:这套 YOLOV8-pose 项目适合谁

姿态识别真正落地时,最先卡住的通常不是网络结构,而是关键点定义、标签格式和一套能跑通的训练脚本。这套基于 YOLOV8-pose 的姿态关键点检测项目,带数据集和可直接跑通的源码,适合想快速验证人体 17 点、自定义关节布局或视频流动作分析的从业者。它不承诺解决所有遮挡和长尾场景,但能让你在一个下午内把训练、验证、推理、导出串起来,先拿到一个能看的 baseline,再决定往哪个方向补数据。如果你手头已有一批关键点标注,却总在kpt_shape、flip_idx和标签列数上翻车,这套东西的价值会更明显:它把数据契约和训练命令放在同一个可对照的工程里,省掉反复试错的时间。

2. 数据契约先立住:YOLO pose 标签、kpt_shape 与 flip_idx 三件套

YOLOv8-pose 的训练能不能一次跑通,七成取决于标签是否和data.yaml里的关键点定义对齐。模型结构反而是后面的事。这一章先把一行标签拆开,再把kpt_shape、flip_idx和转换脚本串起来,最后给一个训练前必跑的自检脚本。顺序不要反:先让数据能被正确解析,再谈调参。

2.1 一行标签里到底存了什么

YOLO pose 的标签文件是.txt,文件名与图片同名,一行对应一个人。单类人体 17 点的典型行如下:

0 0.512000 0.438000 0.276000 0.612000 0.501000 0.221000 2 0.523000 0.214000 2 ...

前 5 个字段依次是类别 id、bbox 中心 x、bbox 中心 y、bbox 宽、bbox 高,全部按图像宽高归一化到 0 到 1。后面每 3 个字段是一组关键点:x y v。x、y同样归一化,v是可见性标记,常见约定是0表示未标注,1表示标注了但不可见,2表示可见。17 点就是 51 个关键点字段,加上前 5 个,一行共 56 列。列数不对,训练框架在解析阶段就会报错,或者把关键点错位读进网络。

bbox 不要随手拿检测框凑。关键点回归依赖 bbox 提供的空间范围,框太紧会把手腕、脚踝裁到框外,框太松又会让模型学到多余背景。常见做法是让 bbox 包住所有可见关键点,并在四周留 5% 到 10% 的边距。如果原始标注里的 bbox 明显不包含某个关键点,转换时先外扩,再归一化。这个动作很小,但对早期收敛影响很直接。

注意:v=0的点不要填一个看似合理的坐标。未标注就是未标注,坐标置 0 更安全;否则翻转增强和损失计算会把一个假坐标当成真值。

2.2 kpt_shape 和 flip_idx 为什么必须与关键点定义绑定

data.yaml里的kpt_shape: [17, 3]告诉模型两件事:每个人有 17 个关键点,每个点有 3 个值。如果你自定义了 5 个点做手势,或者 21 个点做手部,这里必须同步改。只改标签不改kpt_shape,训练会报关键点长度不匹配;只改kpt_shape不改标签,模型会把多出来的列当成下一行数据或者直接越界。

flip_idx是水平翻转增强的索引映射。开启fliplr=0.5后,图像左右翻转,关键点坐标也会翻转,但左右手腕、左右肩膀的语义不能靠坐标自动交换,必须由flip_idx指定“第 i 个点翻转后对应第几个点”。17 点人体常用顺序是鼻、左眼、右眼、左耳、右耳、左肩、右肩、左肘、右肘、左腕、右腕、左髋、右髋、左膝、右膝、左踝、右踝,对应的flip_idx可以写成:

flip_idx: [0, 2, 1, 4, 3, 6, 5, 8, 7, 10, 9, 12, 11, 14, 13, 16, 15]

这套映射的意思是:鼻尖翻转后还是鼻尖,左眼和右眼互换,左耳和右耳互换,肩肘腕髋膝踝依次左右互换。自定义关键点布局时,不要照抄这串数字,拿一张正面图,手动标出左右点,翻转后再对一遍索引。这个检查花十分钟,能省掉后面几天的“玄学掉点”。

2.3 把 COCO 关键点标注转成 YOLO pose 的脚本

很多现成数据集是 COCO 关键点 JSON 格式,不能直接喂给 YOLO pose。下面这个脚本做三件事:读 JSON、把 bbox 转成归一化中心宽高、把关键点按x y v展开写.txt。脚本不复制图片,转换完把图片软链接或复制到images/train、images/val即可。

import json from pathlib import Path import numpy as np def coco_to_yolo_pose(json_path, out_dir, kpt_shape=(17, 3), min_vis=0): root = Path(json_path).parent out_img = Path(out_dir) / "images" out_lbl = Path(out_dir) / "labels" out_img.mkdir(parents=True, exist_ok=True) out_lbl.mkdir(parents=True, exist_ok=True) data = json.loads(Path(json_path).read_text(encoding="utf-8")) images = {im["id"]: im for im in data["images"]} k = kpt_shape[0] for ann in data["annotations"]: if ann.get("iscrowd", 0) == 1: continue if ann.get("num_keypoints", 0) < min_vis: continue im = images[ann["image_id"]] w, h = im["width"], im["height"] x, y, bw, bh = ann["bbox"] if bw <= 1 or bh <= 1: continue # bbox 转为中心点 + 宽高,并归一化 cx = (x + bw / 2) / w cy = (y + bh / 2) / h nw = bw / w nh = bh / h kps = np.asarray(ann["keypoints"], dtype=np.float32).reshape(k, 3) parts = [] for px, py, v in kps: v = int(v) if v <= 0: # 未标注点不要伪造坐标,置 0 并保留 v=0 parts.extend([0.0, 0.0, 0]) else: parts.extend([ float(np.clip(px / w, 0, 1)), float(np.clip(py / h, 0, 1)), v ]) cls_id = 0 # 单类人体;多类场景按 categories 建映射后替换 line = " ".join( [str(cls_id), f"{cx:.6f}", f"{cy:.6f}", f"{nw:.6f}", f"{nh:.6f}"] + [str(v) for v in parts] ) stem = Path(im["file_name"]).stem (out_lbl / f"{stem}.txt").write_text(line + "\n", encoding="utf-8")

kpt_shape必须和数据集关键点数量一致,17 点就是(17, 3)。min_vis用来过滤关键点太少的样本,比如设为 5 可以丢掉几乎全遮挡的标注;设 0 则保留全部。cls_id现在固定为 0,多类场景要先从categories建一个category_id -> 连续类别 id的映射,不能直接把原始类别 id 写进去,否则类别号可能跳号。np.clip是防止个别标注越界导致训练时坐标异常,但裁剪只能兜底,转换后仍要统计越界比例。

关键点顺序必须和flip_idx对应。COCO 的 17 点顺序与上一节列出的顺序一致时,flip_idx可以直接用;如果你在转换时重排了关键点,flip_idx也要跟着重排。最稳的验证方式是:拿一张图,用脚本画出 GT 骨架,再水平翻转图像和关键点,人工看左右手腕是否互换。这个步骤很土,但比盯着 loss 猜有效。

2.4 跑通前的数据自检:五个检查项

训练命令敲下去之前,先跑一遍标签自检。下面脚本检查五件事:标签是否有同名图片、每行列数是否等于5 + 3*k、bbox 是否越界、可见关键点坐标是否越界、可见性分布是否合理。

from pathlib import Path from collections import Counter import cv2 def check_labels(img_dir, lbl_dir, k=17): img_dir, lbl_dir = Path(img_dir), Path(lbl_dir) bad, vis = [], Counter() for p in sorted(lbl_dir.glob("*.txt")): img = img_dir / (p.stem + ".jpg") if not img.exists(): img = img_dir / (p.stem + ".png") if not img.exists(): bad.append((p.name, "缺图片")) continue if cv2.imread(str(img)) is None: bad.append((p.name, "图片不可读")) continue for ln, line in enumerate(p.read_text().splitlines(), 1): if not line.strip(): continue cols = line.split() if len(cols) != 5 + 3 * k: bad.append((p.name, f"第{ln}行列数{len(cols)}")) continue vals = list(map(float, cols[1:])) box = vals[:4] if not all(0 <= v <= 1 for v in box): bad.append((p.name, f"第{ln}行bbox越界")) kps = vals[4:] for i in range(k): x, y, v = kps[3 * i:3 * i + 3] vis[int(v)] += 1 if v > 0 and not (0 <= x <= 1 and 0 <= y <= 1): bad.append((p.name, f"第{ln}行点{i}越界")) print("异常数:", len(bad)) for b in bad[:20]: print(b) print("可见性分布:", vis)

k必须和data.yaml的kpt_shape[0]一致。图片扩展名按实际数据改,.jpeg也要覆盖。输出里如果v=0占比特别高,说明大量关键点未标注,训练时模型能学到的监督信号有限,先补标或换数据比调模型更有效。异常数不为 0 时不要急着开训练,先按文件名定位到具体图片和行号。

自检通过后,目录结构保持images/train、labels/train、images/val、labels/val同级。训练框架按文件名找标签,不按目录递归猜。把同一段视频的相邻帧同时分进 train 和 val 会造成指标虚高,划分时按视频或按人物分组,比随机按帧切更可信。

3. 把训练跑起来:yolov8n-pose 选型、命令行参数与收敛判断

数据契约对齐后,训练本身并不复杂,复杂的是参数之间的牵制。这一章先讲模型尺度怎么选,再给一份能直接跑的命令和data.yaml,然后说清楚日志里哪些指标值得盯,最后把推理和可视化接上。训练不是把 epochs 拉满就完事,能在验证集上稳定复现的配置才有意义。

3.1 选 yolov8n-pose 还是 s/m:显存、帧率与精度的三角

YOLOv8-pose 的 n/s/m 三个尺度,参数量大约在 3M、11M、26M 量级。n 适合边缘设备、单路视频流和快速验证;s 是精度和速度的折中,多数业务先从这个尺度起步;m 在遮挡严重、小目标多、关键点定位要求高的场景更有优势,但显存和推理耗时也更高。不要一上来就上 m,先把 n 跑通,确认标签和增强没问题,再换 s/m 对比。

模型尺度参数量级相对推理耗时适用场景主要代价
yolov8n-pose约 3M最低边缘端、预览、快速 baseline小目标关键点容易抖
yolov8s-pose约 11M中等通用多人姿态、视频流显存占用上升
yolov8m-pose约 26M较高遮挡多、远距离、精度优先训练和部署成本高

选型时不要只看 mAP。姿态关键点的业务指标往往和 mAP 不完全一致,比如手腕角度是否稳定、脚踝在遮挡后能不能找回。先拿 n 跑一个 baseline,记录验证集 mAP 和实际视频抽检结果,再换 s 看提升是否值得多出来的耗时。如果 n 和 s 的指标差距很小,说明数据质量或标签一致性是瓶颈,继续加模型参数收益有限。

3.2 一份能直接跑的训练命令与 data.yaml

data.yaml放在数据集根目录,路径写绝对路径,避免从不同工作目录启动时找不到文件。下面这份配置对应单类人体 17 点:

path: /data/pose_dataset train: images/train val: images/val test: images/test kpt_shape: [17, 3] flip_idx: [0, 2, 1, 4, 3, 6, 5, 8, 7, 10, 9, 12, 11, 14, 13, 16, 15] names: 0: person

训练命令如下。先用yolov8n-pose.pt加载预训练权重,让骨干和检测分支有一个不差的起点;关键点分支如果和 COCO 17 点一致,会直接复用,不一致时会重建,重建部分相当于随机初始化,学习率不要开得太大。

yolo pose train \ model=yolov8n-pose.pt \ data=/data/pose_dataset/data.yaml \ epochs=120 \ imgsz=640 \ batch=16 \ device=0 \ workers=8 \ patience=30 \ optimizer=AdamW \ lr0=0.001 \ lrf=0.01 \ warmup_epochs=3 \ close_mosaic=15 \ fliplr=0.5 \ flipud=0.0 \ degrees=0.0 \ scale=0.5 \ translate=0.1 \ cache=ram \ project=runs/pose \ name=exp01

imgsz=640是通用起点,远距离小目标可以试 960,但显存和耗时都会上去。batch=16在 8GB 显存上可能偏大,报 OOM 就减到 8 或 4,不要同时改imgsz和batch,否则排查变量太多。workers在 Windows 上建议设 0,避免多进程 DataLoader 卡死;Linux 下按 CPU 核心数设 8 左右。cache=ram只适合数据集较小、内存充足的情况,数据量大改成disk或False。close_mosaic=15表示最后 15 个 epoch 关闭 mosaic,让关键点定位在真实拼接分布上收一收,通常比全程开 mosaic 更稳。fliplr=0.5必须配合正确的flip_idx,否则左右点会学混;flipud=0.0是因为人体上下翻转不符合真实分布,除非你的场景确实有倒立需求。

注意:model=yolov8n-pose.pt适合 17 点人体。自定义 5 点或 21 点时,data.yaml的kpt_shape会触发关键点头重建,预训练关键点分支不能直接复用,训练初期 loss 波动更大,建议先把lr0降到 0.0005 观察几个 epoch。

3.3 训练日志怎么看:loss 曲线、pose mAP 与过拟合信号

训练启动后,终端会持续打印 box loss、pose loss、分类 loss 和验证指标。姿态任务重点看pose_loss是否稳定下降,以及验证集上的Pose mAP50、Pose mAP50-95是否同步改善。box loss 下降但 pose mAP 不动,常见原因是 bbox 学得不错,但关键点监督信号太弱:检查v=0比例、关键点是否大量落在框外、flip_idx是否错位。

每个 epoch 结束后,训练目录下会生成results.csv和曲线图。不要只看最后一行,拉出最近 20 个 epoch 看趋势:训练 loss 持续下降、验证 mAP 连续多个 epoch 不升甚至下降,就是过拟合信号,早停或加数据增强比继续拉 epochs 更合理。验证集 mAP 抖动很大时,先确认 val 集样本量是否太少,几百张图以内的验证集本身方差就大,不要根据单个 epoch 的涨跌做决定。

学习率方面,AdamW 配lr0=0.001是常用起点,SGD 可以试 0.01。如果前 5 个 epoch loss 直接炸成 NaN,先降学习率,再检查标签里是否有nan或极端越界坐标。warmup_epochs=3是给预训练权重和新建关键点头一个缓冲,自定义关键点数量时不要关掉 warmup。

3.4 推理与可视化:把 xy 坐标还给原图

训练完拿best.pt做推理,最容易被忽略的是坐标还原。YOLOv8 的results.keypoints.xy已经是原图坐标,不需要再乘宽高;如果你自己做了 letterbox 预处理,才需要反向减 padding、除缩放比例。下面这段代码把每个人可见关键点画到原图上,并保留每个人的 bbox 对应关系。

from ultralytics import YOLO import cv2 model = YOLO("runs/pose/exp01/weights/best.pt") results = model.predict( source="demo.jpg", imgsz=640, conf=0.25, iou=0.7, device=0, save=False ) for r in results: if r.keypoints is None: continue xy = r.keypoints.xy.cpu().numpy() # (N, 17, 2),原图坐标 kconf = r.keypoints.conf.cpu().numpy() # (N, 17) boxes = r.boxes.xyxy.cpu().numpy() # (N, 4) for i in range(xy.shape[0]): x1, y1, x2, y2 = boxes[i].astype(int) cv2.rectangle(r.orig_img, (x1, y1), (x2, y2), (0, 255, 0), 1) for j, (x, y) in enumerate(xy[i]): if kconf[i, j] > 0.5: cv2.circle(r.orig_img, (int(x), int(y)), 3, (0, 0, 255), -1) cv2.imwrite("vis.jpg", r.orig_img)

conf=0.25是检测框置信度阈值,iou=0.7控制 NMS 合并程度,多人拥挤场景可以把iou降到 0.5 到 0.6,减少框粘连。kconf是每个关键点的置信度,画图时过滤 0.5 以下,业务判定时阈值可以单独调,不要和检测框阈值混用。r.orig_img是 BGR 格式,直接交给 OpenCV 写文件。xy和boxes的顺序一一对应,NMS 之后不要只取keypoints再自己排序,否则人和骨架会对错。

4. 避坑与排查:关键点训练里最容易翻车的五类问题

姿态训练的报错往往不直观,表面看是 loss 不降,根因可能在标签列数、翻转映射或导出预处理。这一章按“现象 → 原因 → 解决”列五类高频问题,每条都给可操作的排查动作。遇到问题先按顺序排除,不要一上来就改模型结构。

4.1 标签点数与 kpt_shape 对不上

现象:训练启动即报关键点长度不匹配,或者某个 epoch 后突然出现索引越界;验证时Pose mAP直接为 0。原因通常是data.yaml写了[17, 3],但标签实际是 5 点、21 点,或者混入了一批只有 bbox 没有关键点的检测标签。解决:先统计每个标签文件的列数,换算关键点数量,再统一kpt_shape。

python - <<'PY' from pathlib import Path from collections import Counter c = Counter() for p in Path("labels/train").glob("*.txt"): for line in p.read_text().splitlines(): if line.strip(): c[(len(line.split()) - 5) // 3] += 1 print(c) PY

输出里如果出现多个关键点数量,说明数据集混了不同定义,必须按类别或按子集拆开,不能靠kpt_shape一个值硬吃。列数不能被 3 整除的标签也要单独拎出来,通常是手工编辑时多敲了一个字段。

4.2 左右关键点翻转后错位

现象:训练 loss 能降,但验证集上左右手腕、左右脚踝经常互换,动作判定时角度忽正忽反。原因多半是fliplr开了,flip_idx没配或配错。解决:先关掉水平翻转跑一个短 baseline,确认模型在无翻转增强下左右点能分开;再打开fliplr=0.5,用同一张图做翻转可视化,逐点核对索引。

COCO 17 点的flip_idx可以直接用上一章那串,但自定义关键点必须自己写。检查方法很直接:取一张正面图,画出 GT 骨架,水平翻转图像和关键点,看左肩是否落到右肩位置。如果翻转后骨架交叉,说明映射错了。这个错误不会让训练报错,只会让指标悄悄掉,属于典型的“黑匣子”问题。

4.3 损失下降但 mAP 不动

现象:pose_loss一路下降,训练集可视化看起来不错,验证集Pose mAP50长期停在 0.1 到 0.2。原因可能是验证集标签大量v=0、bbox 与关键点不对应、train/val 同源导致指标虚高后回落,或者学习率过大把关键点头推崩。解决:抽 20 张验证图,把 GT 骨架和预测骨架画在同一张图上对比;统计v分布;检查 val 集是否和 train 来自同一段视频;把lr0减半再跑 30 个 epoch 看趋势。

如果 GT 骨架本身就在框外,优先修 bbox,不要调模型。如果v=0占比超过一半,模型能学到的点太少,补标或换数据比加 epochs 更有效。验证集 mAP 突然从很高掉到很低,先怀疑数据泄漏或验证集里有损坏图片,不要先怀疑模型。

4.4 显存溢出与 DataLoader 卡死

现象:训练刚开始就CUDA out of memory,或者卡在第一个 epoch 不动,Windows 下还会抛多进程相关异常。原因通常是batch或imgsz太大、cache=ram吃满内存、workers在 Windows 上开太多。解决:按顺序减batch、降imgsz、关cache、把workers设 0。每次只改一个变量,否则不知道是哪个参数触发的。

yolo pose train model=yolov8n-pose.pt data=/data/pose_dataset/data.yaml \ epochs=120 imgsz=512 batch=8 device=0 workers=0 cache=False amp=True

amp=True默认开启,能省显存,但如果出现 loss NaN,可以临时关掉排除混合精度问题。imgsz从 640 降到 512 会损失小目标关键点精度,只作为排查手段,不要长期用低分辨率硬扛。

4.5 导出 ONNX 后关键点偏移

现象:PyTorch 推理关键点贴合,导出 ONNX 后整体偏移几个像素,或者多人场景框和点对不上。原因通常是预处理不一致:训练和 PyTorch 推理用了 letterbox,ONNX 侧直接 resize 导致缩放比例和 padding 不同;也可能是导出时没开simplify,或者输出解析时把归一化坐标当成了像素坐标。解决:统一 letterbox 参数,导出时加simplify=True,用同一张图分别跑 PyTorch 和 ONNX,逐点对比。

yolo export model=runs/pose/exp01/weights/best.pt format=onnx \ imgsz=640 opset=12 simplify=True

导出后先用 Netron 看输入输出 shape,确认输出字段顺序。ONNX 侧还原坐标时,先减 padding,再除以缩放比例,顺序不能反。如果导出时加了nms=True,输出格式会变,解析逻辑要跟着改,不要拿旧脚本硬套。

5. 从单帧到视频流:多人关键点归属、动作判定与 ONNX 部署

单张图跑通只完成了一半。视频流里要解决多人关键点归属、跨帧抖动和部署预处理对齐三个问题。这一章按“跟踪 ID → 角度判定 → ONNX 预处理”推进,代码尽量保持可抄,参数按业务调。

5.1 多人关键点归属与跟踪 ID

YOLO pose 的输出天然把每个 bbox 和一组关键点绑定,NMS 后boxes、keypoints、conf同序。多人场景不要只取keypoints再按坐标排序,排序会打乱人的对应关系。需要跨帧稳定身份时,用内置跟踪器把检测框和 ID 关联起来,关键点跟着框走。

from ultralytics import YOLO model = YOLO("runs/pose/exp01/weights/best.pt") results = model.track( source="video.mp4", persist=True, tracker="bytetrack.yaml", conf=0.3, iou=0.6, imgsz=640 ) for r in results: if r.boxes.id is None: continue ids = r.boxes.id.int().cpu().tolist() xy = r.keypoints.xy.cpu().numpy() kconf = r.keypoints.conf.cpu().numpy() for i, tid in enumerate(ids): # xy[i] 就是该跟踪 ID 当前帧的 17 个关键点 pass

persist=True让跟踪器在视频帧之间保留状态,tracker文件控制匹配逻辑。boxes.id为空时说明当前帧没有稳定轨迹,直接跳过,不要给None做索引。跨帧抖动可以用指数滑动平均压一下:对同一个tid,新关键点与上一帧做加权,alpha取 0.5 到 0.7,太低会拖影,太高等于没平滑。关键点置信度低于阈值的点用上一帧位置补,不要用 0 坐标补,否则角度计算会瞬间跳变。

5.2 动作判定:角度、滑动窗口与阈值

姿态关键点本身只是坐标,业务要的是动作。以手臂弯曲为例,用肩、肘、腕三点夹角判断,角度越小弯曲越明显。下面函数负责算夹角,输入三个二维点,输出 0 到 180 度。

import numpy as np def joint_angle(a, b, c): a, b, c = map(np.asarray, (a, b, c)) <p> <a href="https://download.csdn.net/download/m0_51579041/88889941" style="color:#ec7500;font-size:14px;"> 本文还有配套的精品资源,点击获取 </a> <img alt="menu-r.4af5f7ec.gif" src="https://csdnimg.cn/release/wenkucmsfe/public/img/menu-r.4af5f7ec.gif" style="width:16px;margin-left:4px;vertical-align:text-bottom;cursor:text;"> </p>

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询