ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

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

基于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. 姿态识别不是关键点检测为什么YOLOv5OpenPose的先后顺序决定成败“姿态识别”这个标题很容易让人误以为就是把人框出来、把骨架点画上去。真正落地时恰恰相反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结构与PAFOpenPose推理返回的是一组多维数组。以默认的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, pretrainedTrue) model.conf 0.25 # 置信度阈值低于该值的检测框会被丢弃 model.iou 0.45 # NMS的IoU阈值控制重叠框的合并力度 model.classes [0] # 只保留COCO类别0person results model(frame, size640) df results.pandas().xyxy[0] person_boxes df[df[name] person].reset_index(dropTrue)model.classes [0]是在模型后处理阶段直接过滤类别比拿完整检测结果再过滤省掉大量无关节点的计算。conf0.25和iou0.45是YOLOv5后处理里最常用的超参数组合。如果摄像头装在俯视角度或目标普遍偏小conf建议先降到0.2否则漏检会非常严重。OpenPose部分源码项目里通常会封装一个OpenPoseDetector类把模型初始化和关键点提取藏在里面。常见的调用方式是这样的from src.openpose import OpenPoseDetector detector OpenPoseDetector( model_pathmodels/pose/coco/pose_iter_440000.caffemodel ) keypoints, _ detector.run(cropped_img)提醒一下这不是OpenPose官方仓库自带的通用API不同项目封装的类路径和初始化参数可能不一样。关键要理解它的输入输出约定输入一张彩色图返回每个人的关键点数组形状通常是(N, 25, 3)N是人数25是BODY_253是x、y、confidence。3.2 裁图与坐标还原两个坐标系之间的唯一桥梁YOLOv5在原图上输出坐标OpenPose在裁图上输出坐标中间差了一个offset。很多源码工程里的坑不在模型而在这一步坐标换算。裁图函数我一般这样写def crop_person(frame, box, pad_ratio0.25, min_side240): 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_ratio0.25是把检测框四周各外扩25%给OpenPose留出看到完整肢体的空间。min_side240是个经验值低于这个像素高度的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]这里只加offsetconfidence列不用动。容易犯的错是把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 boxlast_box None for frame in video_frames: results model(frame, size640) df results.pandas().xyxy[0] person_boxes df[df[name] person].reset_index(dropTrue) 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误差评估替代“肉眼调参”把自采数据训进YOLOv56.1 用OKS评估关键点质量先把OpenPose的上限测出来调参调得再细不如先知道自己手里这套模型的上限。关键点检测的评估指标不是IoU而是OKSObject Keypoint Similarity。它会把预测点和标注点的距离按人体尺度归一化距离越近得分越高。做法是抽20张代表性图像用标注工具标出关键点真值再跑一遍YOLOv5OpenPose链路逐点计算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.yamlcustom.yaml里包含训练集和验证集路径以及类别数这里类别只有person一个。img 640和batch 16是兼顾显存和精度的常见选择。hyp.scratch-low.yaml里的学习率、anchor等超参数先不动等第一轮训练完看验证集mAP再针对性调lr0和anchor。微调YOLOv5的价值不在让检测更准而在于减少漏检这才是和OpenPose配合时真正需要补的那块短板。6.3 让我少走冤枉路的一个习惯我自己最早在这个方案上翻车就是把OpenPose当成一个“什么图都能应付”的黑匣子结果检测框、输入分辨率、置信度阈值三层有一层没控住下游就整体崩坏。后来养成的习惯是每次改一个阈值之前先问自己一句“这个阈值对应的现象是什么”能用OKS和可视化数据说明就不靠感觉硬调。把坐标系验证和OKS评估做成脚本放在项目里比任何参数经验都管用。希望帮到你。本文还有配套的精品资源点击获取
返回列表