☰
OpenPose+YOLOv5人体姿态识别:从环境搭建到参数调优实战
2026/10/1 11:59:40 网站建设 项目流程

简介:这套源码包是一份结合OpenPose与YOLOv5的人体姿态识别实战项目,面向具备计算机视觉和机器学习基础的开发者与研究者,解决实时多人检测与肢体、面部、手部关键点估计问题,兼顾目标检测与关键点回归,提升了识别准确度和效率。包内共716个文件,以hpp/cpp源文件、py训练脚本、cmake构建配置为主,还包含obj编译中间件、dll/exe运行组件、jpg/png样例图与mp4演示视频等,压缩包整体约85.59MB,目录划分清晰,便于定位不同模块。已有382人学习浏览。源码覆盖数据预处理、网络结构设计、特征提取、关键点预测、后处理等多个环节,同时提供安装配置说明与注释,方便读者复现整个训练流程并根据运动分析、交互设计、安全监控等场景调优。深入阅读和修改源码还能理解各步骤的实现原理,这套项目因此不仅是算法示例,更是一套从理论到落地的完整姿态识别工具包。

1. 人体姿态识别先分清两件事:OpenPose和YOLOv5到底各管哪一段

拿到“基于OpenPose+YOLOv5实现的人体姿态识别算法”这个项目标题,很多新手第一反应是“两个模型是不是要打架”。实际上这套方案的组合逻辑很清楚:YOLOv5负责把人从画面里框出来,OpenPose负责在框出来的这个人身上找关键点(肩膀、手肘、膝盖这些),两个模型串起来才能得到一整套人体姿态骨架。你需要它解决的问题也很具体:给一张图或一段视频,输出每个关节点的坐标和置信度,可用于健身动作计数、安防摔倒检测、人机交互里的手势姿态判断。适合正在做目标检测但不熟悉关键点回归的工程师,或者想用现成源码快速搭一套可演示姿态识别服务的同学。

常见的坑是直接拿OpenPose跑整张图,人一多、背景一杂,关键点会乱跳;而拆成“检测+姿态”两段式后,每个模型的任务都变简单了。下面先讲环境怎么搭,再讲坐标怎么串,最后落到底层参数和验证方法。

2. 先搭环境:OpenPose的两种落地方式和YOLOv5的权重选择

2.1 OpenPose本地跑的两种姿势:一版源码 vs 轻量onnx

OpenPose本身有两个层面的使用方式。第一种是直接拉官方源码编译,CMake配置CUDA和Caffe,能跑原始模型但环境极难一次过。第二种是把它导出成ONNX,用onnxruntime做推理,省掉一堆Python版本和Caffe依赖的兼容问题。实战项目里给你的源码大概率是第二种——数据流更清晰,方便和YOLOv5的推理代码放在同一个Python进程里。

我用得最稳的落地方式是:YOLOv5用PyTorch原生推理,OpenPose用onnxruntime跑ONNX,两者都用同一套OpenCV读帧,避免来回转换张量格式。代码层面的最小骨架是这样:

import cv2 import torch import onnxruntime as ort # 初始化 YOLOv5 det_model = torch.hub.load('yolov5', 'custom', path='yolov5s.pt', source='local') det_model.conf = 0.4 det_model.iou = 0.5 # 初始化 OpenPose ONNX pose_ort = ort.InferenceSession('openpose_light.onnx', providers=['CUDAExecutionProvider'])

这里有个容易忽略的细节:ONNX模型经手的输入张量是NCHW,OpenCV读出来的图是HWC的BGR,必须在送入模型前做归一化并且转换通道顺序。不要用PyTorch里的ToTensor去处理,直接手写一份预处理反而更好排查问题。

2.2 YOLOv5在这里不是检测人,是给姿态估计框人

YOLOv5在姿态识别项目里通常用的是yolov5s或者yolov5m。为什么不用更大的yolov5x?因为姿态估计模型OpenPose的CPU/GPU开销已经在那边了,YOLOv5只需要提供足够准的候选框,框稍微松一点没关系,关键点回归会自己修正一部分误差。yolov5s在COCO上的mAP对“人”这个类别足够用,而且帧率能留出余量给后面的OpenPose。

选权重的另一个标准是看你的场景。俯视摄像头和普通平视摄像头对检测框的尺度要求不一样,俯视时人更小,yolov5s容易漏掉蹲着的人。这时候换成yolov5m,或者把检测的imgsz从640改成1280,都比换大模型来得直接。

还有一个很容易踩的坑:YOLOv5检测到的目标会包含被遮挡一半的人。姿态估计面对这些框时,OpenPose会输出低置信度的关键点。所以要在中间加一道过滤规则——检测框的宽高比太极端,或者框的面积小于整个画面的一定比例,就直接丢弃。把过滤代码放在两段模型中间:

boxes = det_model(img)[0].xyxy[0].cpu().numpy() valid_boxes = [] for box in boxes: x1, y1, x2, y2, conf, cls = box[:6] if int(cls) != 0: continue if conf < det_model.conf: continue w, h = x2 - x1, y2 - y1 if w < 20 or h < 40: # 过滤过小目标 continue if h / w > 5: # 过滤细长碎框 continue valid_boxes.append([int(x1), int(y1), int(x2), int(y2)])

这里cls == 0是COCO数据集中“person”的类别索引。如果你的YOLOv5模型只训练了人,那就不需要这个判断,直接全部当作候选人框。

3. 把OpenPose和YOLOv5串成一条流水线:从框到骨架的坐标流转

3.1 主流程:检测-裁剪-关键点回归-回映射

两段式姿态识别里最核心的是坐标空间转换。YOLOv5给出的框坐标是相对于原图的,而OpenPose的输入是你裁剪下来的那一小块图,输出的关键点坐标也是相对于裁剪图的。所以必须把OpenPose输出的坐标加回裁剪框的偏移量,才能拼回原图坐标。

一个常见的错误是直接用原图去跑OpenPose,这样虽然省了坐标映射,但多人场景下关键点会互相串。更稳的做法是:先对每个检测框做裁剪,再把裁剪图缩放到OpenPose要求的输入尺寸(常见的是368x368或656x368),推理后把坐标按缩放比例映射回原图。

def pose_on_crop(img, box): x1, y1, x2, y2 = box crop = img[y1:y2, x1:x2] h, w = crop.shape[:2] input_w, input_h = 368, 368 resized = cv2.resize(crop, (input_w, input_h)) # 预处理 input_tensor = resized[:, :, ::-1].transpose(2, 0, 1).astype(np.float32) / 255.0 input_tensor = np.expand_dims(input_tensor, axis=0) # 推理 outputs = pose_ort.run(None, {'input': input_tensor}) # outputs[0] 是 PAF 字段,outputs[1] 是关键点热力图 # 从热力图解析关键点坐标(代码省略解析细节) keypoints = parse_heatmaps(outputs[1]) # 回映射到原图坐标 scale_x = w / input_w scale_y = h / input_h for kp in keypoints: kp.x = int(kp.x * scale_x) + x1 kp.y = int(kp.y * scale_y) + y1 return keypoints

parse_heatmaps这一步有讲究:OpenPose输出的热力图形状是(num_keypoints, heatmap_h, heatmap_w),每个通道对应一个关键点。取每个通道最大值位置作为关键点坐标,但不要只看最大值,要做高斯峰值领域的亚像素修正,否则输出坐标只能到像素整数级,在精细动作识别里会看到明显的抖动。

3.2 关键参数:conf_thres、iou_thres、upsample_ratio和两个模型的输入尺寸

这组参数直接决定你的姿态识别会不会翻车,按经验给一组默认值:

参数推荐值说明
YOLOv5 conf_thres0.4低于这个置信度直接丢弃,太低会混入误检
YOLOv5 iou_thres0.5NMS重叠阈值,多人密集场景降到0.3
YOLOv5 imgsz640不要太低,小目标会漏检
OpenPose input_w368标准输入宽度,可调低到256提速
OpenPose input_h368保持宽高比时需先做letterbox
关键点置信度阈值0.3低于这个阈值的关键点不画、不参与后续计算

upsample_ratio是OpenPose模型内部的一个推理参数,控制热力图和PAF的上采样倍数。默认0是直接由网络输出分辨率解析关键点,设为1或2精度更高但更慢。如果你的应用是健身动作计数,坐标抖动容忍度高,直接设0即可;如果是手势级别的细粒度识别,建议设1。设置方式是在ONNX推理时通过ort.InferenceSession的run传入额外输入,很多简化源码把这个参数写死在模型里了,就需要你反序列化模型图看输入节点名。

# 如果 ONNX 模型里有额外输入节点,这样传 outputs = pose_ort.run(None, { 'input': input_tensor, 'upsample_ratio': np.array([1], dtype=np.float32) })

注意不是所有OpenPose ONNX都带这个输入节点,不带就不用传,传了反而报错。开头先用不带的上采样版本跑通,再优化精度。

4. 训练姿态模型时最容易栽的四个避坑点

4.1 标注框和关键点没对齐,导致骨架全歪

很多项目源码里直接使用COCO预训练权重,你只需要在推理阶段调用,这时候不会遇到标注问题。但如果你要微调,最常见的翻车就是:YOLOv5检测框标注和OpenPose关键点标注不一致。例如检测框是紧贴人身边的,但关键点是按COCO标准标在人体骨骼关节上,两者尺度差异不大,但如果头部的框只到头顶,关键点里的鼻子、眼睛坐标却到了框外,训练时网络就会学得混乱。

