news 2026/10/5 9:28:32

基于YOLOv5+OpenPose的人体姿态识别算法工程实践与优化

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
基于YOLOv5+OpenPose的人体姿态识别算法工程实践与优化

简介:一套结合OpenPose与YOLOv5的人体姿态识别完整项目,面向具备计算机视觉与机器学习基础的开发者、研究者,用于解决多人关键点检测、实时姿态估计及工程化部署等实际问题。压缩包共716个文件、约85.59MB,内部以C++头文件/源文件(hpp、cpp)、Visual Studio工程配置(sln、vcxproj、filters)及CMake构建脚本为主体,同时包含Python脚本、Markdown说明文档、PDF资料以及jpg/png/mp4示例与演示文件,文件类型丰富,源码、构建配置、说明文档与示例素材均有覆盖。项目将OpenPose的多人二维关键点检测能力与YOLOv5的实时检测优势结合,源码覆盖数据预处理、网络训练、后处理、模型评估等环节,同时提供数据标注、可视化及性能评估工具,并在文档中给出安装配置指南与运行说明,方便用户复现和二次开发。目前已累计382人学习,适合希望深入理解姿态识别内部机制,并在运动分析、交互设计、安全监控等场景落地算法的实践者。

1. 姿态识别不是关键点检测:为什么YOLOv5+OpenPose的先后顺序决定成败

“姿态识别”这个标题很容易让人误以为就是把人框出来、把骨架点画上去。真正落地时恰恰相反:YOLOv5负责回答“画面里人在哪”,OpenPose负责回答“这个人的每个关节在哪个像素”,而标题里的“人体姿态识别算法”还要再往上一层,把关节坐标变成“站、坐、蹲、抬手、跌倒”这些具体语义。把这三步拆开,是因为只靠OpenPose在背景复杂、人多的场景里会把两个人的骨架连在一起;先让YOLOv5把人框出来再做关键点,精度和速度都能明显提升。这套方案适合做安防跌倒检测、健身动作计数、人机交互的工程同学,也是从“能跑通”到“能交付”之间性价比很高的一条路线。

2. YOLOv5与OpenPose怎么分工:检测框的质量决定了关键点上限

2.1 为什么把YOLOv5放在OpenPose前面:先框人再取点

OpenPose自带的人体检测器在干净、人少的图里够用,一旦进了监控、商场、公交站这类背景杂物多、行人重叠的场景,漏检和误检就一起冒出来。漏检意味着后面关键点直接没有输入,误检则意味着PAF阶段会去组合一个根本不存在的人体结构。把YOLOv5放在最前面,本质上是用一个更容易训练、更容易换数据集的检测网络先解决“人是什么”,再让OpenPose专注于脖子、肩膀、肘部这些细粒度定位。

选型上常见取舍是换YOLOv8或RTMDet,但YOLOv5的优势在于生态旧、资料多、转ONNX和TensorRT的踩坑案例全。很多做姿态识别的源码项目里,YOLOv5已经把检测结果整理成了xyxy格式,后面接OpenPose只需要处理一个二维数组。YOLOv5的CSPDarknet骨架在精度与速度之间比较平衡,yolov5s权重在CPU上也能跑到几十毫秒一帧,对“人体检测”这种单一类别任务绰绰有余。

这里有个容易被忽略的点:YOLOv5给出的person框往往紧贴人体边缘。直接拿这个框去裁图,手肘、膝盖、脚踝很容易被切断,OpenPose在这些关节上的响应会直接归零。所以裁图时一定要做外扩(padding),外扩比例一般取框宽高的20%到30%。这个细节看着小,却是后面大量“关键点漂移”问题的根源。

2.2 OpenPose输出的是坐标点,不是骨架图:BODY_25结构与PAF

OpenPose推理返回的是一组多维数组。以默认的BODY_25模型为例,每个人的结果包含25个关键点,索引从0到24,分别是nose、neck、左右肩、左右肘、左右腕、mid-hip、左右髋、左右膝、左右踝,再加上眼睛、耳朵和脚趾、脚踝的精细点。每个关键点是一个(x, y, confidence)三元组,x和y是像素坐标,confidence范围是0到1。被遮挡或超出画面时,confidence会明显下降甚至直接是0。

