ARTICLE DETAIL

资讯详情

深耕商务建站与企业官网运营的一线实战洞察。

YOLOv8-pose人体17关键点检测与视频流部署实战

YOLOv8-pose人体17关键点检测与视频流部署实战 简介这是一份面向计算机视觉学习者与姿态识别开发者的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 明显不包含某个关键点转换时先外扩再归一化。这个动作很小但对早期收敛影响很直接。注意v0的点不要填一个看似合理的坐标。未标注就是未标注坐标置 0 更安全否则翻转增强和损失计算会把一个假坐标当成真值。2.2 kpt_shape 和 flip_idx 为什么必须与关键点定义绑定data.yaml里的kpt_shape: [17, 3]告诉模型两件事每个人有 17 个关键点每个点有 3 个值。如果你自定义了 5 个点做手势或者 21 个点做手部这里必须同步改。只改标签不改kpt_shape训练会报关键点长度不匹配只改kpt_shape不改标签模型会把多出来的列当成下一行数据或者直接越界。flip_idx是水平翻转增强的索引映射。开启fliplr0.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_vis0): root Path(json_path).parent out_img Path(out_dir) / images out_lbl Path(out_dir) / labels out_img.mkdir(parentsTrue, exist_okTrue) out_lbl.mkdir(parentsTrue, exist_okTrue) data json.loads(Path(json_path).read_text(encodingutf-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], dtypenp.float32).reshape(k, 3) parts [] for px, py, v in kps: v int(v) if v 0: # 未标注点不要伪造坐标置 0 并保留 v0 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, encodingutf-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, k17): 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也要覆盖。输出里如果v0占比特别高说明大量关键点未标注训练时模型能学到的监督信号有限先补标或换数据比调模型更有效。异常数不为 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.yamldata.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 \ modelyolov8n-pose.pt \ data/data/pose_dataset/data.yaml \ epochs120 \ imgsz640 \ batch16 \ device0 \ workers8 \ patience30 \ optimizerAdamW \ lr00.001 \ lrf0.01 \ warmup_epochs3 \ close_mosaic15 \ fliplr0.5 \ flipud0.0 \ degrees0.0 \ scale0.5 \ translate0.1 \ cacheram \ projectruns/pose \ nameexp01imgsz640是通用起点远距离小目标可以试 960但显存和耗时都会上去。batch16在 8GB 显存上可能偏大报 OOM 就减到 8 或 4不要同时改imgsz和batch否则排查变量太多。workers在 Windows 上建议设 0避免多进程 DataLoader 卡死Linux 下按 CPU 核心数设 8 左右。cacheram只适合数据集较小、内存充足的情况数据量大改成disk或False。close_mosaic15表示最后 15 个 epoch 关闭 mosaic让关键点定位在真实拼接分布上收一收通常比全程开 mosaic 更稳。fliplr0.5必须配合正确的flip_idx否则左右点会学混flipud0.0是因为人体上下翻转不符合真实分布除非你的场景确实有倒立需求。注意modelyolov8n-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 学得不错但关键点监督信号太弱检查v0比例、关键点是否大量落在框外、flip_idx是否错位。每个 epoch 结束后训练目录下会生成results.csv和曲线图。不要只看最后一行拉出最近 20 个 epoch 看趋势训练 loss 持续下降、验证 mAP 连续多个 epoch 不升甚至下降就是过拟合信号早停或加数据增强比继续拉 epochs 更合理。验证集 mAP 抖动很大时先确认 val 集样本量是否太少几百张图以内的验证集本身方差就大不要根据单个 epoch 的涨跌做决定。学习率方面AdamW 配lr00.001是常用起点SGD 可以试 0.01。如果前 5 个 epoch loss 直接炸成 NaN先降学习率再检查标签里是否有nan或极端越界坐标。warmup_epochs3是给预训练权重和新建关键点头一个缓冲自定义关键点数量时不要关掉 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( sourcedemo.jpg, imgsz640, conf0.25, iou0.7, device0, saveFalse ) 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)conf0.25是检测框置信度阈值iou0.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确认模型在无翻转增强下左右点能分开再打开fliplr0.5用同一张图做翻转可视化逐点核对索引。COCO 17 点的flip_idx可以直接用上一章那串但自定义关键点必须自己写。检查方法很直接取一张正面图画出 GT 骨架水平翻转图像和关键点看左肩是否落到右肩位置。如果翻转后骨架交叉说明映射错了。这个错误不会让训练报错只会让指标悄悄掉属于典型的“黑匣子”问题。4.3 损失下降但 mAP 不动现象pose_loss一路下降训练集可视化看起来不错验证集Pose mAP50长期停在 0.1 到 0.2。原因可能是验证集标签大量v0、bbox 与关键点不对应、train/val 同源导致指标虚高后回落或者学习率过大把关键点头推崩。解决抽 20 张验证图把 GT 骨架和预测骨架画在同一张图上对比统计v分布检查 val 集是否和 train 来自同一段视频把lr0减半再跑 30 个 epoch 看趋势。如果 GT 骨架本身就在框外优先修 bbox不要调模型。如果v0占比超过一半模型能学到的点太少补标或换数据比加 epochs 更有效。验证集 mAP 突然从很高掉到很低先怀疑数据泄漏或验证集里有损坏图片不要先怀疑模型。4.4 显存溢出与 DataLoader 卡死现象训练刚开始就CUDA out of memory或者卡在第一个 epoch 不动Windows 下还会抛多进程相关异常。原因通常是batch或imgsz太大、cacheram吃满内存、workers在 Windows 上开太多。解决按顺序减batch、降imgsz、关cache、把workers设 0。每次只改一个变量否则不知道是哪个参数触发的。yolo pose train modelyolov8n-pose.pt data/data/pose_dataset/data.yaml \ epochs120 imgsz512 batch8 device0 workers0 cacheFalse ampTrueampTrue默认开启能省显存但如果出现 loss NaN可以临时关掉排除混合精度问题。imgsz从 640 降到 512 会损失小目标关键点精度只作为排查手段不要长期用低分辨率硬扛。4.5 导出 ONNX 后关键点偏移现象PyTorch 推理关键点贴合导出 ONNX 后整体偏移几个像素或者多人场景框和点对不上。原因通常是预处理不一致训练和 PyTorch 推理用了 letterboxONNX 侧直接 resize 导致缩放比例和 padding 不同也可能是导出时没开simplify或者输出解析时把归一化坐标当成了像素坐标。解决统一 letterbox 参数导出时加simplifyTrue用同一张图分别跑 PyTorch 和 ONNX逐点对比。yolo export modelruns/pose/exp01/weights/best.pt formatonnx \ imgsz640 opset12 simplifyTrue导出后先用 Netron 看输入输出 shape确认输出字段顺序。ONNX 侧还原坐标时先减 padding再除以缩放比例顺序不能反。如果导出时加了nmsTrue输出格式会变解析逻辑要跟着改不要拿旧脚本硬套。5. 从单帧到视频流多人关键点归属、动作判定与 ONNX 部署单张图跑通只完成了一半。视频流里要解决多人关键点归属、跨帧抖动和部署预处理对齐三个问题。这一章按“跟踪 ID → 角度判定 → ONNX 预处理”推进代码尽量保持可抄参数按业务调。5.1 多人关键点归属与跟踪 IDYOLO pose 的输出天然把每个 bbox 和一组关键点绑定NMS 后boxes、keypoints、conf同序。多人场景不要只取keypoints再按坐标排序排序会打乱人的对应关系。需要跨帧稳定身份时用内置跟踪器把检测框和 ID 关联起来关键点跟着框走。from ultralytics import YOLO model YOLO(runs/pose/exp01/weights/best.pt) results model.track( sourcevideo.mp4, persistTrue, trackerbytetrack.yaml, conf0.3, iou0.6, imgsz640 ) 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 个关键点 passpersistTrue让跟踪器在视频帧之间保留状态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 hrefhttps://download.csdn.net/download/m0_51579041/88889941 stylecolor:#ec7500;font-size:14px; 本文还有配套的精品资源点击获取 /a img altmenu-r.4af5f7ec.gif srchttps://csdnimg.cn/release/wenkucmsfe/public/img/menu-r.4af5f7ec.gif stylewidth:16px;margin-left:4px;vertical-align:text-bottom;cursor:text; /p
返回列表
PREV
查看更多资讯
NEXT
返回资讯列表