现象:推理出来的骨架整体向左下偏移半截,但检测框很准。原因是训练数据里关键点相对框的位置分布不统一。解决方式是在做数据预处理时,统一把关键点坐标转换成相对检测框的归一化坐标:

# 标注格式统一为:关键点相对框左上角的偏移比例 kx_norm = (kx - x1) / box_w ky_norm = (ky - y1) / box_h

这样模型学的是“鼻子在框内大概0.5x0.2的位置”,而不是学绝对像素,泛化能力强得多。很多项目源码没做这一步,微调后效果反而不如原版,就是这个原因。

4.2 多人场景下只检测到半个人

现象:画面里两个人交叉走过,姿态识别只输出其中一个人的骨架,另一个人的肢体残缺。原因不是姿态模型差,而是YOLOv5检测框把另一个人的上半身和第一个人的下半身框在了一起,OpenPose只能看到半个身体。

解决有两个方向。第一个是把YOLOv5的iou_thres调低到0.3,让重叠的两个人能分成两个框。第二个是在姿态解析时,强制要求单人框宽高比在0.3到1.2之间,超出就按检测框的中心点做左右各一半的扩展裁剪,保证每个人至少有一个完整框。后一种方法更有用,因为YOLOv5训练时很难覆盖密集遮挡场景,不如在推理时做规则修正。

4.3 关键点置信度阈值设太高,把有效点全滤没了

现象:画骨架时只显示半边手臂,或者倒地的人只剩下一个躯干。这时候不要怀疑模型坏了,先看你的关键点置信度阈值。OpenPose输出的关键点置信度在遮挡场景下普遍在0.2到0.45之间,如果你按论文里常见的0.8去过滤,等于把大多数有效关键点都砍掉了。

血泪经验是:对关键点置信度设0.3作为判断“有/无”的门槛,但在绘制和计算角度时使用0.1的软阈值。也就是说,只要置信度大于0.1就参与计算,但低于0.3的点在可视化时标记为空心圆或虚线,这样既不会让骨架断层,又能让下游知道哪些点是猜测出来的。

if kp.conf >= 0.3: draw_solid(kp) elif kp.conf >= 0.1: draw_hollow(kp) # 仍参与角度计算,但标记 low_confidence 标志 else: pass # 完全不使用

这个做法在跌倒检测里尤其重要:人倒地时膝盖和脚踝被身体遮挡,置信度往往只有0.1到0.2,仍然可以从关键点连线方向判断姿态,盲目过滤会直接漏报。

4.4 OpenPose的Paf字段到底要不要用

OpenPose之所以比传统关键点回归强,是因为它同时输出Part Affinity Fields(PAF),用来判断关键点之间的连接关系。但很多简化项目源码只解析了热力图,然后按最近距离把关键点连成骨架,这样在单人场景下问题不大,多人场景就会把A的手接到B的手上。

如果你要处理多人场景,必须用PAF。做法比较复杂,常见做法是用post_process解析PAF的矢量场,计算两个候选关键点之间所有像素的PAF向量与连线的积分相似度,越高越可能是同一人的部件。代码量不小,直接建议使用OpenPose官方库里的getPoseFromHeatmap函数,或者在ONNX推理后调用openpose的postprocess模块。

如果不打算接PAF,也有替代方案:给每个检测框内只跑一次姿态估计,默认框内就是一个人。刚才说了,这样在密集场景会出问题,所以建议至少保留PAF的软连接逻辑,即使只是将两个关键点之间的PAF均值作为边的权重,也能明显减少错误连接。

5. 把姿态识别跑成实时服务:推理速度与性能调优

5.1 帧率瓶颈不在YOLOv5,在OpenPose的PAF计算

先看一个典型数据:YOLOv5s在GPU上检测640x640的图,耗时约10ms到15ms。OpenPose的ONNX在同样的GPU上处理368x368单人框,耗时约20ms到40ms。如果画面中有3个人,就是三次OpenPose推理,总耗时至少60ms,帧率直接掉到15FPS以下。瓶颈非常清楚:OpenPose的PAF计算需要输出几十个通道张量,显存带宽占用极高。

所以调优的第一原则不是减少YOLOv5的输入尺寸,而是减少OpenPose的调用次数。两个有效手段:一是把多个人的裁剪框拼成一个batch一次性推理,而不是循环跑单张;二是把过于接近的检测框合并,比如两个人并排坐,间距小于20像素,可以只取一个人做姿态估计,另一个用相近骨架平移近似。

5.2 用onnx+batch推理把整体延迟压到30ms级别