很多第一次用OpenPose的人以为输出就是一张画好骨架的图,这个理解偏差是后面一系列问题的主要来源。姿态识别算法的下游必须从“点”开始,而不是从“图”开始。比如判断一个人是否蹲下,要算的是膝关节角度,这个角度来自髋、膝、踝三个点的坐标,而不是看图上骨架长什么样。同样,低头看手机时鼻子和脖子会重叠,侧身时一边的肩膀和手腕互相遮挡,关键点缺失是常态,不是异常。姿态识别必须从数据结构上接受缺失点,而不是假设每个点都存在。

PAF(部分亲和场)是OpenPose能在多人场景工作的关键。它给每个骨段生成一个方向向量场,网络在预测关键点的同时预测每段骨骼的方向,最后通过图优化把属于同一个人的关键点连起来。这也是为什么OpenPose比“先检测20个关节再组装”的模型更稳。但PAF也带来了参数敏感性:scale_number、scale_gap、part_candidates_threshold、number_people_max这几个参数每次都要细调。多尺度推理对小目标有帮助,但每多一个scale,耗时几乎线性上涨;在固定摄像头场景里,确认人体尺度范围后把scale_number降到1,推理速度提升非常明显。part_candidates_threshold默认在0.2到0.3之间,调太高会丢点,调太低会出现大量杂点,一般我先固定0.25。

2.3 先跑通默认模型再做微调:最省时间的切入方式

不要一上来就训练自定义模型。先用官方权重把链路的每一环跑通,理解输出格式,等关键点坐标和阈值判断都稳定了,再针对缺检场景微调YOLOv5。这样做的原因是OpenPose的骨干网络本身训练成本很高,重新训练关键点模型需要大量标注数据,而大多数项目真正缺的不是关键点精度,而是检测框质量、输入分辨率、阈值策略这三个外围环节。

多人场景还有个策略问题。把YOLOv5检测到的每个person框分别送入OpenPose,会出现同一人的骨架在相邻两个框里重复检测,而且另一个人被部分裁进来时,PAF会把两个人连起来。我的处理办法是:同帧人数大于3时用整帧一次跑OpenPose,不逐框裁;人数少时逐框裁,速度快,关键点也干净。YOLOv5后处理里的conf和iou在大多数场景里分别调到0.25和0.45,是这套方案里比较常用的底线值。

3. 最小可复现链路:用YOLOv5裁出人体,再交给OpenPose提取关键点

3.1 环境准备:把yolov5s权重和OpenPose的Python接口跑通

先准备YOLOv5环境。常见做法是直接clone官方YOLOv5仓库,然后按requirements安装依赖:

git clone https://github.com/ultralytics/yolov5 cd yolov5 pip install -r requirements.txt

装好后下载yolov5s.pt权重,然后写推理脚本。这里我习惯用torch.hub方式加载,省去维护本地仓库的麻烦:

import torch model = torch.hub.load('ultralytics/yolov5', 'yolov5s', pretrained=True) model.conf = 0.25 # 置信度阈值,低于该值的检测框会被丢弃 model.iou = 0.45 # NMS的IoU阈值,控制重叠框的合并力度 model.classes = [0] # 只保留COCO类别0:person results = model(frame, size=640) df = results.pandas().xyxy[0] person_boxes = df[df['name'] == 'person'].reset_index(drop=True)

model.classes = [0]是在模型后处理阶段直接过滤类别,比拿完整检测结果再过滤省掉大量无关节点的计算。conf=0.25和iou=0.45是YOLOv5后处理里最常用的超参数组合。如果摄像头装在俯视角度或目标普遍偏小,conf建议先降到0.2,否则漏检会非常严重。

OpenPose部分,源码项目里通常会封装一个OpenPoseDetector类,把模型初始化和关键点提取藏在里面。常见的调用方式是这样的:

from src.openpose import OpenPoseDetector detector = OpenPoseDetector( model_path='models/pose/coco/pose_iter_440000.caffemodel' ) keypoints, _ = detector.run(cropped_img)

