ARTICLE DETAIL

资讯详情

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

YOLOv5人体检测与OpenPose姿态估计的摔倒检测实现方案

YOLOv5人体检测与OpenPose姿态估计的摔倒检测实现方案 简介一套结合YOLOv5人体检测与OpenPose姿态估计实现摔倒检测的完整项目包面向具备Python与深度学习基础、希望综合运用目标检测和姿态估计技术的开发者和学生可直接用于算法验证、课程设计或横向课题预研解决单模型难以完成跌倒判定的实际需求。压缩包共183个文件约40.24MB其中包含75张jpg/jpeg图像样本、39个Python脚本、17个YAML配置、2个pt模型和2个jit模型另有pyc缓存、txt标签、xml标注及dockerfile等辅助文件覆盖数据、训练、推理与部署的完整结构。目前已有1063人学习下载。项目中runOpenpose.py可提取人体关键点图并保存至指定目录为后续jit模型训练积累数据detect.py先利用YOLO检测人体再按坐标将人像裁切送入OpenPose做姿态判断代码已加入框宽高比和关键点范围限制方便修改或扩展到摔倒、举手等动作识别同时提供action_detect/train.py分类训练脚本与配套权重目录组织清晰适合直接复现和二次开发。1. 摔倒检测为什么绕不开“人体框姿态点”双路方案现场装过监控的人都有体会摔倒检测难的不是“画面里有没有人”而是“怎么判断这个人正在摔”。yolov5人体检测输出的是一个矩形框它能告诉你目标在哪、有多大但框本身不会说话——站着、弯腰、坐在地上框的宽高比可能有变化却永远无法表达“膝盖弯曲、重心急坠、躯干倾斜”这些摔倒动作的实质。openpose姿态检测恰好补上这一块给出鼻子、肩膀、髋部、膝盖等关键点的像素坐标摔倒与否就变成了对一串坐标的几何与时序判断。把二者串起来先定位再判姿态才是工程上最常见的“yolov5人体检测openpose姿态检测实现摔倒检测”双模型方案。这个方案适合监控摄像头下的老人监护、工地安全帽佩戴者跌倒告警、独居场景看护也能直接用作毕设项目或产品预研。接下来按一线落地顺序讲怎么选型、怎么搭环境、怎么调参、坑在哪里。2. 选型与整体流程YOLOv5 负责定位OpenPose 负责姿态判断逻辑在最后2.1 为什么是双模型而不是一个模型很多人一开始会想用一个分类网络直接把画面判成“摔倒/正常”不就行了短时间看确实能跑但这类端到端方案对相机视角极其敏感。同一个动作从侧面看是摔倒从正上方看只是“人蹲着”光照一变、视角一换数据标注全部作废。纯目标检测同样不够只靠人体框的宽高比判断摔倒人躺在地上和弯腰系鞋带的框形接近误报压不住。方案能告诉我们什么主要瓶颈纯 YOLOv5 人体检测人的位置、数量、框尺寸无法表达关节姿势和动作速度纯 OpenPose 姿态检测关节坐标、姿态轮廓全图跑推理慢缺少目标先验YOLOv5 OpenPose 双路先锁定目标再分析姿态时序变化计算量翻倍需要裁剪和降载我一般会把双模型当“粗筛 精判”两级来用YOLOv5 先把 person 框出来OpenPose 只处理框内区域而不是全图跑一遍关键点。这样既限制 OpenPose 的输入尺寸又避免它在远处小人身上浪费计算——这也是这套方案能在普通设备上跑起来的关键。2.2 整条链路的数据流与目录组织完整数据流是这样的视频帧进来先送 YOLOv5 推理拿到 person 类别的人体框坐标按框裁剪出目标图像区域再送 OpenPose 提取关键点拿到关键点后计算三个核心特征——重心离地高度、躯干与竖直方向的夹角、关键点在连续几帧内的移动速度最后用状态机判断是否触发摔倒报警。这里有个非常容易忽略的设计OpenPose 输出的是全图坐标但如果你先裁剪了坐标原点就变成了裁剪图的左上角。所以在特征计算之前必须把裁剪坐标映射回原图坐标或者干脆把关键点归一化到 0-1 区间否则不同尺寸的检测框会导致同一姿势算出完全不同的高度值。如果你手里拿到的是项目源码 zip打开后通常按这个顺序读先看 weights 或 runs 目录有没有模型文件再看 config 或 yamlYOLOv5 的数据配置和 OpenPose 的输入尺度一般在这里接着找负责摔倒判断的核心 py 文件——它往往不叫 fall而叫 postprocess、utils 或 judge最后才是 demo 或 main 入口把模型加载、循环推理、判断结果串起来。2.3 最小环境准备conda、Python 版本与依赖顺序这套方案最怕的就是环境和依赖冲突所以第一步必须建独立环境。以下是我在 Ubuntu 20.04 / 22.04 上跑通的最小命令conda create -n fall_detect python3.8 -y conda activate fall_detect # 先装 PyTorchCPU 版和 GPU 版二选一 # GPU 版按官方命令装注意 CUDA 版本要和驱动匹配 pip install torch1.12.1 torchvision0.13.1 --index-url https://download.pytorch.org/whl/cu113 pip install opencv-python numpy scipy pyyaml tqdm requests参数说明Python 我用 3.8 而不是 3.10因为 OpenPose 对高版本 Python 的兼容性差虽然它的 Python 接口是基于 C 编译的但很多依赖对 3.10 以上支持的坑会让你浪费一整天。PyTorch 版本不需要追新能跑 YOLOv5 和 OpenPose 的推理就可以。另装一个专门给 OpenPose 用的环境是常见做法两个环境之间通过 JSON 文件或 Socket 交换数据互不污染。提示先创建一个干净的 conda 环境装完 YOLOv5 推理再把 OpenPose 编译进去。如果两个项目塞在同一个环境里protobuf 版本冲突会让你怀疑人生这个在第 5 章细说。3. 用 YOLOv5 把人从画面里稳准地找出来模型选择、数据准备与推理3.1 用预训练权重先跑通 person 检测OpenPose 姿态判断依赖的人体框质量决定了下游误差的上限。框偏了裁剪出来的区域里人就不完整关键点置信度会掉框漏了再好的决策逻辑也白搭。所以 YOLOv5 这一步我建议先用官方 yolov5s.pt 把 COCO 预训练模型跑通COCO 80 类里的第 0 类就是 person。import cv2 import torch # 本地没有 yolov5 源码时直接用 torch.hub 拉取并加载预训练模型 model torch.hub.load(ultralytics/yolov5, yolov5s, pretrainedTrue) model.conf 0.4 # 置信度阈值 model.iou 0.45 # NMS 的 IoU 阈值 model.classes [0] # 只检测 person对应 COCO 类别 0 model.max_det 20 # 单帧最多保留 20 个目标 frame cv2.imread(demo.jpg) frame_rgb cv2.cvtColor(frame, cv2.COLOR_BGR2RGB) results model(frame_rgb, size640) boxes results.xyxy[0].cpu().numpy() # [x1, y1, x2, y2, conf, class] for box in boxes: x1, y1, x2, y2, conf box[:5].astype(float) w, h x2 - x1, y2 - y1 # 过滤掉过小的人体框避免远处小人浪费 OpenPose 计算 if w 30 or h 60: continue cv2.rectangle(frame, (int(x1), int(y1)), (int(x2), int(y2)), (0, 255, 0), 2)逻辑说明model.conf和model.iou是 YOLOv5 推理时直接生效的后处理参数改它们比自己在 NMS 里调参更省事。size640是输入网络的边长YOLOv5 会按比例缩放并填充灰边。过滤小框是实战里几乎必然要加的一步——预训练模型对远处小目标会有大量置信度不高的框让这些框进到 OpenPose 里不仅算得慢而且关键点置信度低会给后面的摔倒判断带来噪声。3.2 训练自己的场景数据标注格式与训练命令如果你要部署的相机视角固定比如养老院走廊顶装摄像头单靠预训练模型能凑合但和目标场景里的真实角度、距离、遮挡情况多少有偏差。想让系统更稳就得用自己的数据微调。YOLOv5 训练数据要转成它的标准格式每张图片对应一个同名的 txt 文件放在 labels 目录下每行是class x_center y_center width height坐标归一到 0-1。标注工具用 LabelImg 或 CVAT 都行只标 person 这一类比标多类省事很多。我一般按 8:1:1 分成 train / val / test每个视频抽帧时不要连续抽间隔 5-10 帧抽一张避免相邻帧太像导致过拟合。python train.py --data dataset.yaml --weights yolov5s.pt \ --epochs 50 --batch-size 16 --img 640 --device 0 \ --project runs/fall_train --name person_v1参数说明dataset.yaml里写三样东西——train 和 val 的图片路径、类别数 nc1、类别名 names[person]。--weights yolov5s.pt表示在 COCO 预训练权重基础上微调比从头训练收敛快得多epochs 设 50 基本够用如果验证集 mAP 还在涨可以继续batch-size 根据显存调整6G 显存跑 16 没问题再大容易 OOM。训练完成后best.pt 就是微调后的模型后续推理用它替换预训练权重。3.3 人体框后处理NMS 后的位置匹配与单人场景YOLOv5 自带的 NMS 已经处理了同一人多框的问题但多人场景下还有个容易被忽略的问题摔倒判断是“按人”来的不是“按帧”来的。一帧里有三个人其中一个人摔了你不能因为画面里还有两个站姿正常就把整帧判成正常。常见做法是给每个检测框一个临时 ID用 IoU 把当前帧的框和上一帧的框做匹配匹配上就继承 ID匹配不上就新建 ID。更省事的方案是直接接 ByteTrack 或 DeepSORT但那些库对单人摔倒检测来说有点重。我自己在实验阶段会先按中心点距离匹配因为人体框在相邻帧的移动量通常远小于框之间的间距。def match_boxes(prev_boxes, cur_boxes, dist_thresh100): matched [] for cur in cur_boxes: best_id, best_dist -1, float(inf) for prev in prev_boxes: d ((cur[0] - prev[0]) ** 2 (cur[1] - prev[1]) ** 2) ** 0.5 if d best_dist: best_dist, best_id d, prev[4] if best_dist dist_thresh: matched.append([best_id, cur]) else: matched.append([-1, cur]) # 新目标 return matched逻辑说明prev_boxes里存的是[center_x, center_y, w, h, id]cur_boxes是当前帧检测框的中心坐标。距离阈值dist_thresh取 100 像素是我在 1080p 画面下的经验值具体要看相机帧率和人走动的速度帧率越低阈值要越大。这里只做相邻两帧的匹配不涉及长时跟踪够摔倒判断用就行。4. 用 OpenPose 提取关键点姿态坐标怎么来摔倒判断规则怎么写4.1 OpenPose 的推理方式选择与实际调用OpenPose 官方项目提供 C 源码和 Python 接口但如果你只想跑摔倒检测没有必要从头编译。一个常见做法是直接用官方提供的openpose.bin命令行推理把每一帧的检测结果输出成 JSON再由自己的 Python 脚本读取关键点坐标。这样绕开 pybind 编译把“姿态估计”和“摔倒判断”完全解耦排错会容易得多。./build/examples/openpose/openpose.bin --image_dir input_frames/ \ --write_json output_keypoints/ \ --model_pose BODY_25 \ --net_resolution 320x176 \ --number_people_max 5 --display 0 --render_pose 0参数说明--model_pose BODY_25是官方推荐的模型输出 25 个关节点覆盖躯干和腿的细节比 COCO 的 17 点更适合摔倒这种下肢大幅动作--net_resolution控制进入网络的分辨率320x176 是我在 CPU 机器上的折中选择越低越快但精度越差--number_people_max 5限制最多检测的人数防止多人时内存暴涨--render_pose 0关闭可视化渲染因为监控场景我们只关心坐标不关心画出来的骨架图。逐帧跑一张图输出一个 JSON每个 JSON 里的people数组就是姿态数据。4.2 BODY_25 关键点编号与摔倒特征提取BODY_25 的关节点编号需要记熟0 是鼻子1 是脖子2 和 5 是左右肩8 和 11 是左右髋9 和 12 是左右膝10 和 13 是左右踝。拿到这些点的坐标后摔倒判断从不看“鼻子在哪”开始而是看几个几何特征组成的向量重心高度取脖子和两髋的平均 y 坐标。正常站立时这个点在画面垂直方向的中上部摔倒躺地时它会大幅下移。坐标系是图像坐标系y 向下为正所以这个值越小代表位置越高。躯干角度脖子到两髋中点的连线与竖直方向的夹角。站立时接近 0 度摔倒时可能接近 60-90 度。重心垂直速度重心高度在连续若干帧内的变化率。摔倒的特点是短时间5-10 帧内重心急速下坠这是区分“慢慢坐下”和“摔倒”的核心信号。这些特征全部要归一化。我的做法是每个坐标除以图像高度y_norm y / image_height。否则同一姿势在 720p 和 4K 画面里算出来的特征值完全不同阈值没法通用。4.3 摔倒判断代码阈值、时序与消抖这里给出一个能直接跑的判断函数输入是 OpenPose 输出的关键点数组输出这一帧的摔倒置信度import numpy as np def parse_keypoints(json_data, image_h): 把 OpenPose 的 BODY_25 输出转成 dict坐标为归一化后的值 pts json_data[people][0][pose_keypoints_2d] # 长度 75 body {} for i in range(25): x, y, conf pts[i * 3], pts[i * 3 1], pts[i * 3 2] body[i] (x / image_h, y / image_h, conf) # 归一化到 0-1 return body def fall_feature(body, prev_center_yNone): # 关键点 1 是脖子8/11 是左右髋 neck_y body[1][1] hip_y (body[8][1] body[11][1]) / 2.0 center_y (neck_y hip_y) / 2.0 # 重心高度归一化值越小越高 # 躯干角度向量 (hip_x-neck_x, hip_y-neck_y) 与竖直方向的夹角 dx body[8][0] - body[1][0] dy body[8][1] - body[1][1] angle np.degrees(np.arctan2(abs(dx), abs(dy))) # 竖直为 0躺平接近 90 speed 0.0 if prev_center_y is not None: speed abs(center_y - prev_center_y) # 移动量归一化后是 0-1 尺度 return center_y, angle, speed # 连续的判断示例满足两个条件才给一次命中 center_y, angle, speed fall_feature(body, prev_y) fall_now (center_y 0.45 and angle 50) or speed 0.12逻辑说明center_y 0.45表示重心落到了画面高度 45% 以下这个值对应人已经躺到画面下半部分angle 50表示躯干严重偏离竖直speed 0.12是在 30fps 下做了归一化后的经验值表示单帧之间重心垂直位移超过整幅画面的 12%。这三个阈值是起步值实拍后必须按相机的安装高度重新标定——相机装得越高重心归一化值越小角度判据越可靠。注意speed判据对单人画面才有意义。多人场景下画面里同时有多个人进出某个人快速走过也会导致速度特征跳变。所以更稳的做法是把“中心点高度持续低位 大幅速度变化”组合起来而不是只强调单一指标。5. 摔倒检测落地中的 5 个高频坑从环境冲突到误报压不住5.1 YOLOv5 和 OpenPose 两个环境互相踩脚现象先装好 YOLOv5再编译 OpenPose原本跑得好好的 YOLOv5 推理突然报错要么是protobuf版本对不上要么是opencv的接口变了。原因OpenPose 编译时依赖的 protobuf 和 TensorFlow 生态会把环境里的 protobuf 版本整体改掉而 YOLOv5 的一些依赖和它冲突。解决我给自己的机器配了双 conda 环境一个叫yolo_env一个叫pose_env。YOLOv5 的检测结果通过 JSON 文件或 ZeroMQ 传给姿态环境两个进程互不干扰。如果你的代码里必须在同一个进程内调用两个模型那就先把 protobuf 固定到一个两边都兼容的版本别让它自由升降级。5.2 弯腰捡东西被误判成摔倒现象老人弯腰系鞋带、捡遥控器系统报警一天十几次。原因只看躯干角度不看速度。弯腰时躯干角度确实接近摔倒但重心下移速度慢没有“突然坠落”的特征。解决给判断逻辑加一个速度门槛——只有当重心垂直速度超过阈值时才进入候选摔倒状态。这个手段能过滤掉绝大多数慢速弯腰动作。同时把角度判据的时间窗口拉长要求连续 3 帧都满足才触发而不是单帧命中就报警。5.3 姿态点抖动导致速度特征被放大现象一个人站在原地因为轻微遮挡或光照波动OpenPose 对髋部关键点的输出在几帧之间跳了几十像素速度特征直接超阈值。原因关键点检测对模糊帧、遮挡区域的置信度不稳定坐标抖动是模型本身的特性不是 bug。解决对关键点 y 坐标做指数移动平均EMAsmooth_y 0.7 * cur_y 0.3 * prev_smooth_y。在检测帧率只有 5-10fps 的 CPU 设备上这个平滑系数可以把瞬时抖动压到很低的水平代价是摔倒检测的响应延迟增加几帧监控场景完全可接受。5.4 CPU / 树莓派 5 上帧率只有 2-3 FPS现象1080p 视频流YOLOv5 和 OpenPose 全图串联跑最多 2FPS画面卡成 PPT。原因OpenPose 全图推理的net_resolution要是默认值CPU 根本扛不住YOLOv5 的输入尺寸全图跑也在浪费算力。解决分三步降载——YOLOv5 的推理尺寸降到 416 甚至 320OpenPose 只跑裁剪出来的目标区域不要全图跑把net_resolution显式调低到 320x176。如果设备是树莓派 5 这种 ARM 平台最可靠的做法是把自己训练的 YOLOv5 模型转成 NCNN 格式再部署OpenPose 侧用官方 ONNX 权重在 CPU 上跑。实跑下来目标区域裁剪后 OpenPose 的耗时能降到原来的三分之一整条链路的帧率能到 5-8FPS用于摔倒报警够了。5.5 摔倒报警后不恢复人站起来还一直响现象摔倒报警触发后人已经自己站起来系统还在持续报警值班人员以为是连续两次摔倒。原因没有做状态机摔倒判定是纯阈值逻辑只要重心高度或角度满足条件就一直触发。解决引入三态状态机——正常态STAND、摔倒态FALL、恢复态RECOVER。只有从 STAND 进入 FALL 才触发报警FALL 后必须检测到人站起来且重心高度恢复到正常值才回到 STAND。状态机的实现不复杂但它是从“能检测”走向“能用”的分水岭。6. 进阶给摔倒检测加上消抖、报警推送和慢速设备降载方案6.1 状态机与消抖不重复报警只报一次我最后跑通的判断逻辑长这样每一帧先算特征是否进入 FALL 状态要看连续 3 帧命中FALL 状态触发一次报警然后必须等人站起并保持 2 秒以上才复位。这个状态机代码不多但能让误报率大幅下降class FallStateMachine: def __init__(self, confirm_frames3): self.state STAND self.hit_count 0 self.confirm_frames confirm_frames self.alert_sent False def update(self, is_fall): if self.state STAND: if is_fall: self.hit_count 1 if self.hit_count self.confirm_frames: self.state FALL self.alert_sent False # 允许触发一次新报警 else: self.hit_count 0 elif self.state FALL: # 连续 60 帧约 2 秒 30fps没有摔倒特征认为人已恢复 if not is_fall: self.recover_count 1 if self.recover_count 60: self.state STAND self.recover_count 0 else: self.recover_count 0 return self.state, self.alert_sent逻辑说明confirm_frames3是确认帧数控制着系统对瞬时误触的免疫程度recover_count 60决定了复位速度太短容易被半蹲姿势卡住太长会让报警阻塞过久。参数按帧率换算帧率越低确认帧数应该越少恢复帧数也相应减少。6.2 报警消息往哪送HTTP 回调就够了摔倒检测的最终出口通常不是屏幕而是值班手机的推送。最简单可靠的方式是 HTTP 回调——不管后端是 Webhook 还是消息机器人只要暴露一个 POST 接口就能接。推送逻辑必须放到独立线程不能阻塞视频循环import requests import threading def send_alert(payload): def _post(): try: requests.post(http://your-server/api/fall, jsonpayload, timeout3) except requests.exceptions.RequestException: pass # 推送失败不能影响主流程 threading.Thread(target_post, daemonTrue).start() # 在状态机触发的回调里用 send_alert({ camera_id: camera_01, timestamp: time.time(), center: [x1, y1, x2, y2], confidence: conf })逻辑说明推送动作交给守护线程后即使网络抖动导致请求超时视频处理循环也不会卡顿。timeout3是为了防止 HTTP 连接长时间挂起把线程池耗尽。这个模式适用所有报警场景包括跌倒、区域入侵、离床检测。6.3 验证方法回放实测数据量化三个指标上线之前一定要做一次离线回放验证。做法是录制三段视频正常行走 10 分钟、坐倒或弯腰 5 分钟、真实摔倒 5-10 次。用脚本把视频逐帧喂给这套链路统计三个数字召回率摔了几次警报了几次、误报次数正常动作触发了几次、响应延迟从摔倒动作发生到报警输出间隔几帧。我自己习惯先跑满一小时的“多人正常活动”再考虑接线上报警这段测试能暴露的误报种类远比几张测试图多。判断性能是否达标的底线是真实摔倒样本召回率 100%同一个小时内误报不超过 2 次响应延迟在 0.5-1 秒以内。如果误报压不住优先调状态机的确认帧数而不是盲目放宽或收紧特征阈值——这也是我踩过最多坑后最想提醒你的一点。希望帮到你。本文还有配套的精品资源点击获取
返回列表