ARTICLE DETAIL

资讯详情

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

基于yolov5与openpose的摔倒检测系统设计与实现

基于yolov5与openpose的摔倒检测系统设计与实现 简介面向计算机专业毕业设计、课程设计与期末大作业场景这份项目整合了YOLOv5人体检测与OpenPose姿态估计算法实现实时摔倒检测提供完整Python源码与训练模型并在导师指导下获98分高分评审可帮助学习者快速搭建同类视觉识别项目。资源包大小约40.25MB包含可直接运行或二次开发的项目源码与模型文件目前已吸引164人学习浏览。项目核心覆盖YOLOv5目标检测定位人体、OpenPose关键点提取姿态特征、摔倒判定逻辑与可视化输出等环节能清晰展示深度学习与姿态估计结合完成安防任务的完整思路。作为高分毕设方案既适合作为课程设计、期末大作业的整体参考也适合需要项目实战练习的Python与深度学习初学者读者可对照源码与模型理解两阶段检测流程的串联方式并可根据自身课题进行功能扩展。1. 摔倒检测毕设为什么选yolov5openpose一条能把毕业设计做完整的技术路线医院走廊、养老院病房、工厂车间、地铁站台随手翻一段监控视频就能看到跌倒在地的场景。基于深度学习的摔倒检测最常见的落地组合就是yolov5人体检测openpose姿态检测前者把画面里的人框出来负责“人在哪”后者把人体骨骼关键点标出来负责“姿态什么样”。摔倒判定完全建立在检测框尺寸和关键点坐标的时序变化上。这个项目带完整源码和预训练模型能从环境搭建一路跑到最后的摔倒报警评审98分。适合正在做视觉方向毕业设计的学生、需要交课程设计或期末大作业的人以及想在真实项目里把目标检测和姿态估计两条技术线完整走通的学习者。2. 把yolov5和openpose接起来从检测框到姿态关键点的坐标对齐2.1 为什么是yolov5而不是faster rcnn速度与精度的取舍摔倒检测是实时性要求很高的任务。视频流每秒25到30帧如果检测模型单帧推理就要100毫秒以上整个系统延迟根本压不住。faster rcnn这类两阶段检测器精度不差但在常规GPU上跑视频也就是十几FPS留给openpose姿态估计和摔倒判定的算力几乎没有余量。yolov5是单阶段检测yolov5s在GPU上能跑到100 FPS以上只检测person一个类别时精度完全够用这就把算力预算的大头腾给了openpose。yolov5的网络结构图网上已有大量现成分析核心是CSPDarknet主干加PANet颈部加四组不同尺度的检测头这里不展开只需要知道它输出的是[x1, y1, x2, y2, conf, class]格式的检测框就够用。选yolov5还有一个现实原因生态太成熟了。torch.hub能直接加载官方预训练权重标注格式、训练脚本、部署资料全齐做毕业设计不用从零造轮子。项目里用的是yolov5s这个规格如果机器显存够大、又希望检出率更稳可以换成yolov5m精度小幅提升但推理时间大约翻倍。反过来在CPU上跑yolov5s是相对能接受的极限yolov5m基本告别实时。实际使用中我一般把置信度阈值设在0.4到0.5之间。阈值设太低比如0.2检测框会大量抖动把背景误检成人设太高比如0.7人体重叠、弯腰的姿态又容易漏检。摔倒场景里人往往已经躺在地上姿态与正常站立差异很大置信度会下降所以0.4是一个比较折中的起点。2.2 openpose关键点体系18个点分别代表什么openpose输出的人体关键点有不同布局最常见的是COCO 18点和BODY_25。摔倒检测用COCO 18点就够了因为真正参与判定的是躯干和髋部这一组点手部、脸部细节对这个任务没有增量意义反而增加后处理负担。18个点的索引顺序是固定的写判定逻辑前必须把这张表放在手边。索引关键点索引关键点0鼻子9右膝1脖子10右踝2右肩11左髋3右肘12左膝4右腕13左踝5左肩14右眼6左肘15左眼7左腕16右耳8右髋17左耳每个关键点输出是一个三维向量前两维是像素坐标第三维是置信度。网络结构上openpose同时预测关键点热图和部位亲和场热图负责定位“点在哪”亲和场负责把属于同一个人的点连起来。这一块不需要自己实现项目源码里已经把后处理封装好了直接调用即可。这里有个容易忽略的问题关键点坐标是在openpose网络输入分辨率下算出来的而yolov5检测框坐标是在原始视频分辨率下算出来的。两套坐标系不做对齐后面所有判定都是错的。2.3 坐标管线把两个模型的输出统一到原图坐标系yolov5输出直接对应原图像素坐标。openpose则不同推理前会把输入图缩放到固定尺寸最常见的是368×368输出的关键点坐标基于缩放后的图像必须映射回原图。# openpose关键点坐标从网络输入分辨率映射回原图 scale_x orig_img_w / net_input_w # 例如 1920 / 368 scale_y orig_img_h / net_input_h # 例如 1080 / 368 keypoints[:, 0] keypoints[:, 0] * scale_x keypoints[:, 1] keypoints[:, 1] * scale_y逻辑说明网络输出的关键点坐标范围在[0, 368]区间但原始画面可能是1920×1080直接用会让骨骼点全部堆在画面左上角。用宽高各自的比例缩放坐标就回到了原图空间。参数上net_input_w和net_input_h必须与构建onnx会话时设置的输入尺寸一致如果推理时改了resize尺寸这两个参数要同步改否则骨骼整体错位。一种常见的做法是先用yolov5检测出人体框裁剪出区域再把裁剪区域resize到368×368喂给openpose。这样openpose只需处理一个人而不是整帧关键点更稳定但裁剪的坐标变换要多做一次。不管裁剪还是整帧推理原则都一样所有坐标最终都要回到原始帧坐标系后面才谈得上计算宽高比和角度。把这两个模型的输出接好之后整个系统的数据流就是视频帧进入yolov5得到人体框人体框对应的图像区域进入openpose得到骨骼点两个结果合在一起进入摔倒判定模块。3. 环境配置与demo复现用已有源码和模型跑出第一组检测结果3.1 conda虚拟环境与依赖版本这个项目依赖PyTorch、onnxruntime、opencv、numpy这几个核心库。深度学习环境配置最容易出问题的地方就是torch和CUDA版本不匹配。环境建议直接用Python 3.8深度学习库对它的兼容性最稳Python 3.10以上有些老版本onnxruntime的CUDA执行提供方会直接报错。CUDA推荐11.x配套的PyTorch轮子最全。conda create -n fall_detect python3.8 conda activate fall_detect pip install torch torchvision --index-url https://download.pytorch.org/whl/cu118 pip install -r requirements.txt逻辑说明第一行创建Python 3.8独立虚拟环境避免和系统Python互相污染第二行激活环境第三行安装GPU版PyTorch指定CUDA 11.8对应的轮子源第四行按项目里的requirements.txt安装剩余依赖。如果机器没有NVIDIA显卡把第三行换成CPU版代码能跑但速度会非常感人一帧可能要几秒只适合验证逻辑。# CPU版PyTorch官方源 pip install torch torchvision提示首次运行yolov5会自动下载预训练权重如果下载超时手动下载后放到~/.cache/torch/hub/目录下重新运行即可。3.2 先跑yolov5人体检测拿到person框yolov5官方仓库支持通过torch.hub直接加载这是最快的方式。摔倒检测只需要person这一类COCO预训练模型里person是类别0设置model.classes之后其他类别直接不参与推理输出。import torch # 加载yolov5s预训练权重第一次运行会自动下载 model torch.hub.load(ultralytics/yolov5, yolov5s, pretrainedTrue) model.classes [0] # 只保留person对应COCO类别索引0 model.conf_thres 0.4 # 置信度阈值 model.iou_thres 0.5 # NMS的IoU阈值 results model(frame) # 输入是BGR格式的numpy数组 boxes results.xyxy[0].cpu().numpy() person_boxes [b for b in boxes if b[4] 0.4]逻辑说明model.classes [0]让yolov5只检测person过滤掉COCO其余的79个类别避免车、椅子、狗这些目标干扰判定。conf_thres控制检测框的置信度门槛0.4是摔倒场景下的折中值。iou_thres是NMS去重阈值0.5代表检测框重叠超过50%时保留分数更高的那个。person_boxes的每个元素是[x1, y1, x2, y2, conf, class]前四个坐标都是原图像素单位。如果项目目录里带了针对场景微调过的权重也可以用下面的方式加载本地文件model torch.hub.load(ultralytics/yolov5, custom, pathbest.pt)path指向项目里的权重文件路径加载后推理接口和官方预训练权重完全一致后面代码不用改。3.3 再跑openpose姿态估计拿到骨骼关键点openpose的落地形式有TensorFlow版、PyTorch版、ONNX版接口各不相同但核心流程一致。这里以项目中最常见的ONNX版为例模型输入是[1, 3, H, W]的归一化张量输出是热图和亲和场需要经过源码里的后处理函数解析成18个关键点。import cv2 import numpy as np import onnxruntime as ort session ort.InferenceSession(openpose.onnx, providers[CUDAExecutionProvider]) def get_keypoints(frame, box): # 裁剪人体区域向外扩20像素避免关键点在框边缘 x1, y1, x2, y2 box h, w frame.shape[:2] pad 20 x1 max(0, int(x1) - pad); y1 max(0, int(y1) - pad) x2 min(w, int(x2) pad); y2 min(h, int(y2) pad) crop frame[y1:y2, x1:x2] # resize到网络输入尺寸并归一化 input_img cv2.resize(crop, (368, 368)) input_tensor input_img.transpose(2, 0, 1)[None].astype(np.float32) / 255.0 # 推理得到热图和PAF亲和场 heatmaps, pafs session.run(None, {session.get_inputs()[0].name: input_tensor}) # 从热图中解析关键点坐标源码里已实现 keypoints parse_heatmaps(heatmaps[0]) # 关键点坐标从368分辨率映射回原图和裁剪前坐标系 keypoints[:, 0] keypoints[:, 0] * (w / 368.0) x1 keypoints[:, 1] keypoints[:, 1] * (h / 368.0) y1 return keypoints逻辑说明函数按“裁剪人体框→缩放→推理→解析→映射回原图”五步走。裁剪时向外扩20像素是因为摔倒时人的肢体可能超出检测框边界一点裁太紧会让腕、踝这些末端关键点被截掉。parse_heatmaps从热图里找每个关键点的峰值位置输出形状是[18, 3]前两列是坐标第三列是置信度。缩放映射时w / 368.0算出裁剪区域的缩放比例最后加上x1, y1偏移量把坐标从裁剪区域坐标系搬回整帧坐标系。如果漏掉最后这一步骨骼会画在裁剪区域的左上角。3.4 串联完整流程每一帧执行什么把两个模型串起来主循环逻辑如下cap cv2.VideoCapture(demo_video.mp4) while cap.isOpened(): ret, frame cap.read() if not ret: break # 第一步yolov5人体检测 results model(frame) boxes results.xyxy[0].cpu().numpy() # 第二步对每个人体框做姿态估计并可视化 for box in [b for b in boxes if b[4] 0.4]: keypoints get_keypoints(frame, box) draw_skeleton(frame, keypoints) x1, y1, x2, y2 box[:4] cv2.rectangle(frame, (int(x1), int(y1)), (int(x2), int(y2)), (0, 255, 0), 2) cv2.imshow(fall_detect, frame) if cv2.waitKey(1) 0xFF ord(q): break cap.release() cv2.destroyAllWindows()逻辑说明每一帧先过yolov5检测到的人体框逐个送入get_keypoints做姿态估计然后在原图上叠加骨骼和检测框。draw_skeleton是源码里的可视化函数按关键点索引连线比如1-2连右肩、1-5连左肩、8-9-10连右腿。跑通这个demo之后整条数据管线就建立了后面所有摔倒判定逻辑都拿box和keypoints作为输入。4. 摔倒判定核心逻辑宽高比、躯干角度与垂直速度的三路融合4.1 特征一检测框宽高比摔倒时为什么比值会翻转检测框是最容易拿到的信息也是第一时间参与判断的特征。站立时人的身高明显大于肩宽检测框的高宽比通常在2.5到4.0之间摔倒躺平后身体长轴变成水平方向检测框宽高比降到1.0以下。这个翻转是摔倒最直观的视觉信号。但宽高比单独用会出问题。人蹲下、弯腰系鞋带时检测框高度也会大幅缩减宽高比同样会降到1左右这类误报是摔倒检测最常见的翻车点。所以宽高比只能作为第一路特征用于“快速筛选”不能当成唯一判据。姿态检测框宽高比 h/w是否能判定摔倒站立2.5 ~ 4.0否蹲下1.0 ~ 1.5否需要其他特征排除躺倒正面0.5 ~ 1.0是躺倒侧身0.8 ~ 1.5需要结合关键点和速度计算宽高比的代码很简单x1, y1, x2, y2 person_box h max(1e-5, y2 - y1) # 加极小值防止除零 w max(1e-5, x2 - x1) ratio h / w # 小于1.0说明检测框已经横过来了逻辑说明max(1e-5, ...)是防止检测框退化成一个点导致除零报错。ratio小于1说明框是横向的但只是可疑不是最终结论。4.2 特征二躯干与竖直方向的夹角关键点能提供比检测框更细的信息。摔倒的本质是躯干从竖直变成近水平所以用脖子和两个髋部的坐标计算躯干倾斜角度是第二路更有说服力的特征。具体做法取脖子关键点索引1和左右髋部关键点索引8、11用左右髋部中点作为骨盆中心计算脖子到骨盆中心连线与竖直方向的夹角。import math def body_angle(keypoints): neck keypoints[1] l_hip keypoints[11] r_hip keypoints[8] # 置信度低于0.3的关键点视为不可信直接返回 if neck[2] 0.3 or l_hip[2] 0.3 or r_hip[2] 0.3: return 0.0, False hip_cx (l_hip[0] r_hip[0]) / 2.0 hip_cy (l_hip[1] r_hip[1]) / 2.0 dx abs(neck[0] - hip_cx) dy abs(neck[1] - hip_cy) angle math.degrees(math.atan2(dx, dy)) return angle, True逻辑说明atan2(dx, dy)计算躯干连线与竖直方向y轴的夹角。站立时dx很小、dy很大角度接近0度躺倒时dx变大、dy变小角度接近90度。返回值里的False表示关键点置信度不足比如人部分被遮挡时这一路特征直接跳过而不是硬算出一个错误值。阈值经验值设为60度躯干偏离竖直方向超过60度才视为可疑这个值跟人体运动学里的跌倒研究结论基本一致。4.3 特征三中心点垂直速度前两路特征都是单帧静态特征。但蹲下和摔倒的静态特征高度相似都是宽高比变小、躯干角度变大只靠静态特征蹲下被误判的问题很难根治。区分两者的关键在动态信息正常人蹲下是缓慢动作摔倒是快速过程人体重心在0.5到1秒内垂直下降几十厘米。用检测框中心点的y坐标变化来计算垂直速度是最简单有效的方案# 上一帧和当前帧的检测框中心y坐标 prev_center_y prev_box_center_y center_y (y1 y2) / 2.0 # 垂直速度像素/秒fps是视频帧率 v_y (center_y - prev_center_y) * fps逻辑说明(center_y - prev_center_y)是两帧之间中心点下移的像素数乘以fps换算成每秒的垂直位移。正常走路时垂直速度很小因为身体重心基本保持在同一水平面弯腰捡东西会有一个向下的位移但速度远低于摔倒。经验值在1080p分辨率下正常下蹲的垂直速度通常低于100像素/秒摔倒时能到300像素/秒以上。注意这个阈值和画面分辨率强相关720p视频要等比例缩小4K画面要放大不能直接套同一个数。4.4 三路融合判定与连续帧确认机制把三路特征整合进一个判定类是整个项目的核心模块。每路特征超过阈值记1分至少两路命中、且连续多帧保持才触发摔倒报警。这样设计比单特征阈值稳健得多宽高比和角度同时异常说明不仅框变横了骨骼也跟着躺平速度和角度同时异常说明是快速倾倒而不是缓慢蹲下。import math class FallDetector: def __init__(self, fps30, trigger_frames3, ratio_thresh1.0, angle_thresh60.0, speed_thresh250.0): self.fps fps self.trigger_frames trigger_frames self.ratio_thresh ratio_thresh self.angle_thresh angle_thresh self.speed_thresh speed_thresh self.prev_center {} # 每个目标ID的上一帧中心点y self.fall_count {} # 每个目标ID的连续命中帧数 def body_angle(self, keypoints): neck keypoints[1] l_hip keypoints[11] r_hip keypoints[8] if neck[2] 0.3 or l_hip[2] 0.3 or r_hip[2] 0.3: return 0.0, False hip_cx (l_hip[0] r_hip[0]) / 2.0 hip_cy (l_hip[1] r_hip[1]) / 2.0 angle math.degrees(math.atan2(abs(neck[0] - hip_cx), abs(neck[1] - hip_cy))) return angle, True def update(self, track_id, box, keypoints): x1, y1, x2, y2 box h max(1e-5, y2 - y1) w max(1e-5, x2 - x1) ratio h / w center_y (y1 y2) / 2.0 score 0 if ratio self.ratio_thresh: score 1 angle, ok self.body_angle(keypoints) if ok and angle self.angle_thresh: score 1 v_y (center_y - self.prev_center.get(track_id, center_y)) * self.fps if v_y self.speed_thresh: score 1 self.prev_center[track_id] center_y if score 2: self.fall_count[track_id] self.fall_count.get(track_id, 0) 1 else: self.fall_count[track_id] 0 # 连续命中超过trigger_frames帧才确认摔倒 return self.fall_count[track_id] self.trigger_frames逻辑说明update的入参是目标ID、检测框和关键点。track_id在多人场景下至关重要它保证每个目标的判定状态独立——第一个人摔倒了不会因为第二个人正常走路而被重置计数。score 2表示三路特征里至少两路命中trigger_frames默认3帧在30FPS视频里就是连续3帧约100毫秒保持异常才报警单帧抖动和关键点跳变都被过滤掉。prev_center.get(track_id, center_y)处理的是目标第一帧出现的情况没有上一帧记录就直接用当前值速度记为0避免首帧误报。参数调整优先级帧率变化先改fps画面分辨率变化先等比缩放speed_thresh误报多就提高trigger_frames到4或5漏报多就降到2。这个类可以直接嵌进主循环检测到一个人体框就调用一次update。实际调参时把这三个阈值当成yolov5超参数一样对待逐个扫一遍不要拍脑袋定。5. 避坑与排查五个常见问题的现象、原因与解决方法5.1 openpose推理太慢帧率只有2到3 FPS现象视频流跑起来后画面严重卡顿实测每秒只能处理两到三帧完全达不到实时。原因openpose默认输入分辨率偏高常见的是656×368且如果整帧送入网络算力全部耗在姿态估计上。部分环境下还会触发CPU回退看着在跑实际没走CUDA。解决先用session.get_providers()确认CUDAExecutionProvider生效再把输入分辨率降到368×368或320×320最关键的是改成“先yolov5检测到人体框再只裁剪人体区域送入openpose”的管线而不是每帧整图推理。单人场景帧率能提高一倍以上项目源码里已经按这个思路封装好了直接用就行。5.2 蹲下、系鞋带被误判为摔倒现象测试视频里人物蹲下整理物品系统持续报警。原因只用了宽高比单路特征。蹲下时检测框同样会变扁宽高比同样跌破阈值单靠ratio区分不了蹲下与躺倒。解决把判定改为三路特征融合引入躯干角度和垂直速度。蹲下的躯干角度虽然也变大但垂直速度很小达不到摔倒级别的下降速度卷缩着蹲下时脖子到髋部的角度计算还会因为关键点置信度过低而直接跳过。改完之后误报率明显下降。核心思路就是“静态相似就比动态”速度是区分缓慢动作和突发摔倒最有效的维度。5.3 yolov5检测框抖动宽高比忽大忽小现象视频里人体检测框在相邻帧跳动有时框住全身有时只框住上半身宽高比在0.8和2.5之间来回跳。原因置信度阈值太低低质量检测框混了进来另外帧间没有做任何平滑处理每一帧的检测都是独立决策。解决把conf_thres从0.25提高到0.4到0.5低置信度的残缺框被过滤掉再对检测框做EMA平滑box alpha * current_box (1 - alpha) * prev_box逻辑说明alpha取0.3到0.5平滑之后宽高比的波动会小很多。注意alpha不要低于0.2否则检测框反应太慢摔倒这种快速动作会被平滑掉反而漏报。5.4 多人场景下检测框和关键点错位现象两个人靠近时A的关键点骨骼画到了B的身体上判定结果混乱。原因每个检测框独立送入openpose但openpose在裁剪区域内看到的是整幅画面如果裁剪区域包含了另一个人的肢体关键点就会解算到别人身上。解决裁剪人体框后缩小裁剪范围减少背景占比更稳妥的是引入目标跟踪器用IoU匹配把检测框和已有目标绑定每个track_id独立调用FallDetector.update。即使帧间跟踪偶尔丢失也不至于把两个人的姿态数据揉在一起。5.5 相机是俯视角度固定阈值直接失效现象摄像头装在房间角落的高处画面里人站着时检测框宽高比也只有1.5左右宽高比阈值1.0永远触发不了躯干角度同样偏小。原因俯视角度改变了人体在画面中的投影形态基于平视监控标定的阈值不再适用。这是固定机位场景最容易忽略的坑。解决把阈值从“绝对阈值”改成“相对基线”。部署时先录一段场景里正常走路的视频统计宽高比、角度的均值作为基线运行时与基线比较宽高比下降到基线的60%以下、角度偏离基线的程度超过设定值才判定异常。这个思路能顺带解决不同摄像头高度、角度导致的阈值迁移问题。6. 进阶微调yolov5、把延迟压下来与报警联动6.1 用自己的数据微调yolov5把通用模型换成自己的通用COCO权重对马路上的人检得好对病房、车间这类固定机位场景未必最优。微调yolov5的数据量不需要很大几百到一千张标注图就有明显效果。标注格式用YOLO txt每行是class x_center y_center width height坐标都归一化到0到1。目录结构按yolov5要求组织dataset/ ├── images/ │ ├── train/ │ └── val/ ├── labels/ │ ├── train/ │ └── val/ └── dataset.yaml训练命令python train.py --img 640 --batch 16 --epochs 100 \ --data dataset.yaml --weights yolov5s.pt逻辑说明--img 640是训练分辨率--epochs 100在小数据集上足够收敛--batch根据显存调整6G显存用16。训练完的权重在runs/train/exp/weights/best.pt替换推理时的模型路径即可。6.2 把延迟压下来的三个实测技巧第一个是跳帧策略yolov5每帧都跑openpose每两帧跑一次中间帧复用最近一次的关键点。摔倒动作持续约0.5到1秒隔一帧算一次角度和速度时间分辨率依然足够。第二个是缩小openpose输入分辨率368改256推理时间大约缩短三分之一关键点精度在这个任务上可以接受。第三个是裁剪人体区域再送入openpose而不是整帧推理单个人时速度提升最明显。6.3 报警联动输出检测到摔倒后保存截图、画高亮框、写结构化日志三件事一起做形成可回溯的报警记录。保存的截图带时间戳和track_id日志里记录检测框坐标配合完整录像可以做离线复核。做这个项目最后一步时我拿了一段从未参与过调参的测试视频跑完整流程画面里的人在扫地、蹲下整理物品、正常行走系统保持不报警然后让一个同事突然倒地系统在两秒内触发报警。最大的感悟是不要拿参与调参的那段视频来评估性能模型对熟悉场景的记忆会让人误以为系统很稳。从那以后我每次做检测类项目都强制自己留出20%的数据做最终验证不再拿训练素材自我感动。希望帮到你。本文还有配套的精品资源点击获取
返回列表