提醒一下:这不是OpenPose官方仓库自带的通用API,不同项目封装的类路径和初始化参数可能不一样。关键要理解它的输入输出约定:输入一张彩色图,返回每个人的关键点数组,形状通常是(N, 25, 3),N是人数,25是BODY_25,3是x、y、confidence。

3.2 裁图与坐标还原:两个坐标系之间的唯一桥梁

YOLOv5在原图上输出坐标,OpenPose在裁图上输出坐标,中间差了一个offset。很多源码工程里的坑不在模型,而在这一步坐标换算。裁图函数我一般这样写:

def crop_person(frame, box, pad_ratio=0.25, min_side=240): x1, y1, x2, y2 = int(box['xmin']), int(box['ymin']), int(box['xmax']), int(box['ymax']) w, h = x2 - x1, y2 - y1 # 外扩,避免手肘膝盖被裁掉 px, py = int(w * pad_ratio), int(h * pad_ratio) x1 = max(0, x1 - px) y1 = max(0, y1 - py) x2 = min(frame.shape[1], x2 + px) y2 = min(frame.shape[0], y2 + py) # 过小的框直接放弃,关键点精度不够没有意义 if (x2 - x1) < min_side: return None, None return frame[y1:y2, x1:x2], (x1, y1)

pad_ratio=0.25是把检测框四周各外扩25%,给OpenPose留出看到完整肢体的空间。min_side=240是个经验值,低于这个像素高度的person框,OpenPose算出的关节坐标误差会大到没有实用价值,不如直接标记low_resolution。

拿到裁图后:

cropped, offset = crop_person(frame, person_boxes.iloc[0]) if cropped is None: continue kps, _ = detector.run(cropped) # 关键点坐标还原到原图 kps[:, 0] += offset[0] kps[:, 1] += offset[1]

这里只加offset,confidence列不用动。容易犯的错是把offset加了两遍,或者忘了OpenPose内部对输入图做了resize。如果OpenPose返回的坐标是网络输入尺寸下的坐标,还需要先按缩放比例还原:

scale_x = cropped.shape[1] / net_input_width scale_y = cropped.shape[0] / net_input_height kps[:, 0] *= scale_x kps[:, 1] *= scale_y

具体要不要乘,取决于项目源码里封装时有没有处理过。稳妥的做法是先用一张已知坐标的图做一次端到端验证,确认坐标落到原图的正确位置后再写业务逻辑。

3.3 相邻帧漏检兜底:用上一帧的检测框顶一下

视频流里YOLOv5偶尔会漏掉一帧,如果漏检发生在关键动作切换的瞬间,下游状态机就会丢掉一次状态变化。最轻量的兜底方案是保留上一帧的person box:

last_box = None for frame in video_frames: results = model(frame, size=640) df = results.pandas().xyxy[0] person_boxes = df[df['name'] == 'person'].reset_index(drop=True) if len(person_boxes) == 0 and last_box is not None: person_boxes = last_box.copy() if len(person_boxes) > 0: last_box = person_boxes.copy()

这个做法只适合目标没有大幅移动的场景。如果行人快速横穿画面,上一帧的框和当前帧位置偏差过大,OpenPose会在一个空区域里瞎算,反而产生更离谱的骨架点。我一般只在确认目标静止或缓慢移动的场合用它,比如坐姿识别、工位行为分析。这是典型的“没有银弹”的工程取舍。

4. 姿态识别算法实装:从关键点坐标到“站/蹲/坐”的可解释规则管线

4.1 三类不依赖绝对坐标的几何特征:角度、比例、偏移

拿到关键点坐标后,第一步不是直接判断动作,而是先把坐标转换成不受画面位置影响的特征。理由很简单:同一个站立姿势,站在画面左边和右边,x坐标完全不同;摄像头有俯仰角时,同样的身高在画面里的像素高度也不一样。关节角度、比例、偏移这三类特征对这些问题天然鲁棒。

以BODY_25为例,先定义索引映射:

