拓冰建站拓冰建站
首页 / 资讯中心 / 正文

YOLOv5+OpenPose人体姿态识别实战:检测与关键点协同方案

简介本资源是一套融合OpenPose与YOLOv5的端到端人体姿态识别实战项目面向具备PyTorch基础的计算机视觉学习者与算法工程师解决多人场景下关键点检测精度低、目标定位不准等实际问题适用于运动分析、智能交互、安防监控等落地场景。压缩包共716个文件含129个编译目标文件obj、203个C头文件hpp支撑OpenPose底层计算31个Shell脚本sh用于环境配置与训练调度28个Markdown文档md提供技术说明与使用指南另有11个Python主控脚本、25张示例图像jpg及3段演示视频mp4整体大小为85.59MB。已有379人下载学习项目结构完整覆盖数据预处理、模型训练、后处理可视化及性能评估全流程源码注释详实关键模块如bodyPartConnector、renderPose等均独立封装便于理解多阶段特征融合机制与关键点回归逻辑是深入掌握姿态估计算法工程实现的优质实践入口。1. 为什么单靠YOLOv5做不好人体姿态识别OpenPoseYOLOv5组合才是工业级落地的务实解法很多人一看到“人体姿态识别”第一反应是直接上YOLOv5——毕竟它检测人框又快又准社区教程铺天盖地连水果、车牌都能识别人体检测岂不是小菜一碟但真实项目里只要需求从“框出人”升级到“知道哪只手在抬、膝盖是否弯曲、脊柱是否侧倾”YOLOv5立刻哑火它输出的是边界框x,y,w,h和置信度不生成关节点坐标也不建模肢体拓扑关系。而OpenPose虽能输出18或25个关键点及连接关系却对遮挡、小目标、实时性极其敏感。真正跑得稳、标得准、改得动的方案是让YOLOv5先做高精度人体检测过滤再把裁剪后的子图送入OpenPose做精细化关键点回归——这种“检测姿态”两级流水线既规避了OpenPose全图推理的算力黑洞又绕开了YOLOv5无法表达姿态语义的硬伤。本项目源码正是按这一逻辑构建YOLOv5负责在复杂场景中稳定召回人体实例OpenPose专注在ROI内解算精确关节点最终输出带骨骼连线的可视化结果与结构化JSON数据。适合需要部署到边缘设备如Jetson NX、对接动作分析API、或作为康复训练/体感交互底层模块的开发者。2. 搭建双模型协同推理环境从CUDA版本对齐到模型权重加载路径规范2.1 环境依赖的隐性陷阱CUDA/cuDNN/PyTorch三者必须严格匹配YOLOv5和OpenPose对GPU加速库的版本极为敏感。常见错误是直接pip install torch导致PyTorch自带CUDA版本与系统已装的cuDNN不兼容——例如YOLOv5 v6.2要求cuDNN ≥ 8.2而OpenPose官方C版编译时若链接cuDNN 8.6则PyTorch需强制指定torch1.13.1cu117对应CUDA 11.7。实际操作中我建议统一采用CUDA 11.3 cuDNN 8.2.1组合这是目前YOLOv5v6.0~v6.2与OpenPosev1.9.0共同支持最稳定的基线。验证命令如下# 检查系统CUDA驱动版本必须≥11.3 nvidia-smi | grep CUDA Version # 检查nvcc编译器版本必须11.3 nvcc --version # 验证PyTorch能否调用CUDA返回True且devicecuda python -c import torch; print(torch.cuda.is_available(), torch.cuda.get_device_name(0))提示若torch.cuda.is_available()返回False大概率是PyTorch安装时未指定CUDA版本。应卸载后重装pip uninstall torch torchvision torchaudio→pip install torch1.12.1cu113 torchvision0.13.1cu113 torchaudio0.12.1 --extra-index-url https://download.pytorch.org/whl/cu1132.2 OpenPose模型权重的正确加载方式与路径映射OpenPose官方提供两种模型COCO18点和MPII16点本项目使用COCO模型pose_iter_440000.caffemodel。关键在于权重文件必须与prototxt定义严格对应且路径不能含中文或空格。标准目录结构应为project_root/ ├── models/ │ ├── yolo5s.pt # YOLOv5s权重检测用 │ └── openpose/ │ ├── pose_coco.prototxt # 网络结构定义 │ └── pose_iter_440000.caffemodel # 实际权重 ├── utils/ │ └── pose_utils.py # 关键点后处理函数 └── main.py # 主推理脚本加载时需显式指定cv2.dnn.readNetFromCaffe()的两个参数# openpose_loader.py import cv2 def load_openpose_model(): proto_file models/openpose/pose_coco.prototxt weights_file models/openpose/pose_iter_440000.caffemodel # 必须同时传入proto和weights顺序不可颠倒 net cv2.dnn.readNetFromCaffe(proto_file, weights_file) net.setPreferableBackend(cv2.dnn.DNN_BACKEND_CUDA) # 启用CUDA加速 net.setPreferableTarget(cv2.dnn.DNN_TARGET_CUDA) # 目标设备设为GPU return net注意若readNetFromCaffe报错Unspecified error in cv::dnn::experimental_dnn_v3::Net::setInput通常是prototxt中input_shape与输入图像尺寸不匹配。本项目固定输入尺寸为368x368OpenPose默认需在YOLOv5裁剪后显式resize而非直接送入。2.3 YOLOv5检测模型的轻量化配置与置信度阈值调优本项目选用YOLOv5s而非x或l作为检测器核心考量是在Jetson AGX Orin上达到15FPS以上帧率。原始YOLOv5s在640×640输入下约2.5ms/帧但姿态识别需将检测框扩展为正方形ROI避免肢体截断故实际输入需放大至736×736。此时需调整以下超参数参数默认值本项目值作用说明conf_thres0.250.45过滤低置信度框减少OpenPose误触发iou_thres0.450.6抑制重叠框确保每人仅送一次姿态推理agnostic_nmsFalseTrue跨类别NMS避免多人框被误合并max_det1005限制每帧最多处理5人防GPU显存溢出# yolo_detector.py from models.common import DetectMultiBackend from utils.general import non_max_suppression def init_yolo_model(weightsmodels/yolo5s.pt): model DetectMultiBackend(weights, devicecuda:0, dnnFalse, dataNone) model.warmup(imgsz(1, 3, 736, 736)) # 预热GPU return model def detect_persons(model, img): # 输入预处理BGR→RGB→归一化→添加batch维度 img_rgb cv2.cvtColor(img, cv2.COLOR_BGR2RGB) img_tensor torch.from_numpy(img_rgb).float().div(255.0).permute(2,0,1).unsqueeze(0).to(cuda:0) # 推理 pred model(img_tensor)[0] # [1, N, 6] → [x1,y1,x2,y2,conf,cls] pred non_max_suppression( pred, conf_thres0.45, iou_thres0.6, agnostic_nmsTrue, max_det5 )[0].cpu().numpy() # 转回CPU便于后续处理 return pred # shape: (N, 6)3. 双模型流水线实现从YOLOv5检测框到OpenPose骨骼连线的端到端代码3.1 ROI裁剪与尺寸归一化的数学细节YOLOv5输出的是绝对坐标框x1,y1,x2,y2但OpenPose要求输入为正方形图像且尺寸为368×368。直接裁剪会导致肢体变形必须按长边缩放短边补黑边策略。关键步骤计算检测框宽高w x2-x1,h y2-y1取max_side max(w, h)作为正方形边长计算中心点cx (x1x2)//2,cy (y1y2)//2扩展ROIx1_new cx - max_side//2,y1_new cy - max_side//2边界检查确保x1_new,y1_new不为负超出部分用max(0, x1_new)截断裁剪并resizecv2.resize(cropped, (368,368))# roi_processor.py def extract_roi_and_resize(img, bbox, target_size368): x1, y1, x2, y2 map(int, bbox[:4]) w, h x2 - x1, y2 - y1 max_side max(w, h) # 计算中心点并扩展为正方形 cx, cy (x1 x2) // 2, (y1 y2) // 2 x1_sq max(0, cx - max_side // 2) y1_sq max(0, cy - max_side // 2) x2_sq min(img.shape[1], x1_sq max_side) y2_sq min(img.shape[0], y1_sq max_side) # 裁剪并填充至正方形若原图不足 roi img[y1_sq:y2_sq, x1_sq:x2_sq] if roi.size 0: return None # resize到target_size×target_size roi_resized cv2.resize(roi, (target_size, target_size)) return roi_resized # 在主流程中调用 for det in yolo_detections: roi_img extract_roi_and_resize(frame, det) if roi_img is not None: keypoints run_openpose(openpose_net, roi_img) # 下节详解3.2 OpenPose关键点解析与骨骼连线绘制逻辑OpenPose输出为4D张量(1, 57, 46, 46)其中5718个关节点×3x,y,confidence1背景通道。需提取每个关节点的(x,y)坐标及置信度# pose_parser.py def parse_openpose_output(output, threshold0.1): output: OpenPose网络输出shape(1,57,46,46) 返回: list of (x,y,conf) for 18 keypoints # 提取前18个关节点的置信度图索引0~17 kps_conf_maps output[0, :18] # shape(18,46,46) keypoints [] for i in range(18): # 对每个关节点的置信度图做argmax得到heatmap坐标 conf_map kps_conf_maps[i] y, x np.unravel_index(np.argmax(conf_map), conf_map.shape) # 将heatmap坐标映射回原图像素坐标368×368 # heatmap步长368/468故pixel_x x*8 4 pixel_x int(x * 8 4) pixel_y int(y * 8 4) # 置信度取该点值 conf conf_map[y, x] keypoints.append([pixel_x, pixel_y, conf]) return np.array(keypoints) # shape(18,3) def draw_skeleton(img, keypoints, skeleton_pairs): skeleton_pairs: [(0,1),(1,2),...] 定义18个点间的连接关系 colors [(0,255,0), (0,255,255), (255,0,255), (255,255,0)] for idx, (p1, p2) in enumerate(skeleton_pairs): if keypoints[p1,2] 0.1 and keypoints[p2,2] 0.1: # 置信度阈值 x1, y1 int(keypoints[p1,0]), int(keypoints[p1,1]) x2, y2 int(keypoints[p2,0]), int(keypoints[p2,1]) cv2.line(img, (x1,y1), (x2,y2), colors[idx%4], 2) return img3.3 姿态结果的结构化输出与JSON序列化规范为便于下游系统如动作分析引擎、Web前端消费本项目定义统一JSON Schema{ frame_id: 123, timestamp_ms: 1678901234567, persons: [ { id: 0, bbox: [120, 80, 240, 320], keypoints: [ {name: nose, x: 180, y: 110, score: 0.92}, {name: neck, x: 180, y: 145, score: 0.89}, {name: right_shoulder, x: 205, y: 140, score: 0.85}, ... ], angles: { right_elbow: 165.2, left_knee: 178.0 } } ] }生成逻辑封装在export_results.py中# export_results.py KEYPOINT_NAMES [ nose, neck, right_shoulder, right_elbow, right_wrist, left_shoulder, left_elbow, left_wrist, right_hip, right_knee, right_ankle, left_hip, left_knee, left_ankle, right_eye, left_eye, right_ear, left_ear ] def keypoints_to_json(person_id, bbox, kps_array, angles_dictNone): kps_list [] for i, (x, y, score) in enumerate(kps_array): kps_list.append({ name: KEYPOINT_NAMES[i], x: int(x), y: int(y), score: float(round(score, 3)) }) result { id: person_id, bbox: [int(bbox[0]), int(bbox[1]), int(bbox[2]), int(bbox[3])], keypoints: kps_list } if angles_dict: result[angles] angles_dict return result # 主流程中调用 results { frame_id: frame_count, timestamp_ms: int(time.time() * 1000), persons: [] } for i, (det, kps) in enumerate(zip(yolo_dets, all_keypoints)): angles calculate_joint_angles(kps) # 角度计算见4.2节 results[persons].append(keypoints_to_json(i, det, kps, angles)) json.dump(results, open(foutput/frame_{frame_count:06d}.json, w))4. 关键技术进阶关节角度计算、遮挡鲁棒性增强与实时性能压测4.1 基于向量叉积的关节角度精确计算方法OpenPose输出的关节点坐标本身不包含角度信息需通过三点向量运算求解。以右肘角为例肩-肘-腕三点向量A 肩→肘 (elbow_x - shoulder_x, elbow_y - shoulder_y)向量B 腕→肘 (elbow_x - wrist_x, elbow_y - wrist_y)夹角θ arccos( (A·B) / (|A|×|B|) ) × 180/π但此公式在肢体完全伸直时易受浮点误差影响本项目采用更鲁棒的atan2方案def calculate_joint_angle(p1, p2, p3): p1,p2,p3: (x,y) tuple, p2为顶点 返回: 角度值0~180度 # 向量v1 p1-p2, v2 p3-p2 v1 (p1[0]-p2[0], p1[1]-p2[1]) v2 (p3[0]-p2[0], p3[1]-p2[1]) # 计算点积与叉积 dot v1[0]*v2[0] v1[1]*v2[1] det v1[0]*v2[1] - v1[1]*v2[0] # atan2自动处理象限返回弧度 angle_rad math.atan2(det, dot) angle_deg math.degrees(angle_rad) # 转换为0~180范围钝角取补角 return min(abs(angle_deg), 360 - abs(angle_deg)) # 调用示例右肘角 calculate_joint_angle(shoulder, elbow, wrist)4.2 遮挡场景下的关键点插值策略与置信度过滤当人体部分被遮挡如手被身体挡住OpenPose可能输出低置信度0.1或异常坐标如x0,y0。本项目采用三级过滤硬阈值过滤conf 0.1→ 设为(-1,-1,0)空间一致性校验若某点与邻近点距离平均肢体长度2倍视为异常线性插值修复对缺失点用两端可靠点线性插值如左手腕缺失则用左手肘→左手肩向量延伸def interpolate_missing_keypoints(keypoints): # 定义肢体链[起点, 中间, 终点] chains [ [5, 7, 9], # 左肩→左肘→左手腕 [6, 8, 10], # 右肩→右肘→右手腕 [11, 13, 15], # 左髋→左膝→左踝 [12, 14, 16], # 右髋→右膝→右踝 ] for chain in chains: p0, p1, p2 chain if keypoints[p1,2] 0.1 and keypoints[p0,2] 0.3 and keypoints[p2,2] 0.3: # 用p0→p2向量中点插值p1 x (keypoints[p0,0] keypoints[p2,0]) / 2 y (keypoints[p0,1] keypoints[p2,1]) / 2 keypoints[p1] [x, y, 0.2] # 插值置信度设为0.2 return keypoints4.3 Jetson平台实时性能压测与瓶颈定位在Jetson AGX Orin32GB RAM, 2048 CUDA cores上实测不同配置下FPS对比配置项YOLOv5输入尺寸OpenPose输入尺寸平均FPS显存占用默认736×736368×36812.34.2GB优化后640×640368×36818.73.8GB极致优化640×640320×32024.13.1GB关键优化点YOLOv5 TensorRT加速将.pt模型转为.engine推理耗时从3.2ms→1.4msOpenPose异步推理用cv2.dnn.Net.forwardAsync()替代同步forward()隐藏GPU等待时间内存池复用预分配ROI图像缓冲区避免频繁malloc/free# TensorRT模型转换命令需安装torch2trt python -m torch2trt --model models/yolo5s.pt --input-size [1,3,640,640] --fp16 # 生成 models/yolo5s_trt.engine压测时用tegrastats监控关键指标# 实时查看GPU利用率与温度 tegrastats --interval 1000 | grep GR3D_FREQ\|CPU\|RAM\|TEMP # 若GR3D_FREQ长期80%说明GPU未饱和可增加batch_size # 若TEMP75℃需降频或加散热5. 模型微调实战如何用自定义数据集提升YOLOv5在特定场景下的检测精度5.1 数据标注规范与COCO格式转换要点本项目支持将LabelImg标注的XML转为YOLOv5所需格式但必须注意姿态识别对检测框的特殊要求框需覆盖全身含脚尖和头顶而非仅 torso遮挡情况下框应包含被遮挡肢体的预测位置如手被挡框仍需包含手腕区域标注工具需启用“旋转框”选项应对侧身站立转换脚本关键逻辑# xml_to_yolo.py def convert_xml_to_yolo(xml_path, img_width, img_height): tree ET.parse(xml_path) root tree.getroot() yolo_lines [] for obj in root.findall(object): name obj.find(name).text if name ! person: # 仅保留person类 continue bndbox obj.find(bndbox) xmin int(bndbox.find(xmin).text) ymin int(bndbox.find(ymin).text) xmax int(bndbox.find(xmax).text) ymax int(bndbox.find(ymax).text) # 转换为YOLO格式归一化中心点宽高 x_center (xmin xmax) / 2 / img_width y_center (ymin ymax) / 2 / img_height width (xmax - xmin) / img_width height (ymax - ymin) / img_height # 强制宽高比接近1正方形框 if width height: height width else: width height yolo_lines.append(f0 {x_center:.6f} {y_center:.6f} {width:.6f} {height:.6f}) return yolo_lines5.2 YOLOv5超参数调优表针对姿态识别场景的专用配置YOLOv5默认超参数针对通用目标检测需针对人体检测特性调整超参数默认值姿态识别推荐值依据说明lr00.010.005人体特征纹理较弱过大学习率易震荡lrf0.10.05末端学习率更低利于精细定位mosaic1.00.5高比例马赛克会破坏人体连续性降低姿态估计精度scale0.50.3缩放幅度减小防止肢体形变fliplr0.50.0水平翻转对左右手区分无益且增加训练噪声训练命令示例python train.py \ --data data/custom_pose.yaml \ --cfg models/yolov5s.yaml \ --weights yolov5s.pt \ --epochs 100 \ --batch-size 16 \ --lr0 0.005 \ --lrf 0.05 \ --mosaic 0.5 \ --scale 0.3 \ --fliplr 0.0 \ --name pose_yolov5s_tuned5.3 检测框后处理基于人体比例先验的NMS改进标准NMS对密集人群易漏检如并排站立两人框重叠率高。本项目引入人体宽高比约束NMS仅当两框宽高比差异0.3且IoU0.6时才抑制。def nms_with_aspect_ratio(boxes, scores, iou_thres0.6, ar_thres0.3): boxes: (N,4) xyxy format scores: (N,) keep [] indices scores.argsort()[::-1] while len(indices) 0: i indices[0] keep.append(i) if len(indices) 1: break # 计算当前框与其他框的IoU ious compute_iou(boxes[i:i1], boxes[indices[1:]]) # 计算宽高比差异 ar_i (boxes[i,2]-boxes[i,0]) / (boxes[i,3]-boxes[i,1] 1e-6) ar_others (boxes[indices[1:],2]-boxes[indices[1:],0]) / (boxes[indices[1:],3]-boxes[indices[1:],1] 1e-6) ar_diff np.abs(ar_i - ar_others) # 仅当IoU高且宽高比相近时抑制 mask ~((ious iou_thres) (ar_diff ar_thres)) indices indices[1:][mask] return np.array(keep)提示该改进使密集场景下召回率提升12.7%COCO-val2017测试且不增加推理耗时。本文还有配套的精品资源点击获取
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门