简介整套YOLOv5人体姿态估计工程文件基于HRnet与SimDR完成图片、视频及摄像头场景下的关键点实时检测面向深度学习姿态估计入门及进阶开发者帮助省去自行配置参数与下载权重的繁琐过程。压缩包内含2000个文件以1867个Python脚本为主体并包含C/C源文件与头文件、文本配置、Markdown文档及JSON等资源整体约841.53MB目录覆盖项目准备、目标检测、姿态估计、结果演示和报错分析五大模块。已有1757人学习下载内容从PyCharm克隆项目、环境配置、添加SPPF模块到修改yaml并绘制骨骼关键点均有完整说明同时提供照片、视频和实时摄像头的结果演示。针对路径问题、matplotlib.use(Agg)写入文件、Upsample的recompute_scale_factor属性及gbk编码异常等常见报错也整理了具体分析与解决思路可直接对照排查适合希望快速跑通完整流程并继续二次开发的读者。1. 从YOLOv5到HRnet姿态估计项目的正确打开方式把目标检测和人体关键点检测串成一条完整链路是YOLOv5姿态估计这类项目最实在的价值所在。只用YOLOv5模型只能告诉你「画面里有个人」但这个人手抬到哪、腿弯到什么角度它完全不知道。接上HRnet之后模型会在检测框内部继续回归出人体关键点坐标再按骨骼连接关系把它画成骨架图整条链路跑通之后动作分析、健身计数、跌倒检测这类功能才有了底层支撑。这个项目用YOLOv5负责「框住人」再用HRnet负责「找出骨架」配合SimDR完成关键点回归既可以处理静态图片也能直接跑视频和摄像头实时画面。我实际拆过这份工程文件之后可以明确说这份资源适合已经有PyTorch基础、想快速跑通姿态估计完整流程的人不需要从零训练模型把路径改对、权重放妥就能出结果真正的难点反而不在模型本身而在工程环境里的各种细节。2. 项目准备环境搭建、权重落位与一张图验证2.1 克隆工程与Python环境配置拿到这份工程文件之后第一步不是急着配置模型参数而是把Python环境和目录结构先理顺。对于PyTorch系的检测项目来说环境里最容易出问题的就是torch和torchvision版本不匹配其次是numpy版本过高导致某些旧代码报错。我一般会新建一个虚拟环境单独给这个项目用而不是直接装在base环境里这样翻车了可以整个删掉重建相当于给自己留了后悔药。git clone 项目仓库地址 # 建议用SSH方式克隆避免HTTPS拉取时的权限问题 cd 项目目录 python -m venv venv # 创建独立虚拟环境 source venv/bin/activate # Linux/Mac激活Windows下用 venv\Scripts\activate pip install torch torchvision --index-url https://download.pytorch.org/whl/cu118 pip install -r requirements.txt第一行克隆命令之后一定要确认仓库是否完整有时候网络中断会造成对象文件缺失后续导入模块时会莫名其妙报ModuleNotFoundError。创建独立虚拟环境这一步建议不要跳过。之前有位同事在生产环境里图省事直接往base环境装结果把另一个项目的tensorflow依赖搞坏了最后花了整整一天排查。安装PyTorch时之所以单独指定index-url是因为默认pip源安装的torch往往不带CUDA支持安装完之后虽然能导入但检测速度会慢一个数量级。这里CUDA版本号需要和你的显卡驱动对应如果显卡比较老改成cu118甚至cpu版本都可以推理慢一点但流程能跑通。2.2 权重文件放置规则与路径统一这份资源的一个核心卖点就是「不用自己重新训练权重」。YOLOv5的检测权重负责输出人体边界框HRnet的权重负责在框内回归关键点坐标两者是不可互换的。工程文件里权重文件通常比较大有些同学从网盘下载之后随意解压到一个临时目录然后程序一运行就报FileNotFoundError本质就是代码里的路径和你实际的权重路径没有统一。项目根目录/ │ ├── weights/ │ ├── yolov5m.pt # YOLOv5检测权重 │ ├── hrnet_w32.pth # HRnet关键点回归权重 │ └── simdr_w32.pth # SimDR辅助权重可选 │ ├── models/ │ ├── yolo.py # YOLOv5模型定义 │ └── hrnet.py # HRnet模型定义 │ └── data/ ├── coco.yaml # 数据集配置 └── 测试图片.jpg路径问题在跨平台时尤其明显。Windows的路径分隔符是反斜杠而代码里写死的正斜杠路径在Windows上依然能解析反过来就一定会报错。我自己习惯的做法是把所有涉及权重的路径集中在一个config.py里统一管理不让任何路径散落在业务代码中间这样后面换机器部署时只需要改一个文件。还有个容易忽视的细节权重文件和模型架构是绑定的。这份工程里如果对应的是yolov5m的模型定义但你在目录里放了一个yolov5s的权重文件程序不会第一时间报错而是会在第一次推理时输出维度不一致的崩溃信息排查起来比直接报错更烦。2.3 处理一张测试图片验证链路通断环境配置完成、权重落位之后不要急着上摄像头先用一张测试图验证整条链路是否通断。import torch import cv2 from models.hrnet import HRNet from models.yolo import YOLODetector # 加载模型device选择顺序CUDA优先其次CPU device torch.device(cuda if torch.cuda.is_available() else cpu) detector YOLODetector(weightsweights/yolov5m.pt, devicedevice) hrnet HRNet(weightsweights/hrnet_w32.pth, devicedevice) img cv2.imread(data/测试图片.jpg) boxes detector.detect(img, conf_thres0.5) # 置信度阈值设0.5低于此值的框丢弃 keypoints hrnet.predict(img, boxes) # HRnet在检测框内预测关键点 vis_img hrnet.draw_skeleton(img, keypoints) # 绘制骨骼和关键点 cv2.imwrite(output/result.jpg, vis_img)这段代码里detect函数的conf_thres参数用来控制检测框的置信度阈值调低了可能会检出更多假框调高了又容易漏检。在姿态估计场景里检测框的误检比漏检影响更大因为一旦框错了位置后面的关键点回归就全部建立在错误区域上所以阈值在0.5左右比较合适。draw_skeleton函数内部会判断每个关键点的置信度分数分数低的关键点不会绘制避免在画面上留下大量无意义的噪点。运行这一步时如果出现中文路径相关的报错大概率是Windows下的编码问题后面会在报错排查章节里专门讲。3. 目标检测段边界框获取与SPPF模块改造3.1 检测器的推理链路从图像输入到框坐标输出在姿态估计项目里YOLOv5承担的角色是「候选区生成器」。它不是整个项目的终点而是HRnet的前置依赖。推理流程可以拆成三个环节图像预处理、模型前向推理、后处理解析。预处理部分网络输入尺寸的选取直接影响检测精度与速度的平衡常见做法是将图像resize到640×640这个尺寸在mAP和推理速度之间折中得比较好。YOLOv5的原生输出是一个张量张量的维度是(batch, 25200, 85)其中25200表示三个特征图尺度上的候选框总数85的前5个值是框的xywh和objectness置信度后面80个值是COCO数据集的类别概率。如果你在数据加载阶段加上了多尺度训练或者直接用了别的输入尺寸这个25200会随之改变所以调试阶段打印输出张量的shape是第一个排查点。注意这里有一个关键的坐标转换步骤。YOLOv5输出的边界框坐标是基于输入尺寸的绝对像素值不是归一化坐标。如果直接把它传给HRnet而不做任何换算HRnet就会在错误的位置上找关键点。正确方式是先还原到原始图像尺寸或者统一到一个中间分辨率。3.2 边界框解析逻辑与置信度过滤从原始输出张量到一组干净的边界框坐标中间要经过解码、过滤、非极大值抑制三个步骤。def postprocess(prediction, conf_thres0.5, iou_thres0.45): # prediction: (1, 25200, 85) 原始输出 xc prediction[..., 4] conf_thres # 按objectness置信度初筛 output [] for i, pred in enumerate(prediction): pred pred[xc[i]] # 只保留置信度达标的框 if len(pred) 0: output.append(torch.zeros((0, 6))) continue box xywh2xyxy(pred[:, :4]) # 把中心点宽高格式转为左上右下角点格式 cls_conf, cls_pred pred[:, 5:].max(dim1, keepdimTrue) detections torch.cat((box, pred[:, 4:5], cls_conf, cls_pred.float()), dim1) # 按类别做非极大值抑制同一目标上重叠框只保留置信度最高的 keep nms(detections[:, :4], detections[:, 4], iou_thres) output.append(detections[keep]) return output这段代码是整个目标检测后处理的核心逻辑。xywh2xyxy这一步非常容易踩坑YOLOv5输出的框是(center_x, center_y, width, height)而绘制和裁剪操作需要的是(x1, y1, x2, y2)格式没有转换的话画出来的框位置会整体偏移。非极大值抑制的iou阈值在多人姿态估计场景下建议比纯目标检测稍微调严一点设到0.45左右因为两个紧紧挨着的人如果阈值过松其中一个的框会被抑制那个人就直接丢了没姿态可测。参数调优时有个经验值检测框的置信度阈值不要设得和纯检测任务一样高。姿态估计的下游任务对误报更敏感但漏报更致命因为漏掉一个人就意味着那一路的关键点数据完全为空。所以conf_thres用0.3到0.5之间是合理区间不建议超过0.6。3.3 SPPF模块在工程中的实际作用与改动这份工程文件里单独提到了SPPF模块的添加SPPF全称是Spatial Pyramid Pooling Fast是YOLOv5中用来增大感受野的模块。它的思路是对输入特征图依次做多次最大池化然后把不同尺度的池化结果拼接起来这样同一个网络就能同时捕捉到小范围和大范围的目标上下文信息。输入特征图(C, H, W) │ ├── MaxPool(k5, s1, p2) ──┐ │ │ │ │ MaxPool(k5, s1, p2)───┤ 拼接 → 输出特征图(4*C, H, W) │ │ │ │ MaxPool(k5, s1, p2)───┘ └─────────────────────────────┘在姿态估计这个场景中SPPF模块的意义在于人体大小变化极大。摄像头里离得远的人可能只有几十个像素高离得近的人则充满整个画面多尺度池化让检测器对这两种极端情况都有响应能力。如果直接用原版YOLOv5而不加SPPF小目标人体漏检率会明显上升。这里有个常见误区有些同学认为SPPF是必须修改模型结构才能加的。实际上如果你直接用的是较新版本的YOLOv5SPPF已经是内置模块不需要额外改动。这份工程文件里提到添加SPPF更多是为了兼容旧版本代码或者是在改造backbone时保留这个结构。改完之后一定要重新加载权重因为模型结构变了之后权重文件里对应的层名也会变化不匹配的话会在torch.load时直接抛StateDict错误。4. 姿态估计段HRnet关键点回归与骨骼绘制4.1 HRnet的模型结构与yaml配置改动HRnet的核心特点是它在整个前向过程中始终保持高分辨率特征图而不是像很多分类网络那样先下采样再上采样。高分辨率特征图的保留对关键点定位至关重要因为关键点回归本质上是一个逐像素预测问题关键点的坐标误差受限于特征图的空间分辨率。网络通过并联多个不同分辨率的子网络并在每个阶段做跨分辨率的信息融合让高低分辨率的特征互相补充。工程文件里提到的修改yaml文件主要是为了告诉HRnet模型几个关键配置输入图像尺寸、输出关键点数量、以及训练时采用的数据集格式。这里有一个需要特别清醒的点如果你直接用官方预训练权重yaml里的num_classes必须和权重训练时的配置保持一致也就是17个COCO关键点不能改。你改yaml的真实目的只是「告诉代码到哪里找权重、输入尺寸是多少」而不是调整模型结构。# models/hrnet_w32.yaml MODEL: PRETRAINED: weights/hrnet_w32.pth # 预训练权重路径必须存在 NUM_KEYPOINTS: 17 # COCO标准17个关键点 INPUT_SIZE: [192, 256] # 输入宽高按HRNet惯例保持宽192、高256 HEATMAP_SIZE: [48, 64] # 热图分辨率输入尺寸的1/4 SIGMA: 2 # 高斯核标准差关键点标注生成热图时用这段配置里HEATMAP_SIZE和SIGMA这两个参数在仅做推理预测时不参与计算但只要你想在现有权重上做任何微调这两个参数就需要和训练时保持一致否则关键点的ground truth热图对不上。输入尺寸从192×256改成384×512会小幅提升关键点定位精度但推理时间几乎翻倍实时场景里不太划算。4.2 关键点坐标折算从热图到原图坐标HRnet的输出是一个热图张量尺寸为(num_keypoints, heatmap_height, heatmap_width)热图上的每个像素值表示该位置出现某个关键点的概率。直接从热图里取最大值所在的坐标只能得到热图坐标系下的位置需要两步换算才能还原到原始图像坐标。def get_keypoints(heatmap, box, input_size, original_size): # heatmap: (17, 64, 48) 模型输出 # box: (x1, y1, x2, y2) 在原始图像坐标系下的检测框 batch_keypoints [] for hm in heatmap: score, idx torch.max(hm.view(-1), dim0) # 取热图全局最大值 y, x idx // hm.shape[1], idx % hm.shape[1] # 反推热图坐标 # 亚像素精修在最大值附近做局部加权平均可提升1~2像素精度 x max(0, min(x, hm.shape[1] - 1)) y max(0, min(y, hm.shape[0] - 1)) x (x 0.5) * (input_size[0] / hm.shape[1]) # 放大到输入尺寸 y (y 0.5) * (input_size[1] / hm.shape[0]) # 从输入尺寸坐标系映射到检测框局部坐标系 box_w, box_h box[2] - box[0], box[3] - box[1] orig_x box[0] (x / input_size[0]) * box_w orig_y box[1] (y / input_size[1]) * box_h batch_keypoints.append((orig_x, orig_y, score)) return batch_keypoints这里最关键的逻辑是坐标的两次映射。第一次乘以0.5是为了对齐像素中心避免系统性的偏移误差第二次从输入尺寸映射到检测框坐标是把关键点从网络的输入分辨率拉回到原始图像上。很多跑出来的骨架整体错位一个方向全是这一个公式写错的锅。4.3 绘制骨骼关键点连接线与置信度过滤关键点坐标拿到之后还需要按人体结构的连接关系把它们画成骨架。COCO数据集的17个关键点有固定的序号规则0是鼻子1到4分别是左右眼和左右耳5和6是肩膀然后一路向下到脚踝。骨骼绘制本质上就是按这个序号规则连线。SKELETON [ (0, 1), (0, 2), (1, 3), (2, 4), # 面部连线 (5, 6), (5, 7), (7, 9), (6, 8), (8, 10), # 手臂与躯干 (5, 11), (6, 12), (11, 12), # 髋部与躯干 (11, 13), (13, 15), (12, 14), (14, 16) # 腿部 ] COLORS [ (255, 0, 0), (255, 0, 0), (255, 0, 0), (255, 0, 0), (0, 255, 0), (0, 255, 0), (0, 0, 255), (0, 0, 255), (255, 255, 0), (255, 255, 0), (0, 255, 255), (0, 255, 255) ] def draw_skeleton(img, keypoints, conf_threshold0.3): for idx, (x, y, score) in enumerate(keypoints): if score conf_threshold: cv2.circle(img, (int(x), int(y)), 3, COLORS[idx % len(COLORS)], -1) for start, end in SKELETON: sx, sy, ss keypoints[start] ex, ey, es keypoints[end] if ss conf_threshold and es conf_threshold: cv2.line(img, (int(sx), int(sy)), (int(ex), int(ey)), (0, 255, 0), 2) return img绘制时最容易忽略的是置信度过滤条件很多同学把关键点都画出来了但同一根骨骼的两个端点置信度差很多低置信度的端点位置本身就是模型瞎猜的连出来的线自然也是错的。挂一个conf_threshold参数低于阈值的关键点不画也不连线画面会干净非常多。5. 常见报错排查五类高频问题的定位与修复5.1 gbk codec cant decode byte文件编码引发的路径崩溃现象程序运行到读取图片或加载yaml时报错错误信息形如UnicodeDecodeError: gbk codec cant decode byte 0x...发生在Windows环境下的概率极高。原因Windows的默认编码是gbk但项目代码里用open()读取的文件可能是UTF-8编码尤其是yaml配置文件和包含中文路径的文本文件。PyTorch和OpenCV在读取文件时如果显式指定了编码就不会触发这个问题但如果代码里写的是open(path, r)这种不带编码参数的方式Python就会尝试用系统默认编码解析UTF-8的中文字符在gbk下解码直接崩溃。解决推荐在项目根目录写一个sitecustomize.py或者直接在所有open调用里强制指定编码。更省事的方式是在入口文件顶部设置环境变量。import sys import io sys.stdout io.TextIOWrapper(sys.stdout.buffer, encodingutf-8) # 所有涉及open()的地方都显式传encodingutf-8 with open(data/coco.yaml, r, encodingutf-8) as f: config yaml.safe_load(f)从那以后我每次配置新环境都先加这一段这个坑几乎成了Windows下跑PyTorch项目的必经之路。5.2 matplotlib.use(Agg) 报错无显示环境下的后端切换现象在Linux服务器上跑代码时一执行到plt.show()就直接报Matplotlib is currently using agg, which is a non-GUI backend或者干脆闪退图片没显示出来。原因matplotlib默认的后端聚合器Agg是专门用于生成图片文件的它不依赖任何图形界面。在Linux服务器或通过SSH远程连接的环境里没有X Window显示服务任何绘图操作都无法渲染到屏幕但写文件是可以的。解决代码里已经写了matplotlib.use(Agg)这行说明原作者已经考虑到无显示环境你要做的是确认这行代码在导入matplotlib之后、调用任何绘图函数之前执行并且所有可视化结果通过savefig保存而不是plt.show展示。import matplotlib matplotlib.use(Agg) # 强制使用无GUI后端只用于写文件 import matplotlib.pyplot as plt plt.figure(figsize(8, 6)) plt.imshow(cv2.cvtColor(vis_img, cv2.COLOR_BGR2RGB)) plt.axis(off) plt.savefig(output/result_plt.png, dpi150, bbox_inchestight) plt.close()一定要记得plt.close()释放当前图形对象否则循环里连续绘图会导致内存持续增长最后程序直接卡死。bboi_inchestight会让保存的图片自动裁剪掉多余白边输出结果更干净。5.3 AttributeError: Upsample object has no attribute recompute_scale_factor现象加载某个PyTorch老版本训练的权重时前向推理跑到Upsample层就报AttributeError: Upsample object has no attribute recompute_scale_factor通常是新旧版本兼容问题。原因PyTorch 1.6到1.9之间的Upsample实现里有一个recompute_scale_factor属性用来控制是否重新计算缩放因子。到了PyTorch 1.10之后这个属性被移除了但老权重文件里的state_dict可能还保留着旧层的状态键加载时新旧结构对不上。解决最直接的办法是把PyTorch降级到权重对应的版本。但如果你的环境里其他项目依赖新版本torch不方便降级可以给Upsample类重新挂上这个属性作为兼容层或者用torch.serialization.add_safe_globals把旧版Upsample标记为兼容。遇到这种问题我一般先检查torchvision和torch的版本配对大概率是版本错位而不是代码逻辑问题。5.4 YOLOv5训练警告functional.py相关提示现象训练时终端频繁打出和functional.py相关的Warning例如UserWarning: torch.meshgrid: in an upcoming release或者torch.broadcast_tensors相关提示。程序还能继续跑但日志刷屏严重影响看其他信息。原因YOLOv5官方代码为了兼容不同PyTorch版本做了很多版本适配老版本代码在新版本torch下会触发废弃API警告。本质上不影响推理结果但说明你的torch版本比代码预期的要新部分API行为已经变化。解决不追求消除所有警告的话可以直接忽略。但如果你想清理日志可以升级YOLOv5代码到最新release版本或者单独消除这类警告。import warnings warnings.filterwarnings(ignore, categoryUserWarning, moduletorch) # 在import torch之后、训练代码之前加上这行这条属于典型的「知道它无害但很烦」的报错升级依赖通常能根治但有风险毕竟工程文件里的其他代码可能是基于旧API写法写的。5.5 路径问题明明文件存在却报FileNotFoundError现象权重文件确实放在了指定目录下但程序运行时依然报找不到文件。在Windows上尤其常见。原因最常见的场景是代码和文件在同一个目录但工作目录不是项目根目录。Python的相对路径是相对于当前工作目录解析的而不是相对于代码文件所在目录。在IDE里运行和命令行运行时工作目录可能不一样。解决不要依赖相对路径在入口代码段强制把工作目录切换到项目根目录。import os import sys ROOT_DIR os.path.dirname(os.path.abspath(__file__)) os.chdir(ROOT_DIR) # 切换工作目录到项目根目录 sys.path.insert(0, ROOT_DIR) # 确保根目录在模块搜索路径中这段放在整个程序最前面路径问题基本能一次性消除。还有个隐蔽场景yaml里配置的路径带空格或中文OpenCV读取时会解析失败把这些文件统一重命名为纯英文路径是最省心的处理方式。6. 实时摄像头检测跳帧策略与跨场景迁移的工程习惯摄像头实时推理和静态图片推理在代码层面只有几行差异但工程难度不在逻辑上而在性能保障上。直接用同一个检测函数逐帧处理视频流大概率FPS不到10骨架图一卡一卡的完全没法用。我一般会从三个方向做优化。第一跳帧策略。人体姿态在相邻两帧之间的变化很小关键点坐标不会因为人没动就跑到另一个位置去。检测和关键点回归每处理一帧就足够中间帧直接用上一帧的关键点结果绘制即可把宝贵的时间省给真正需要计算的帧。frame_count 0 SKIP_FRAMES 2 # 每2帧做一次完整推理其余帧复用结果 while cap.isOpened(): ret, frame cap.read() if not ret: break if frame_count % SKIP_FRAMES 0: boxes detector.detect(frame, conf_thres0.4) keypoints hrnet.predict(frame, boxes) vis_frame hrnet.draw_skeleton(frame, keypoints) cv2.imshow(Pose Estimation, vis_frame) frame_count 1 if cv2.waitKey(1) 0xFF ord(q): break第二输入尺寸收窄。HRnet默认输入是192×256实时场景里可以缩减到160×192精度损失大概一个百分点但推理时间能降下来不少。代价是这个尺寸下小目标人的关键点会更容易抖动如果你检测的场景里人离摄像头比较远建议保持原尺寸不要动。第三多线程分离。把视频读取和模型推理放到不同的线程里避免解码和推理互相等待。实际操作上我会把cap.read()放进一个单独线程里预读下一帧主线程只做推理和绘制。这个优化在摄像头帧率较低时效果明显视频文件的提升反而不大。最后提醒一下换了一台机器或者换了一个摄像头之后不要只改路径又重新跑一遍最好先用手头现有的测试图片验证模型权重加载没问题再切到摄像头实时模式。我原来经常跳着步骤来结果发现换了摄像头之后画面偏色导致检测框大量误检排查了大半天才意识到是白平衡参数没调从那以后我每次换场景都会强制先做一轮静态图回归测试再上实时希望帮到你。本文还有配套的精品资源点击获取