JOINT = { 'nose': 0, 'neck': 1, 'RShoulder': 2, 'RElbow': 3, 'RWrist': 4, 'LShoulder': 5, 'LElbow': 6, 'LWrist': 7, 'MidHip': 8, 'RHip': 9, 'RKnee': 10, 'RAnkle': 11, 'LHip': 12, 'LKnee': 13, 'LAnkle': 14, 'REye': 15, 'LEye': 16, 'REar': 17, 'LEar': 18, }

然后写一个通用的三点夹角函数,用来算肘关节角、膝关节角、肩髋连线角度:

def calc_angle(a, b, c): """从三点坐标计算角度,b点是角点""" a, b, c = map(np.array, (a, b, c)) v1, v2 = a - b, c - b cos = np.dot(v1, v2) / (np.linalg.norm(v1) * np.linalg.norm(v2) + 1e-6) return np.degrees(np.arccos(np.clip(cos, -1.0, 1.0))) # 右膝关节角:髋-膝-踝 knee_angle = calc_angle(kps[JOINT['RHip']], kps[JOINT['RKnee']], kps[JOINT['RAnkle']])

1e-6加在分母上,防止两个向量长度为零时除零。膝盖角在站立时接近170到180度,蹲下时小于90度,坐下时通常在80到110度,是一个非常直接的特征。

比例特征用髋部相对高度。把髋部到脚踝的像素距离除以脖子到脚踝的像素距离,得到一个基本不受人物远近影响的比例:

body_height = np.linalg.norm(kps[JOINT['neck']] - kps[JOINT['RAnkle']]) hip_height = np.linalg.norm(kps[JOINT['MidHip']] - kps[JOINT['RAnkle']]) hip_ratio = hip_height / (body_height + 1e-6)

这个hip_ratio在站立时大约0.55到0.65,坐下或蹲下时会明显低于0.5,是区分“站着”和“坐着”的一个强特征。偏移特征我常用的是肩中点和髋中点的水平位移差,用来判断弯腰方向。

4.2 规则阈值不是拍脑袋:用标注样本观察,而不是凭感觉设

很多人拿到源码第一件事就是改阈值,改来改去发现这个场景能用,换个摄像头又废了。我的经验是:先采集当前摄像头视角下30到50帧已知动作的样本,把关键点坐标打印出来,观察角度和比例的分布,再定阈值。比如一个斜上方45度的监控视角,站立时膝关节角可能是160度,坐下时可能只有70度,和侧面平视场景的数据差别很大。

一个比较常见的初始阈值表是这样:

动作膝关节角度髋高比补充条件
站立>150度>0.55肩髋偏移小
坐下80~110度0.35~0.50髋部明显低于站立时
蹲下<85度0.25~0.40膝盖超过脚尖水平位置

这张表只能作为起点。固定摄像头场景里,真正要做的是把阈值写成配置文件,用一个标注脚本批量验证,而不是在代码里硬编码。我见过太多项目源码里阈值散落在各个函数里,改一个数要全局搜索,这就是后续维护最大的坑。

4.3 置信度过滤与缺失点处理:不硬补缺失,而是进入过渡态

OpenPose给出的每个关键点都有confidence,这个值本身就是一个信号。我会把低于0.3的关键点直接视为缺失:

conf = kps[:, 2] if conf[JOINT['RKnee']] < 0.3 or conf[JOINT['RAnkle']] < 0.3: state = 'transition' else: knee_angle = calc_angle(kps[JOINT['RHip']], kps[JOINT['RKnee']], kps[JOINT['RAnkle']]) state = classify_pose(knee_angle, hip_ratio)

这里的关键点是:缺失时不要用上一帧的数据去插值硬补,而是给一个transition过渡状态。原因很简单,动作切换的中间几帧骨架本身就是在变化的,插值补出来的数据会把“站起来”和“坐下去”中间那段过渡变成一段假的稳定状态,导致状态机切换延迟。过渡态会让整个逻辑更接近真实的人的物理动作。

分类函数classify_pose里规则本身不复杂,复杂的是让规则在连续帧上保持稳定。所以我会再加一个滑动窗口:最近5帧里同一状态出现3次以上才真正切换。这个“先滤波,再判定”的顺序,比单纯调角度阈值有效得多。