批处理是见效最快的提速手段。ONNX运行时会自动把相同shape的输入拼到batch维度,你只需要把多个人裁剪图预处理后堆叠成一个numpy数组:

crops = [] for box in valid_boxes: crop = crop_and_resize(img, box, 368, 368) crops.append(preprocess(crop)) if len(crops) > 0: batch = np.stack(crops) # shape: (N, 3, 368, 368) outputs = pose_ort.run(None, {'input': batch})

注意几个参数:ONNX模型是否支持动态batch维度,可以在InferenceSession创建后打印输入shape,如果第一个维度是None或dynamic_axes,就可以直接传N张图;如果是固定1,需要先用onnx工具把模型转成动态batch。转换命令通常是:

import onnx model = onnx.load('openpose_light.onnx') model.graph.input[0].type.tensor_type.shape.dim[0].dim_param = 'N' onnx.save(model, 'openpose_dynamic.onnx')

但这样直接改文件不够可靠,建议用onnxruntime.transformers的优化器,它会自动处理动态轴。批处理4个人时,单帧总耗时可从120ms降到60ms左右,加上YOLOv5的15ms,整体大约75ms,仍然达不到理想30ms。再往下压就要换思路:把OpenPose输入尺寸从368x368降到256x256,精度略有损失,但耗时能再减一半,最终可以到40ms以内。我一般会先在256x256下跑通全流程,再调回368x368看精度差异,用数据决定尺寸。

另一个经常被忽略的调参点是YOLOv5的NMS。默认的NMS在人群密集时会产生大量冗余框,每个冗余框都会触发一次OpenPose推理。把iou_thres从0.5调到0.3,能减少30%到50%的无效框,速度提升明显,误检率反而更低。

6. 验证姿态识别准不准:三个不看acc的落地验证方法

6.1 画骨架叠加视频,先看时序抖动

模型在单帧上看准确率很高,不代表落地能用。最常见的问题就是时序抖动:同一个动作在连续帧里,手肘坐标上下跳个不停。验证方法是把骨架画在视频上,放慢到逐帧播放,观察关节点像素位置是否在半径5个像素内稳定。如果抖动明显,先看是否有必要增加关键点置信度软阈值,其次考虑在输出侧加一阶低通滤波。

# 轻量平滑:让当前关键点位置只更新20% kp_cur = kp_new * 0.8 + kp_prev * 0.2

这个系数在需要快速转身的动作里要调快一些,不要一刀切,否则反应迟钝。我通常先按0.2跑一遍,看动作延迟能否接受,再往0.5方向调。

6.2 用PCK/OKS算关键点误差,别只看loss

训练时的loss下降不代表关键点像素误差小。落地验证要看两个指标:PCK(关键点正确估计比例)和OKS(关键点相似度,COCO官方指标)。PCK的阈值一般设置为躯干直径的10%,也就是如果预测点与真实点距离小于这个阈值,算正确。OKS则根据人体尺度做了归一化,更合理。

计算时不要写错关键点顺序。COCO的17个关键点顺序是:鼻子、左眼、右眼、左耳、右耳、左肩、右肩、左肘、右肘、左腕、右腕、左髋、右髋、左膝、右膝、左踝、右踝。如果你的模型输出顺序不同,先对齐再算误差,否则OKS分数会特别差,而且没人能看出来算错。

6.3 端到端场景测试:一套动作的识别稳定性

姿态识别的最终价值不是画骨架,而是驱动业务逻辑。以健身动作计数为例,你需要验证的是仰卧起坐做10次,系统计出几次。这里最容易出现的问题是计数依赖的关键点角度忽然跳到异常值。比如按躯干和腿的夹角判断起坐,手肘碰到地面时别人体遮挡导致髋关节置信度低,角度突变一次,计数器就多做一次。

我的验证方法是录下三段测试视频:正常速度10次、快速5次、遮挡情况下5次,统计计数误差。如果遮挡场景误差超过30%,就去调整判断条件中的角度阈值,而不是去调姿态模型。姿态模型在这个场景中只负责输出坐标,业务逻辑必须对低置信度有容错。

另外,给骨架绘制代码里加一个调试开关,把置信度低于0.3的点画成不同颜色。当你看到大量空心点出现在挤压区域,就能直接判断需要增加相机角度还是调低阈值,而不是对着测试结果盲目改参数。

这套“检测+姿态”的组合方案,如果能跑通坐标回流和参数过滤,已经能覆盖绝大多数室内人体姿态识别需求。我自己的习惯是先把YOLOv5的检测框单独输出一版视频,确认框稳定后再去接OpenPose,哪两级出了问题,就先单独验证哪一级,别一上来就跑全流程,否则定位问题都无从下手。希望帮到你。

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

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

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

立即咨询