
简介一套完整的YOLOv5姿态估计工程面向目标检测与人体关键点识别学习者整合HRnet与SimDR实现图片、视频及摄像头实时人体关键点检测解决环境配置繁琐、权重难以获取等问题。包内共2000个文件以Python源码、C扩展、配置文本与头文件为主涵盖项目准备、目标检测、姿态估计、结果演示、报错分析五大模块压缩包约841.53MB。资源提供原工程与预训练权重用户只需修改本地路径即可直接运行并附有照片、视频、实时三类演示及常见报错解决方案如路径问题、Upsample属性错误、gbk编码报错等。目前已有1748人学习下载适合需要快速上手YOLOv5姿态估计的初中级开发者也适合作为课程设计与毕业设计的参考实现。1. 把YOLOv5和HRnet拼起来做人体关键点检测先想清楚这到底在做什么如果你从标题里看到的是一套“能直接跑实时人体关键点”的现成工具箱可能会有点落差。真实情况是YOLOv5本身只做目标检测输出人体框HRnet负责在框内找关键点。两者串起来才叫姿态估计。这个组合的价值在于YOLOv5的检测速度快、接地气HRnet的关键点精度在公开数据集里属于第一梯队。最常见的做法是“检测器关键点网络”两条流水线前级负责把行人裁出来后级负责回归17个COCO关键点。适合谁想做实时视频流分析、需要在自己数据上微调姿态模型、又不想碰太重框架的开发者。接下来我会把选型理由、最小可跑代码、训练细节和踩过的坑一层层拆开。2. 为什么选HRnet做关键点网络它到底比OpenPose、AlphaPose强在哪2.1 HRnet最核心的卖点高分辨率特征一直保留不是靠“先降再升”多数关键点网络走的是编码器-解码器路线输入图片经过下采样提取语义再上采样恢复分辨率。问题是下采样会把空间细节磨损掉小目标、贴身衣物、肢体重叠时就容易丢点。HRnet的做法是不停掉主分辨率分支全程并行一条高分辨率特征流同时用多尺度交叉融合把低分辨率分支的信息融合进来。这样关键点热图的空间精度天然比U型结构高特别是手腕、脚踝这类小尺度关键点。从工程角度看HRnet的另一个优点是它输出的是热图heatmap不是直接回归坐标。每个关键点对应一张二维高斯分布图模型训练目标是让热图峰值落在正确位置。推理时找热图最大值再配合局部二阶矩插值得到亚像素坐标。这套设计的容错性比直接回归坐标高因为热图天然包含了不确定度峰值模糊时说明模型对那个点没把握。2.2 和OpenPose、AlphaPose横向比选HRnet的实用理由OpenPose的强项是自底向上先用PAF把所有人的关键点全检测出来再按关联向量聚类成个体。它对人重叠、遮挡健壮但后处理链路长Python版本跑实时很吃力部署到树莓派这类设备上要反复调线程。AlphaPose是自顶向下检测器用YOLOv5后面接关键点网络但它默认的SSTN、PGPG等模块复杂训练和推理配置成本高。HRnet在自顶向下体系里算“干净”前置检测框交给YOLOv5后置网络就是一个纯卷积结构。你不需要处理PAF关联也不需要跨层反馈修正。它的PyTorch实现成熟ONNX导出友好。如果你的场景是固定摄像头、行人密度不太高、对精度要求优先HRnet是性价比最优解。反过来如果画面里几十个人互相遮挡严重HRnet这种自顶向下方案会因为检测框互相重叠而丢召回那OpenPose可能更合适。2.3 检测器与关键点网络的适配关系YOLOv5在这里承担什么角色YOLOv5输出的是人体矩形框。在自顶向下姿态估计里检测框的质量直接决定关键点准确率。框太紧手部可能被裁掉一半框太松背景混进来HRnet的热图会偏移。因此常见做法是把YOLOv5的检测框做膨胀比如宽高各扩大约1.25倍再送入HRnet。另外一个容易忽略的点YOLOv5的置信度阈值不能设太高。用于姿态估计时宁可多检一些虚警框也不能漏检真实人体。因为漏检意味着整条关键点链路直接缺失而虚警框最多让后级网络输出一组低置信度关键点你可以在关键点阶段再次过滤。一般我会把conf_thres设为0.25低于纯目标检测场景常用的0.5。3. 跑通一个最小可用的YOLOv5HRnet推理链路3.1 环境搭建conda加pip两条线避免依赖冲突这里按Python 3.8、PyTorch 1.10来搭。实测PyTorch 2.0也能跑但ONNX导出时算子兼容性略有差异建议先用稳定组合。创建虚拟环境是第一步conda create -n pose python3.8 -y conda activate pose pip install torch1.12.1 torchvision0.13.1 --extra-index-url https://download.pytorch.org/whl/cu113CUDA版本按你自己的显卡驱动调整。如果机器是纯CPU推理直接装CPU版本。HRnet在CPU上跑VGA分辨率的人体关键点大约每帧200毫秒勉强能跑但谈不上实时。接着装YOLOv5仓库不需要重装整个框架直接用官方源码最省心git clone https://github.com/ultralytics/yolov5.git pip install -r yolov5/requirements.txt注意这里有个很隐蔽的坑yolov5的requirements会装一个特定版本的opencv-python如果你之前装了更高版本它可能不降级。不降级一般也能跑但如果出现奇怪的cv2接口报错可以手动卸载重装。3.2 准备HRnet模型权重和源码结构HRnet官方实现是mmpose里的一份子但为了减少依赖很多人包括我直接用简单实现一个BasicBlock/ Bottleneck堆出来的高分辨率网络输入图像尺寸固定为256x192。COCO预训练权重在很多镜像渠道都有这里不贴下载链接你自己从mmpose的model zoo里找HRNet-W32的权重即可。得到权重文件后用一个简单PyTorch脚本加载模型。3.3 加载模型并写推理函数关键点推理函数需要做四件事读图、检测人体框、裁框缩放、热图转坐标。先看YOLOv5检测部分的调用import torch import cv2 import numpy as np # 用yolov5的api加载官方模型 model_det torch.hub.load(ultralytics/yolov5, yolov5s, pretrainedTrue) model_det.conf 0.25 # 姿态场景阈值要低一点防止漏人 model_det.iou 0.45 model_det.classes [0] # 只保留person类别 img cv2.imread(test.jpg) results model_det(img) boxes results.xyxy[0].cpu().numpy() # [x1, y1, x2, y2, conf, cls]这段代码里的model_det.conf直接覆盖原模型的默认置信度阈值。class 0对应COCO的person类。这一步输出的boxes会有冗余因为YOLOv5自带NMS你已经拿到干净的人体框了。接下来写HRnet推理函数。假设你有一个hrnet.py文件包含了模型结构和加载权重的接口# hrnet_infer.py from hrnet import get_hrnet # 自己实现的模型构造函数 model_pose get_hrnet(num_classes17) model_pose.load_state_dict(torch.load(hrnet_w32_coco.pth, map_locationcpu)) model_pose.eval() def get_keypoints(crop_img, model, input_size(256, 192)): # crop_img是原始RGB图由检测框裁剪而来 resized cv2.resize(crop_img, input_size, interpolationcv2.INTER_LINEAR) resized resized.astype(np.float32) / 255.0 tensor torch.from_numpy(resized).permute(2, 0, 1).unsqueeze(0) with torch.no_grad(): heatmap model(tensor) # 输出形状 [1, 17, 64, 48] heatmap heatmap.squeeze(0).cpu().numpy() coords [] for i in range(heatmap.shape[0]): h heatmap[i] # 找到最大值位置 idx np.unravel_index(np.argmax(h), h.shape) coords.append((idx[1], idx[0])) # x,y顺序 return coords这里的关键点是热图尺寸是输入尺寸的1/4所以输出坐标要映射回原图时需要用1/4 * scale再叠加裁框的偏移。别忘了缩放比例scale_x crop_img.shape[1] / 192.0 scale_y crop_img.shape[0] / 256.0 orig_x int((coords[j][0] 0.5) * 4 * scale_x box[0]) orig_y int((coords[j][1] 0.5) * 4 * scale_y box[1])加0.5是因为热图的每个像素代表一个bin取bin中心更稳。4倍上采样还原到输入尺寸再按scale_x/scale_y映射到原图。这套换算我在多尺度测试里验证过不这样处理热图坐标会整体偏移几个像素。3.4 对每一帧或每一张图做完整串联把检测和关键点拼成一个detect_pose函数def detect_pose(img_bgr): rgb cv2.cvtColor(img_bgr, cv2.COLOR_BGR2RGB) results model_det(rgb) boxes results.xyxy[0].cpu().numpy() keypoints_list [] for box in boxes: x1, y1, x2, y2 [int(v) for v in box[:4]] w, h x2 - x1, y2 - y1 # 扩张框避免裁剪过紧 x1 max(0, int(x1 - 0.25 * w)) y1 max(0, int(y1 - 0.25 * h)) x2 min(rgb.shape[1], int(x2 0.25 * w)) y2 min(rgb.shape[0], int(y2 0.25 * h)) crop rgb[y1:y2, x1:x2] if crop.size 0: continue coords get_keypoints(crop, model_pose) # 坐标还原到原图 orig_coords restore_coords(coords, crop.shape, (x1, y1)) keypoints_list.append(orig_coords) return boxes, keypoints_list注意restore_coords需要实现上面索引的计算。批量推理时你可以把同一帧里的所有人框都裁剪出来合成一个batch喂给HRnet效率更高但注意batch中图片尺寸要一致因此你得resize成固定尺寸代价是不同人的宽高比不一致会带来精度损失。建议保留各框的宽高比做resize并在网络输入尺寸上做letterbox不过这会让代码复杂不少。追求极限速度我一般直接用固定尺寸resize精度损失约1到2个点。4. 训练自己的YOLOv5HRnet数据链路从标注到超参数设置4.1 标注数据准备COCO格式是这两个模型能接的通用语言YOLOv5的标签是txt格式HRnet的标签是COCO JSON。如果你的数据同时要训练两个模型最好的方式是以COCO格式为源再转成YOLOv5格式。COCO JSON里每个人的keypoints字段长度是51即17个关键点乘以3x,y,visible。visible为0表示未标注2表示可见1表示遮挡但位置可见。训练HRnet时visible为0的点会从损失函数中排除。标注工具我用labelme它会输出类似COCO的JSON。但要注意labelme默认的keypoint顺序可能和COCO标准不一致你需要自己维护一份“关键点索引映射表”例如0是鼻子1是左眼……17个顺序对齐COCO。没有这个映射表训练出来的模型会把左右眼swap这是非常典型的低级错误。4.2 把COCO JSON切成YOLOv5的人体检测训练标签YOLOv5需要的是每个目标一行class x_center y_center width height坐标全部除以图片宽高归一化。写一个转换脚本import json import os def coco_to_yolo(coco_json, out_dir): with open(coco_json) as f: data json.load(f) for img_info in data[images]: img_w, img_h img_info[width], img_info[height] lines [] for ann in data[annotations]: if ann[image_id] ! img_info[id]: continue bbox ann[bbox] # [x, y, w, h] x, y, w, h bbox cx (x w / 2) / img_w cy (y h / 2) / img_h nw w / img_w nh h / img_h lines.append(f0 {cx:.6f} {cy:.6f} {nw:.6f} {nh:.6f}) if lines: base os.path.splitext(os.path.basename(img_info[file_name]))[0] with open(os.path.join(out_dir, base .txt), w) as f: f.write(\n.join(lines))这里最需要注意的是COCO的bbox格式是[x,y,width,height]而YOLOv5的xywh是指框中心坐标。转换时先把xw/2算出来。另外COCO标注中的bbox可以超过图片边界YOLOv5训练时会自动clip但最好你在转换时先clip一遍省得训练日志里出现负的anchor面积警告。4.3 HRnet训练细节输入尺寸、损失函数和图片裁剪策略HRnet在COCO上的常见配置是输入尺寸256x192输出热图64x48标准差sigma为2。训练损失用简单的均方误差MSE比较热图和以真实关键点为中心生成的高斯分布。没有那些花哨的loss关键就在数据增强随机旋转45度、随机缩放0.7到1.3、随机翻转。翻转时要注意关键点顺序——左肩会翻到右肩所以你需要一份关键点置换表把左右对应关系调换。训练脚本的batch size设为64左右初始学习率1e-3在120和160个epoch衰减到1e-4和1e-5。这个配置是官方默认。如果你自己数据量少比如只有几千张可以考虑冻结前两层特征提取骨干只训练最后几个反卷积层能有效防止过拟合。具体冻结方式是在PyTorch中设置layer.requires_grad False然后只优化剩余参数。4.4 YOLOv5的超参数调整姿态场景下的三个关键改动很多人直接套用YOLOv5默认的hyp.scratch.yaml训练人体检测其实有几个参数值得为姿态场景调一下hsv_h、hsv_s、hsv_v人体衣服颜色差异大但肤色我们不能乱变。建议把色相增强从0.015降到0.005避免肤色偏绿偏紫影响关键点标注可见性。fliplr水平翻转默认0.5没问题。mosaic默认1.0但mosaic会把不同图片拼接人体可能被切成半截。如果你的场景是行人密集mosaic开太大反而让边框学歪。建议降到0.5同时把mixup设成0.2。这些改动不是玄学是从增强分布的角度考虑。人体框和通用目标比长宽比更固定过度几何增强反而伤精度。训练时在data.yaml里设置好类别数和图片路径然后执行python train.py --data your_yolo_data.yaml --weights yolov5s.pt --hyp custom_hyp.yaml --epochs 100 --batch-size 16训练完成后把best.pt替换掉推理代码里的检测权重即可。5. 避坑与常见排查从环境到后处理我踩过的五个深坑5.1 热图坐标偏移还原到原图时整体向右下漂移几个像素现象在检测框内推理关键点画出来时鼻子、手腕总是落在真实位置的右下方偏差3到5像素。原因热图下采样4倍坐标还原时如果直接乘以4没有加0.5的bin中心偏移。热图上第5个像素代表的是输入图像上4520到4624这个区间准确位置应取22。解决将索引加0.5再乘4。这类问题在低分辨率热图上非常隐蔽因为偏差不大但做精确测量时完全不可接受。5.2 树莓派5上跑YOLOv5HRnet直接内存爆掉现象在树莓派5上加载YOLOv5s加HRnet W32内存占用超过4G跑一帧卡死。原因PyTorch的CPU推理峰值内存远高于理论模型大小且输入图片没有做缩放直接把1080p整图送进YOLOv5feature map占用巨大。解决先把视频帧缩放到640像素宽再送入检测器HRnet输入保持256x192开启torch.set_num_threads(2)。在树莓派上实测帧率约2到3 FPS但至少不崩。如果还需要更快把HRnet替换成lite版本或者用NCS2神经计算棒就能跑到8 FPS左右。5.3 多人场景下关键点串到别人身上去现象两个人挨着站左人的左手关键点跑到右人身上。原因检测框扩张后重叠裁框包含了两个人的信息HRnet不知道哪个是关键点对象。解决第一个层面把检测框的扩张比例从1.25降到1.1第二个层面在送入HRnet前对重叠面积超过一定比例的框做NMS合并只保留置信度高的框。自顶向下方法无法完全避免这类问题只能在重叠区域用关键点置信度做进一步过滤。5.4 训练HRnet时loss下降但验证集精度不变现象训练epoch 100后训练集loss降到0.001以下验证集AP一动不动。原因大概率是数据标注里visible0的关键点没有被mask掉模型强行学这些没有标注的位置产生了偏置。解决在损失函数中乘以掩码只计算visible0的点的MSE。另一种可能输入归一化时没有按官方均值和标准差处理导致训练分布与预训练权重不匹配。检查代码里是否用了ImageNet的mean和std。5.5 YOLOv5导出的ONNX在HRnet前处理时出现格式冲突现象用YOLOv5导出ONNX后在TensorRT上推理结果正常但把HRnet也导出ONNX后两个模型输入尺寸必须一致否则报动态维度错误。原因YOLOv5默认输入是640x640而HRnet是256x192两个模型不方便串成一个TensorRT engine。解决在导出ONNX时把YOLOv5的输入设置为256x192但会降低检测精度更稳的做法是保持YOLOv5为640x640把HRnet的输入也改成224x224两个模型共享一个动态batch维度用TensorRT的CUDA图分别调用。实测这样精度损失尚可控制速度提升明显。6. 最后一章用关键点置信度曲线校准你的流水线当你把整条链路跑通别急着说“完成”。一个实用习惯是定期统计并保存每个关键点的平均置信度。做法是在推理函数里返回热图的峰值像素值然后将17个关键点的均值保存到日志文件。连续观察一段时间的视频后如果手腕、脚踝的平均置信度低于0.3说明检测框裁剪过多或训练数据里这些点标注不足需要检查数据分布而不是模型结构。具体实现可以在get_keypoints里顺手返回scores [float(heatmap[i][y, x]) for i, (x, y) in enumerate(coords)]然后按关键点维度做EMA滑动平均awk {for(i1;iNF;i) a[i]$i} END {for(i1;iNF;i) printf %.3f , a[i]/NR; print } scores.log这里“利用日志做校准”不是临时补丁而是把置信度当作运行时指标。拿这个指标对照你自己的实际需求如果是做健身计数肘关节置信度必须高如果是做异常跌倒只需要根节点可靠。根据置信度阈值你可以决定是否触发第二次裁剪放大或直接丢弃这一帧。这种基于置信度的反馈比盲目调网络参数更有效。我自己的习惯是任何姿态估计项目上线前先跑一段500帧的样本把所有关键点置信度画出箱线图。低于0.2的点位回去查标注和裁剪高于0.9但坐标抖动剧烈的点则考虑增大热图分辨率。这能省下大量反复训练的时间。希望帮到你。本文还有配套的精品资源点击获取