5. 姿态识别项目最容易翻车的五个现场:现象、原因与排查方案

5.1 关键点大量为0或乱飘:YOLOv5的框把人“裁骨折”了

现象:OpenPose只在胸口附近散着三四个点,手腕、膝盖完全不出现,或者时好时坏。

原因:YOLOv5给出的person框太贴合身体,外扩比例不够,OpenPose根本看不到被裁掉的肢体。这是最普遍的问题,和模型好坏没有关系。

解决:把裁图函数的pad_ratio从0.1调到0.25到0.3,再检查一下框边缘。我的习惯是把裁出来的图直接保存成调试图像,肉眼确认关节都在画面内。另外保证ROI长边不低于240像素,过小的目标对OpenPose没有意义。

5.2 OpenPose一帧要跑两秒:没有利用检测框裁剪输入

现象:视频离线分析要跑一晚上,摄像头实时流完全没法用。

原因:OpenPose的VGG前端在全图推理时计算量极大。直接把1080p整帧传进去,网络要扫描整张图找所有可能的人,耗时自然爆炸。

解决:先YOLOv5裁图,再把ROI的长边限制在400像素以内。这是整套方案里收益最大的一次改动,速度能提升一个数量级。如果还要更快,换OpenPose的轻量化版本或转ONNX,边缘设备上还要做量化,但那是另一条路。

5.3 站/坐/蹲分类在临界点疯狂跳变:阈值判定太“脆”了

现象:同一动作在不同帧里来回跳变,视频看着像抽搐。

原因:关键点坐标本身有1到2像素的抖动,算出来的角度在阈值边界附近来回震荡,单帧判定必然不稳定。

解决:用滑动窗口中位数加状态机切换。连续三帧判定为同一状态才真正切换,而不是每帧都更新。这是这类项目里最常见的玄学问题,调阈值调半天,最后发现问题是出在缺少时序平滑。

5.4 画面里明明有人但整套链路没反应:YOLOv5漏检引发连锁反应

现象:人站在画面里,但姿态识别结果一直是空。

原因:YOLOv5本身没检测到人,后面所有逻辑全部空转。常见诱因是目标太小、遮挡严重、逆光,或者conf阈值设得过高。

解决:先用results.pandas()打印置信度看具体数值,如果普遍低于0.4,说明模型对这个视角本身就不自信。把model.conf降到0.2,或者换yolov5m、yolov5x权重。如果是俯视、低照度这类特殊视角,光调阈值已经不够了,需要收集自己的数据微调YOLOv5,这一步属于“yolov5训练自己的数据集”范畴,第6章会讲训练参数。

5.5 骨架画出来歪得离谱:坐标系还原少乘了一次缩放或漏了offset

现象:关键点整体偏移几十像素,或者骨架出现“X形”错位。

原因:OpenPose内部对输入图做了resize,输出坐标没有恢复到裁图尺寸;或者裁图offset在加回原图时用错了坐标。

解决:把坐标还原拆成两步——先乘resize比例,再加offset。每一步用可视化单独验证,正确结果是把关键点画在原图上时,骨架和人体轮廓应该严丝合缝。不要图省事把两个操作合并成一个公式,否则出问题很难定位。

6. 进阶:用OKS误差评估替代“肉眼调参”,把自采数据训进YOLOv5

6.1 用OKS评估关键点质量:先把OpenPose的上限测出来

调参调得再细,不如先知道自己手里这套模型的上限。关键点检测的评估指标不是IoU,而是OKS(Object Keypoint Similarity)。它会把预测点和标注点的距离按人体尺度归一化,距离越近得分越高。做法是抽20张代表性图像,用标注工具标出关键点真值,再跑一遍YOLOv5+OpenPose链路,逐点计算OKS。如果某个关节的OKS均值低于0.7,说明OpenPose在这个关节上的输出本身就不可靠,后面不管怎么调规则阈值都白搭。

6.2 针对场景微调YOLOv5:俯视与低照度场景的补救手段

如果OKS评估发现问题出在检测漏检,而不是关键点本身,就需要用自己的数据微调YOLOv5。常见训练命令长这样:

python train.py --img 640 --batch 16 --epochs 50 \ --data custom.yaml --weights yolov5s.pt \ --hyp data/hyps/hyp.scratch-low.yaml

custom.yaml里包含训练集和验证集路径以及类别数,这里类别只有person一个。img 640和batch 16是兼顾显存和精度的常见选择。hyp.scratch-low.yaml里的学习率、anchor等超参数先不动,等第一轮训练完看验证集mAP,再针对性调lr0和anchor。微调YOLOv5的价值不在让检测更准,而在于减少漏检,这才是和OpenPose配合时真正需要补的那块短板。

6.3 让我少走冤枉路的一个习惯

我自己最早在这个方案上翻车,就是把OpenPose当成一个“什么图都能应付”的黑匣子,结果检测框、输入分辨率、置信度阈值三层有一层没控住,下游就整体崩坏。后来养成的习惯是:每次改一个阈值之前,先问自己一句“这个阈值对应的现象是什么”,能用OKS和可视化数据说明,就不靠感觉硬调。把坐标系验证和OKS评估做成脚本放在项目里,比任何参数经验都管用。希望帮到你。

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

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/5 9:28:18

通达信主力吸筹猛攻指标:源码拆解与实战用法

很多人拿到所谓“主力吸筹猛攻指标”&#xff0c;第一反应是看它能不能让自己买在起爆点。我的看法很直接&#xff1a;这类指标真正的价值&#xff0c;不在于那个红红绿绿的信号箭头&#xff0c;而在于它背后对“量、价、资金”三者关系的刻画方式。今天我把自己多年折腾通达信…

作者头像 李华
网站建设 2026/10/5 9:27:57

从闭源到开源:本地部署NeoHorse-Jev-4B决策模型实战指南

最近圈子里讨论最多的模型&#xff0c;除了各种Chat类大模型&#xff0c;还有一个叫Jev的决策模型。它不写诗、不聊天&#xff0c;专干“判断”这活&#xff0c;比如“这条工单该分给哪个组”“这笔交易要不要人工审核”。老实说&#xff0c;第一次看到Demo的时候我也觉得只是又…

作者头像 李华
网站建设 2026/10/5 9:27:16

让AI编程从“快”到“可靠”:Superpowers工作流全解析

在AI编程工具越来越普及的圈子里&#xff0c;Superpowers这个名字最近被频繁提起。它不是某个大厂发布的新IDE&#xff0c;也不是又一款“AI编程软件”&#xff0c;而是一套针对AI编程代理设计的开源技能与工作流集合。我最初接触它&#xff0c;是因为自己用命令行AI写代码时觉…

作者头像 李华
网站建设 2026/10/5 9:27:02

AI Native团队落地手册:Agent编排、CLAUDE.md与安全边界实战

1. 从“人写代码”到“人管意图”&#xff1a;AI Native 团队到底在变什么“AI Native”这个词最近被喊得很响&#xff0c;但真正落到一个研发团队里&#xff0c;它到底意味着什么&#xff1f;我见过太多团队把 AI Native 理解成“给每个人发一个 AI 编程助手账号”&#xff0c…

作者头像 李华
网站建设 2026/10/5 9:26:56

Java工程师转型AI Agent实战:Spring AI与LangChain4j构建ReAct循环

1. 为什么 Java 工程师转 AI Agent 有天然优势1.1 从 CRUD 到智能体&#xff1a;一次能力栈的平移这两年身边不少做 Java 的朋友都在焦虑同一件事&#xff1a;AI Agent 火了&#xff0c;但好像跟自己没什么关系。打开教程一看&#xff0c;清一色 Python&#xff0c;LangChain、…

作者头像 李华
网站建设 2026/10/5 9:26:01

KLayout形状编辑教程:矩形、多边形、路径与文本绘制技巧

1. 动手画之前&#xff0c;先搞懂KLayout里的“形状”体系 KLayout这个开源版图编辑器&#xff0c;我断断续续用了好几年。从最早只是拿它看看GDS文件&#xff0c;到后来真的用它在流片项目里画版图、做DRC检查、导出OAS&#xff0c;它一直是我工作流里离不开的一个工具。上一篇…

作者头像 李华