ARTICLE DETAIL

资讯详情

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

OpenPose+YOLOv5姿态识别:目标检测与关键点检测全流程解析

OpenPose+YOLOv5姿态识别:目标检测与关键点检测全流程解析 简介面向计算机视觉开发者和研究者这套基于OpenPose与YOLOv5的人体姿态识别项目将实时多人关键点检测与高效目标检测相融合适用于运动分析、交互设计、安全监控等多种场景。压缩包共716个文件大小85.59MB以C头文件、CMake构建脚本、Python脚本、可执行文件及动态库为主同时提供示例图像、演示视频、说明文档与模型配置文件目录按功能模块划分便于快速定位源码、编译产物和测试数据。项目源码完整覆盖数据预处理、网络训练、关键点预测、后处理与模型评估全流程并包含OpenPose底层CUDA编译文件与构建中间产物有助于理解底层实现与自定义扩展。此外还提供数据标注与可视化工具、性能评估脚本、安装配置指南和演示录像帮助开发者从环境搭建到实际调参逐步落地。已有382人学习下载是深入研究姿态识别算法、学习工程化实践与二次开发的优质实战项目。1. 姿态识别不是花架子OpenPose配YOLOv5解决的是“先把人找出来再画骨架”姿态识别在很多人印象里是论文演示里的花架子真正做过落地项目的人知道它能让一个摄像头从“只会录像”变成“能看懂动作”。健身动作计数、康复评估、安防越界检测底层逻辑都是同一件事先找到画面里的人再把人的肩、肘、膝等关节变成坐标序列让程序算角度、判行为。标题里的OpenPoseYOLOv5正是工业界最常用来搭人体姿态识别底座的组合。整套方案的边界很清楚YOLOv5负责输出人体框OpenPose负责在框内回归关键点关节乱跳找姿态模型人体漏框找检测模型坐标对不上就查坐标换算。适合手里有GPU或云主机的工程师和学生能从这篇文章里拿到原理选型、数据准备、训练推理、避坑和进阶调参一整套可以直接使用的参数与代码。下面按这个顺序展开中途给出的代码块都按最小可运行标准写改路径就能跑。2. 职责边界先想明白YOLOv5给检测框OpenPose给关节点别让两个模型互相背锅2.1 看一遍YOLOv5网络结构图会发现它输出的是框和类别不是骨架YOLOv5是目标检测网络Backbone负责提特征Neck做多尺度融合最后的Detect头输出三组尺寸的预测张量。看一遍YOLOv5网络结构图就确认它每一行输出都对应候选框、置信度和类别没有任何一支分支直接给关节坐标。所以在姿态识别方案里YOLOv5的角色是“人形锚点”负责回答画面里有哪些人、大概在哪个位置。它的作用不是画骨架而是把OpenPose的推理范围从整帧缩小到若干个裁剪区域省掉在背景和无关物体上算热图的成本。实际项目里我一般不会让YOLOv5裸跑整帧不预处理而是根据摄像头视野范围压缩输入尺寸。1080P输入时通常把imgsz设成640画面里的人特别小再升到960或1280。输入分辨率提高会直接影响召回率代价是推理时间变长。很多刚上手的人以为姿态不准就该去调OpenPose实际上在1080P画面里检测框都偏了半个人身位OpenPose拿到的裁剪区域里肢体信息不完整再准的骨架模型也修不回来。YOLOv5在这一步的产出物是形如[x1, y1, x2, y2, conf, cls]的向量。x1、y1是框左上角x2、y2是右下角conf是置信度cls是类别编号。后续所有裁剪操作都依赖这四个坐标值因此检测框越紧OpenPose的输入越干净。注意这里说的“紧”不是尽量贴住皮肤而是不要把人从膝盖处拦腰切掉留出足够边界。2.2 OpenPose输出的是带置信度的关键点不是“框里有什么”OpenPose的输出是固定的骨骼点集合。最常见的是COCO 17点定义鼻子、左右眼、左右耳、左右肩、左右肘、左右腕、左右髋、左右膝、左右踝每三个数一组对应x坐标、y坐标、置信度。整个结果可以理解成一个17×3的数组也可以拍平成一维51个float写进JSON不到一百字节下游动作识别、角度计算甚至ROS无人小车的行为控制都可以直接消费这组坐标。OpenPose的一个核心特点是它不只让关节点独立回归还用Part Affinity FieldsPAF解决“一个点属于哪个人”的问题。每个肢体都有一组方向场把肩膀和手肘连接起来时会计算方向一致性这样就算画面里有两个人交叉站在一起PAF也能尽量把左右手配回正确的人体。官方训练好的模型输出里每个关键点热图都会给一个置信度分数范围0到1。实际使用中低于0.3的点基本不可信要么被遮挡要么是热图在平坦区域随机出了一个最大值。使用原版OpenPose C接口很重CMake编译要装一堆依赖。落地时更常见的做法是拿到一个导出好的ONNX模型统一用onnxruntime或TensorRT来跑。这不改变OpenPose本身的方法论只是把推理管线换成更可控的形式。2.3 为什么两阶段组合没有输给单阶段姿态模型现在单阶段姿态模型也很多比如YOLOv8-pose、MediaPipe输入一张图直接给骨架。单阶段方案速度快部署简单为什么还要坚持OpenPoseYOLOv5这样的两阶段结构关键在于可控性。两阶段相当于把“找人体”和“找关节点”拆成两个独立问题先用目标检测消除背景干扰再让OpenPose在干净区域里精修骨架。单阶段模型在多人和遮挡场景下经常出现关节配对错乱一旦错乱整套下游逻辑都会跟着出错。还有一个工程上的理由两阶段的组件都可替换。X光、红外、俯拍视角下YOLOv5可以换成专门训练过的检测模型OpenPose可以换成更轻量的关键点模型数据流不需要改。单阶段方案要换模型则往往连带换整个训练流程。这个特性在需要适配多个摄像头、多种机位角度的项目里非常值钱。对比项OpenPoseYOLOv5两阶段MediaPipe/YOLOv8-pose单阶段多人遮挡检测框裁剪后单独推理干扰少全图直接回归交叉遮挡易配错计算成本高适合GPU低CPU也能跑组件替换检测和姿态可分别替换通常要整体切换排错难度问题能定位到单侧模型黑匣子只能整体调下游稳定性骨架坐标稳定适合算角度帧间噪声更明显需要额外平滑这张表不是要否认单阶段方案。固定机位、单目标、CPU环境下MediaPipe往往更合适。但在这个项目标题对应的场景里OpenPoseYOLOv5这类两阶段方案才是能把动作角度算稳定、能在多人场景下兜住底线的做法。3. 准备训练数据把COCO标注转成YOLOv5检测标签的脚本与四个边界坑3.1 COCO 17点为什么成了关键点标注默认语言训练姿态识别模型之前先得确定关键点的标注格式。COCO 17点像是这个领域的通用语言OpenPose的COCO模式、YOLOv5-pose、MMPose都认它。它的索引顺序固定鼻子在第0位左右眼分别是1和2左右耳是3和4左右肩是5和6左右肘是7和8左右腕是9和10左右髋是11和12左右膝是13和14左右踝是15和16。不同项目里这个顺序不能随意改训练和推理必须保持一致否则出来的关键点全错位。关键点标签中每个点还有一个可见性标记v。v0表示这个点没有标注v1表示被遮挡但坐标可靠v2表示完整可见。训练时如果直接把这些坐标统一喂进去未标注点的位置会被当成真实位置学习模型会被带偏。转换数据时应该把v0的点过滤掉或置成忽略值这是最容易犯的低级错误但返工成本很高。3.2 标注工具选型与标签文件长什么样常用标注工具是CVAT和Label Studio。CVAT对视频标注支持好导出的COCO json格式比较标准Label Studio更灵活适合自定义布局和多人协作。不管用哪个最终导出格式尽量统一成COCO json后续转换脚本才能复用。人工标注人体关键点比画框贵得多一个关节一个关节点一个人物就要点17下所以项目里应减少重复标注工作先做目标检测只标注画面中出现的人体区域再把关键点标注复用进姿态模型训练。标注文件里除了关键点每个人体还会带一个bbox字段定义是[x, y, width, height]左上角坐标加宽高。整个COCO标注json里同时有images、annotations、categories三大段。images管文件名和图片尺寸annotations管每个人体的bbox、关键点、可见性标注categories定义类别。对YOLOv5训练来说它只需要annotations里的bbox转成txt格式关键点信息可以暂时不用。3.3 把COCO标注转成YOLOv5检测标签一个转换脚本与四个边界坑下面这个脚本把COCO person_keypoints_json转成YOLOv5需要的txt标签。YOLOv5检测标签格式是class_id cx cy w h全部归一化cx、cy是中心点坐标w、h是归一化宽高。这个脚本只生成人体检测框不处理关键点因为这条流水线里YOLOv5只做定位。# 把COCO格式的标注转成YOLOv5检测标签只保留人物框 import json import os def coco_to_yolo_txt(coco_path, out_dir): os.makedirs(out_dir, exist_okTrue) with open(coco_path, r, encodingutf-8) as f: coco json.load(f) # COCO images段图片id - 文件名、宽度、高度 images {img[id]: img for img in coco[images]} # 把category里所有名为person的id收集起来不写死为1 person_ids {c[id] for c in coco[categories] if c[name] person} # 按图片id聚合同一张图里的所有人 img_anns {} for ann in coco[annotations]: if ann[category_id] not in person_ids: continue img_anns.setdefault(ann[image_id], []).append(ann) for img_id, anns in img_anns.items(): img_info images[img_id] img_w img_info[width] img_h img_info[height] lines [] for ann in anns: x, y, w, h ann[bbox] # COCO bbox是左上角 宽高 # 过滤太小的框常见阈值为100像素面积 if w * h 100: continue # 转成YOLO的center格式 cx (x w / 2) / img_w cy (y h / 2) / img_h nw w / img_w nh h / img_h if nw 0 or nh 0: continue lines.append(f0 {cx:.6f} {cy:.6f} {nw:.6f} {nh:.6f}) if lines: # 用图片文件名去掉扩展名作为txt名 stem os.path.splitext(img_info[file_name])[0] out_path os.path.join(out_dir, stem .txt) with open(out_path, w, encodingutf-8) as f: f.write(\n.join(lines) \n) if __name__ __main__: coco_to_yolo_txt(annotations/person_keypoints_train.json, labels/train)脚本逻辑很简单但参数含义和边界情况必须说清楚。面积过滤阈值100是经验值如果摄像头离得远人体只有几十个像素这个阈值会把有效样本全部删掉反之如果图片是高清特写100以下的基本是远处行人保留只会引入噪声。实际使用时要先看目标在画面里占多少像素再定阈值。四个常见的边界坑第一COCO里的person类别id不一定是1直接用数字判断会漏掉所有标注。脚本里先读categories再找person_ids就是为了避开这个坑。第二COCO的类别编号是全局的有些子数据集从0开始编有些从1开始盲写死必然翻车。第三bbox格式在CVAT、Label Studio导出时都统一成COCO形式但如果是自定义导出脚本给了xmin、ymin、xmax、ymax转换前要先做一次坐标换算。第四txt文件名必须和图片文件名前缀一致YOLOv5训练时靠前缀匹配图片和标签如果图片是a.jpg标签写成b.txt这个样本等于白标训练过程还不报错属于最难查的隐性故障。4. 跑通单帧流水线YOLOv5检测人体框、OpenPose热图推理与坐标反算4.1 YOLOv5检测人体框推理接口与置信度阈值先用YOLOv5做检测拿到人体框后裁剪再送OpenPose。下面是最小推理代码适用于已经下载好YOLOv5仓库或能访问Hub的环境。import torch # 加载YOLOv5ss是轻量版适合快速验证 model torch.hub.load(ultralytics/yolov5, yolov5s, pretrainedTrue) model.conf 0.45 # 置信度阈值低于0.45的框丢弃 model.classes [0] # 类别过滤只保留person类 model.max_det 20 # 一帧最多20个人框防止密集场景爆内存 # frame是numpy数组BGR格式也支持图片路径 results model(frame) # xyxy格式: x1, y1, x2, y2, conf, cls boxes results.xyxy[0].cpu().numpy()这段代码里的三个参数都是YOLOv5后处理里最常用的调整项。model.conf控制置信度阈值调低会捡回更多漏检但误检也会上升一般场景0.35到0.5之间试。model.classes只保留类别0如果训练时人物类别编号不是0要改成自己的编号。model.max_det把单帧输出框数量限制住防止密集场景下OpenPose被几十个人体框拖垮。刚上手时不建议一开始就调训练超参数先把这两个推理参数摸一遍很多时候漏检问题调整conf就能解决。如果离线环境不能访问Hub常见做法是把YOLOv5仓库clone到本地用attempt_load加载权重。改动只需把torch.hub.load换成下面两行from models.experimental import attempt_load model attempt_load(yolov5s.pt, map_locationcuda)代码逻辑上Confidence和NMS在YOLOv5内部已经封装好我们只需要在外部设置阈值即可。boxes返回的是Tensor转换成numpy数组后方便后续帧循环里逐框裁剪。4.2 OpenPose热图推理让模型只计算人体裁剪区域OpenPose原版C环境太重落地时多数是导出ONNX模型再用onnxruntime推理。下面代码是一份最小可用的45行推理函数输入人体裁剪区域返回17个关键点坐标。import cv2 import numpy as np import onnxruntime as ort # GPU可用时优先CUDA没有就自动退到CPU sess ort.InferenceSession( models/openpose_368x368.onnx, providers[CUDAExecutionProvider, CPUExecutionProvider], ) def openpose_forward(crop): h, w crop.shape[:2] # 模型固定输入368x368按长方形缩放会变形但工程上可接受 inp cv2.resize(crop, (368, 368)) inp inp.astype(np.float32) / 255.0 # ImageNet归一化参数OpenPose沿用这套值不能随意改动 mean np.array([0.485, 0.456, 0.406], dtypenp.float32) std np.array([0.229, 0.224, 0.225], dtypenp.float32) inp (inp - mean) / std # 从HWC转CHW并加一个batch维度 inp inp.transpose(2, 0, 1)[None, ...].astype(np.float32) out sess.run(None, {input: inp})[0] # 输出形状一般是[1, 通道数, 46, 46]368/468模型下采样8倍 maps out[0] # COCO 17点热图一般排在通道最前面具体索引依模型而定 heatmaps maps[:17] coords [] for i in range(17): hm heatmaps[i] # 在热图上找响应最大的位置 idx np.unravel_index(np.argmax(hm), hm.shape) y, x idx[0] * 8, idx[1] * 8 # 热图步长8乘回368坐标 conf float(hm[idx]) # 缩放回裁剪图的原始尺寸 coords.append([x * w / 368.0, y * h / 368.0, conf]) return np.array(coords) # 形状[17,3]这段代码有两点要特别说明。第一输出层通道顺序不同模型有差异COCO 17点OpenPose模型通常把17个热图排在前17通道但有些二次导出模型混入了背景通道拿到模型后先打印out.shape再确认前17个通道对应的关节顺序不要闭眼用。第二如果只取argmax在肩膀这种热图平缓区域坐标会有噪声后面第六章会讲平滑方案这里先把基础推理跑通。OpenPose热图输出分辨率只有46×46等于把368输入下采样8倍。热图上一个像素对应输入图8个像素。对x坐标和y坐标乘以8之后再按裁剪图实际宽高比例乘回去就得到了原坐标。4.3 合并成帧循环裁剪扩边与坐标反算把YOLOv5的检测结果和OpenPose串起来最关键的一步是坐标换算。OpenPose返回的相对坐标是以裁剪图为参考系如果直接输出关节坐标而不加回检测框的偏移量下游拿到的就是一个局部坐标无法对应原画面。def process_frame(frame): # 先用YOLOv5得到全图人体框 results model(frame) boxes results.xyxy[0].cpu().numpy() skeletons [] for *xyxy, conf, cls in boxes: x1, y1, x2, y2 [int(v) for v in xyxy] # 四个方向各扩边40像素给被框线切到的肘腕留余地 x1 max(0, x1 - 40) y1 max(0, y1 - 40) x2 min(frame.shape[1], x2 40) y2 min(frame.shape[0], y2 40) crop frame[y1:y2, x1:x2] # 裁剪区域的骨架坐标 kpts openpose_forward(crop) # 关键步骤把相对坐标加上框的左上角变成全图坐标 kpts[:, 0] x1 kpts[:, 1] y1 skeletons.append(kpts) return skeletons # list每个元素是[17,3]扩边值40是经验参数不是越小越好。检测框如果贴着人体边缘切手肘、手腕会被截掉一部分OpenPose拿到残缺裁剪图后关节坐标会往画面边界偏移。扩40像素让肢体完整进入输入图能明显改善肘部和踝关节的抖动。但扩边太大会引入周围其他人反而把PAF配对搞乱这一步要和第五章的多人干扰坑配合来看。坐标反算的规则是全图坐标裁剪图坐标裁剪框左上角坐标。也就是说在process_frame里对kpts[:, 0]和kpts[:, 1]分别加上x1和y1就完成了从裁剪图坐标系到原图坐标系的转换。这是整条流水线最容易被忽略的环节很多人姿态识别出来的坐标看起来没问题但画在原图上全部错位十有八九是少了这一步。5. 避坑与常见问题排查这套流水线最容易翻车的五个环节5.1 现象同一个动作肘关节坐标在两个位置之间跳来跳去原因OpenPose热图在肩肘部位常常有不止一个局部极大值。当YOLOv5给出的裁剪框分辨率不够高裁剪后的图像变小热图分辨率进一步降低肘关节的响应区域就变得平坦argmax取到的位置就来回摆动。这不是模型坏了是输入信息量不足。解决先把YOLOv5的检测框扩边让更多躯干信息进入OpenPose再把裁剪图按比例放大到至少368×368的输入。只调姿态模型的平滑参数治标不治本扩边和放大裁剪图才是止血方案。5.2 现象密集人群中YOLOv5漏检导致OpenPose拿不到输入原因yolov5训练自己的数据集时如果标注里把远景小人、遮挡大半的人全部忽略模型在推理时就学不会识别这类目标。再加上NMS在重叠人脸框里把低分框去掉了漏检率会直线上升。解决先在conf阈值上往下探0.45降到0.3看召回率变化。如果仍漏就需要补充密集人群的裁剪样本做微调同时调整NMS参数降低重叠抑制强度YOLOv5里对应就是nms_thres这一项。千万不要跳过检测环节直接把整帧送OpenPose来抢救那样虽然不漏人但显存和时间开销大到无法接受。5.3 现象一张仅有一个人的图显存却占掉4GB以上原因OpenPose不是直接回归31个坐标而是生成19组PAF加17个热图中间特征图的通道数很大。这些中间结果全部驻留在显存里计算量和输入分辨率几乎成正比输入越大显存涨得越快。很多人以为模型只有几百MB显存不会高实际运行起来才发现整帧推理显存占用非常惊人。解决先确认OpenPose输入是人形裁剪区域而不是整帧。整帧1080P输入相当于同时计算几百个“伪人体”热图。把输入限制在裁剪框范围显存能立刻降下来。如果还要压再把裁剪图缩小到256×256输入精度损失视觉上不明显速度提升一倍左右。5.4 现象裁剪框里混进第二个人骨架线交叉成一只怪物原因YOLOv5框是水平矩形两个人靠得近时扩边之后的框会把旁边人的手臂、肩膀一起包进来。OpenPose的PAF在单人区域内工作正常但一旦出现第二个人的肢体方向匹配会产生交叉配对输出的17个点里混了两套身体系统。解决先减小扩边值40像素改成20像素减少混入概率。如果遮挡仍然严重用目标检测框的中心点做一次裁剪框偏移优先把当前主体放在裁剪图中央。这种做法会牺牲一点边缘关节信息但得到的骨架逻辑上更合理。还有一条路是在YOLOv5检测阶段单独训练一个“密集人群头部/上身”类别把多个人拆成独立实例不过这套方案要额外标注数据成本和收益要算清楚。5.5 现象在RK3568或树莓派4b上部署后INT8量化模型姿态点明显偏移原因基于浮点模型做INT8量化时数据校准不充分。量化的scale系数是按YOLOv5检测任务特性算出来的直接拿到OpenPose这种体重建密度输出的模型上关键点热图的峰值被压缩坐标会整体偏移。端侧设备做姿态模型时经常出现这种现象因为大家都默认一个量化流程能通吃所有模型。解决把YOLOv5和OpenPose分开处理。YOLOv5量化后精度影响不大的部分可以走INT8OpenPose用FP16或者保持FP32。量化时准备专门的校准集内容要覆盖实际摄像头场景校准集分布越接近线上数据量化掉点越少。如果还是飘EMCC里把带热图的层从量化白名单里移除这些层的开销大一点但保住了骨架质量。6. 进阶验证与调参骨架坐标稳定到能直接算关节角度6.1 用中值滑动窗口平滑51维坐标姿态模型的原始坐标在单帧里看起来没问题但连续播放时抖动明显。我常用的平滑方法是滑动窗口中位数对每个关节点的x、y、置信度分别取中值。均值会被单帧异常点拉走中位数能把明显离群的点直接滤掉。窗口大小选5帧太小效果不足太大延迟明显实时交互场景会感觉骨架“粘住”。def smooth_skeleton(kp_seq, window5): # kp_seq是最近window帧的骨架数组形状为[window, 17, 3] import numpy as np arr np.array(kp_seq[-window:], dtypenp.float32) # 中位数比均值更能扛单帧抖动 return np.median(arr, axis0) # 输出[17,3]这个平滑函数放在process_frame外层调用保存最近5帧的骨架结果输出中位数即可。注意处理视频前几帧窗口不足时直接返回当前帧避免坐标延迟感。6.2 可量化的三个验证指标验证项指标合理参考值单帧关节稳定性同帧重复推理坐标差小于3像素下游角度稳定性肘关节角度标准差小于5度漏检率有人的帧中无检测框的帧占比小于5%项目验收时先跑这三个数字别只看可视化视频。可视化的画面对坐标偏移不敏感肉眼很难分辨几像素的误差一旦下游要算关节角度几像素误差就会被放大成十几度的波动。角度稳定性这个指标最容易暴露坐标换算的问题如果角度标准差过大回头检查第4章的坐标反算多半是偏移没加回。6.3 一次调框架的经历检测框质量决定骨架上限这套方案跑了几轮之后我发现每次姿态效果提升最大的节点都是改检测性能收益最大的阶段是调扩边最重要的教训是骨架模型能发挥得好坏很大程度取决于检测框质量不如先把YOLOv5的置信度和输入尺寸调到合理范围再去折腾OpenPose的输入尺寸、后处理和置信度阈值。希望这篇从原理到参数的设计方法对你有切实帮助。本文还有配套的精品资源点击获取
返回列表