基于YOLO与MediaPipe的实时防摔倒检测系统:双模型串联与工程调优
简介这份资源是面向高校计算机、电子信息与数学专业学生的毕业设计及课程设计参考项目聚焦目标检测与姿态识别技术在实时防摔倒场景中的落地实现。系统以摄像头视频流为输入先由目标检测模块定位人体再通过姿态识别网络分析站立、坐下、行走、跌倒等状态并结合决策逻辑判断摔倒征兆、触发报警可应用于老年人照护、幼儿监护、残疾人辅助及工地、场馆等公共安全监控场景。压缩包共899个文件约117.18MB以hpp、cpp、h等C头文件与源码为主体配合py脚本、json配置、cu与prototxt等深度学习模型文件以及avi示例视频、sh与bat运行脚本、yml与cmake构建配置覆盖从模型推理到系统部署的完整链路。目前已有89人学习关注。项目对算法优化、模块划分与功能扩展均有较完整呈现适合作为大作业、期末项目或毕设的实践蓝本帮助读者理解目标检测与姿态识别的工程化流程并提升解决实际问题的能力。1. 从一次宿舍夜测说起这套防摔倒系统到底能跑出什么效果去年帮隔壁实验室的师弟调一个毕设需求很朴素摄像头对着走廊老人或者学生摔倒时能立刻报警。他一开始想用纯姿态识别靠骨架角度阈值判断结果人一蹲下系鞋带就误报躺沙发上也被判成摔倒。后来换成目标检测加姿态识别两条腿走路误报率才压下来。这套「基于目标检测和姿态识别的实时防摔倒检测系统」走的就是这个思路先用 YOLO 系列把人框出来再对框内的人做姿态估计拿到关键点后算躯干角度和宽高比最后综合判定是否摔倒。它适合做毕设、课设也适合想入门「检测姿态」双模型串联的开发者。整包是完整可运行的工程不是只给几个权重文件让你自己拼。下面我按实际拆包和跑通的顺序把选型理由、环境配置、推理链路和踩过的坑一条条讲清楚。2. 双模型串联的架构选型为什么不是单靠姿态或单靠检测2.1 目标检测负责「有没有人」姿态识别负责「人是什么状态」纯姿态识别的问题在于它默认画面里已经有人而且往往只处理单人或固定区域。一旦画面里出现多人、遮挡、或者人只露出一半关键点就会乱飞。纯目标检测则只能告诉你「这里有个像人的东西」分不清站着、坐着还是躺着。这套系统把两者串起来YOLO 先输出每个人的边界框和置信度再把每个框裁剪出来送进姿态模型得到 17 个 COCO 关键点。这样即使画面里有三四个人的也能逐人判定不会互相干扰。常见做法是检测和姿态用两个独立模型中间靠 ROI 裁剪衔接。也有用 YOLO-Pose 这类单阶段多任务模型的速度快但姿态精度略低。这套资源走的是两阶段路线好处是检测和姿态可以各自替换升级比如检测换 YOLOv8姿态换 HRNet互不影响。代价是推理耗时是两者之和实时性要靠帧采样和跳帧来补。2.2 摔倒判定的三个几何特征角度、宽高比、重心高度拿到关键点后不能直接扔给分类器那样需要大量标注数据。工程上更稳的是用几何规则做初筛。这套系统主要看三个量躯干与水平面的夹角、人体框的宽高比、以及髋部关键点相对画面底部的高度。站立时躯干接近垂直宽高比小于 1摔倒时躯干接近水平宽高比大于 1.2髋部高度骤降。三个条件同时满足才触发报警能过滤掉大部分弯腰、下蹲、坐下的动作。参数不是拍脑袋定的。宽高比阈值我一般从 1.0 开始试走廊俯拍场景调到 1.3 更稳角度阈值 45 度是常用起点但侧躺和趴倒差异大需要按摄像头安装角度微调。这些阈值在配置文件里都能改不用重新训练模型。2.3 环境配置YOLOv8 加 MediaPipe 的最小依赖组合这套工程我实测用 Python 3.9 加 PyTorch 2.0 能跑通。检测侧用 ultralytics 的 YOLOv8姿态侧用 MediaPipe Pose两者都是 pip 能装的不需要编译。如果你机器上有 CUDAYOLO 会自动走 GPUMediaPipe 默认 CPU整体在 1080p 下能到 15 到 20 FPS够实时用。# 创建虚拟环境避免和系统包冲突 conda create -n fall_detect python3.9 -y conda activate fall_detect # 安装检测框架ultralytics 自带 YOLOv8 权重下载 pip install ultralytics8.0.200 # 姿态估计用 MediaPipeCPU 版即可 pip install mediapipe0.10.9 # 视频读取和绘图 pip install opencv-python4.8.1.78这里锁版本是有血泪经验的。ultralytics 8.0.200 之后的版本改过推理接口老代码里的model.predict参数名对不上会直接报 TypeError。MediaPipe 0.10.9 在 Windows 和 Linux 上行为一致再新的版本在某些 Linux 发行版上会缺 libGL。装完先跑一句python -c from ultralytics import YOLO; YOLO(yolov8n.pt)能自动下载权重就说明检测侧通了。3. 从视频流到报警推理链路的代码拆解与参数调优3.1 逐帧读取与检测框过滤只留人这一类视频源可以是本地文件也可以是 USB 摄像头。OpenCV 的 VideoCapture 两种都支持区别只在参数传路径还是传设备号。读进来的帧先送 YOLO但 YOLO 会输出 80 类我们只要 person 这一类所以拿到结果后按 class id 过滤COCO 里 person 的 id 是 0。import cv2 from ultralytics import YOLO model YOLO(yolov8n.pt) # n 版最轻实时优先 cap cv2.VideoCapture(0) # 0 是默认摄像头传路径则读文件 while cap.isOpened(): ret, frame cap.read() if not ret: break # conf 设 0.5低于此值不认为是人减少误检 results model.predict(frame, conf0.5, classes[0], verboseFalse) boxes results[0].boxes for box in boxes: x1, y1, x2, y2 map(int, box.xyxy[0]) # 裁剪出每个人送姿态模型 person_crop frame[y1:y2, x1:x2] # 后续姿态处理见下一节 cv2.imshow(fall_detect, frame) if cv2.waitKey(1) 0xFF ord(q): break cap.release() cv2.destroyAllWindows()conf0.5是检测置信度阈值调低会检出更多疑似人但误报增加调高会漏掉远处的人。classes[0]是关键不加这行会把椅子、背包也框进来后面姿态模型对着椅子算关键点结果全是乱的。verboseFalse只是关掉每帧的日志刷屏不影响结果。裁剪时记得做边界保护x1 可能为负直接切片会报错实际工程里要加max(0, x1)。3.2 姿态关键点提取与摔倒判定函数MediaPipe Pose 接收 RGB 图像输出 33 个关键点我们只用其中肩、髋、膝、踝这几组。判定函数接收关键点列表算躯干角度和宽高比返回布尔值。这里要注意 MediaPipe 的坐标是归一化的 0 到 1算角度前要乘回图像宽高否则比例失真。import mediapipe as mp import numpy as np mp_pose mp.solutions.pose pose mp_pose.Pose(static_image_modeFalse, min_detection_confidence0.5) def is_fall(landmarks, img_w, img_h): # 取左右肩和左右髋索引见 MediaPipe 定义 l_shoulder landmarks[11] r_shoulder landmarks[12] l_hip landmarks[23] r_hip landmarks[24] # 归一化坐标转像素 shoulder_mid np.array([(l_shoulder.x r_shoulder.x) / 2 * img_w, (l_shoulder.y r_shoulder.y) / 2 * img_h]) hip_mid np.array([(l_hip.x r_hip.x) / 2 * img_w, (l_hip.y r_hip.y) / 2 * img_h]) # 躯干向量与水平方向夹角 vec shoulder_mid - hip_mid angle np.degrees(np.arctan2(abs(vec[1]), abs(vec[0]))) # 宽高比用肩髋横向跨度比纵向跨度 width abs(l_shoulder.x - r_shoulder.x) * img_w height abs(shoulder_mid[1] - hip_mid[1]) ratio width / height if height 0 else 0 # 角度小于 45 度且宽高比大于 1.2 判为摔倒 return angle 45 and ratio 1.2min_detection_confidence0.5控制姿态模型多确信才输出关键点画面模糊时调低到 0.3 能救回一些帧但关键点抖动会变大。角度用arctan2算的是躯干偏离水平线的程度站立时接近 90 度躺平时接近 0 度。宽高比这里用肩宽比躯干高比用整个人体框更稳因为人体框会被手臂张开干扰。两个条件用 and 连接宁可漏报也不误报这是防摔倒系统的工程底线。3.3 报警去抖与帧采样让系统不抽风逐帧判定会有一个问题人摔倒过程中有一两帧刚好满足条件但马上又爬起来系统就报了一次假警。工程上要加去抖连续 N 帧判定为摔倒才触发报警。同时为了保实时性不必每帧都跑姿态可以每 3 帧跑一次中间帧复用上次结果。fall_counter 0 FALL_THRESHOLD 5 # 连续 5 次判定才报警 frame_idx 0 while cap.isOpened(): ret, frame cap.read() if not ret: break frame_idx 1 if frame_idx % 3 ! 0: # 每 3 帧处理一次 continue # ... 检测和姿态代码 ... if is_fall(landmarks, w, h): fall_counter 1 else: fall_counter max(0, fall_counter - 1) # 非摔倒帧递减 if fall_counter FALL_THRESHOLD: print(报警检测到摔倒) fall_counter 0 # 报警后重置避免连续刷屏FALL_THRESHOLD5配合每 3 帧处理一次相当于连续 15 帧约 0.5 秒确认既能抓住真实摔倒又不会因为一帧抖动就报警。递减而不是清零是为了应对摔倒过程中姿态短暂恢复又继续倒的情况。报警后重置计数器否则人会一直躺在地上系统每轮都报。这套去抖逻辑是实际部署时最容易被忽略、但最影响体验的部分。4. 避坑与排查跑不起来时先看这五条4.1 报错 No module named ultralytics 但明明装了现象是 pip list 里能看到 ultralytics运行却提示找不到模块。原因通常是 conda 环境和 pip 环境不是同一个或者 IDE 的解释器选错了。解决方法是先which python确认当前解释器路径再用python -m pip install ultralytics强制装到当前解释器下。如果用的是 PyCharm去设置里把项目解释器指到 conda 环境的 python 可执行文件。4.2 摄像头读出来全是黑屏或第一帧就卡死现象是cap.read()返回 False或者窗口一片黑。原因多半是设备号不对或者摄像头被其他程序占用。Linux 下用ls /dev/video*看有哪些设备Windows 下在设备管理器确认摄像头启用。另外 OpenCV 默认后端在某些摄像头上会卡可以在 VideoCapture 后加cap.set(cv2.CAP_PROP_BUFFERSIZE, 1)减少缓冲。如果还是不行换cv2.CAP_DSHOW后端试试。4.3 姿态关键点乱飞人明明站着却判成摔倒现象是画面里人正常站立但关键点跳到画面角落角度算出来接近 0。原因是 MediaPipe 对裁剪后的小图做姿态估计如果裁剪框太紧或者分辨率太低关键点会失准。解决方法是在裁剪时向外扩 20% 的边距并且把裁剪图缩放到至少 256x256 再送模型。另外 MediaPipe 需要 RGB 输入OpenCV 读进来是 BGR忘了cv2.cvtColor转换也会导致关键点全乱。4.4 多人场景下报警串人现象是画面里两个人一个人摔倒系统却对另一个人也报警。原因是姿态关键点没有和检测框绑定所有框共用了一套关键点。解决方法是每个检测框单独裁剪、单独跑姿态、单独维护 fall_counter用字典按框的 id 存状态。不要图省事把所有框拼成一张图送姿态模型那样关键点会混在一起。4.5 帧率掉到个位数画面卡成幻灯片现象是 FPS 显示只有 3 到 5完全达不到实时。原因是 YOLOv8n 虽然轻但 1080p 输入下 CPU 推理也要 100ms 以上加上 MediaPipe 更慢。解决方法是把输入分辨率降到 640x480检测和姿态都在小图上做画面上再放大显示。另外把model.predict的imgsz参数设成 640默认是 640 但有人改成 1280 忘了改回来。如果机器有 GPU确认 torch.cuda.is_available() 返回 True否则白装 CUDA。5. 进阶技巧把判定逻辑从硬阈值换成时序滑窗硬阈值判定在实验室里够用但真实场景里光照变化、摄像头角度、人的体型差异都会让固定阈值翻车。我后来习惯加一层时序滑窗维护最近 30 帧的角度和宽高比序列算滑动平均和方差只有均值满足条件且方差小于阈值才报警。这样能过滤掉关键点抖动带来的瞬时误判。from collections import deque angle_window deque(maxlen30) ratio_window deque(maxlen30) def is_fall_smooth(angle, ratio): angle_window.append(angle) ratio_window.append(ratio) if len(angle_window) 30: return False avg_angle np.mean(angle_window) avg_ratio np.mean(ratio_window) std_angle np.std(angle_window) # 均值满足且抖动小才认为稳定摔倒 return avg_angle 45 and avg_ratio 1.2 and std_angle 10滑窗长度 30 对应约 1 秒的窗口太长会延迟报警太短起不到平滑作用。方差阈值 10 是经验值关键点稳定时角度标准差通常在 5 以内抖动时会超过 20。这套逻辑替换掉原来的单帧判定后误报率能再降一个档次。验证方法很简单录一段包含站立、坐下、弯腰、真实摔倒的视频逐段跑看报警时刻是否只在摔倒段出现。我一般会统计误报次数和漏报次数误报优先压到零漏报可以通过调低阈值补。参数调完后把配置写进 yaml别硬编码在代码里换场景时只改配置不动代码。从那以后我每次拿到这类检测加姿态的工程都强制先跑一遍纯视频回放确认判定逻辑在离线数据上稳定了再接摄像头做实时。实时调试容易手忙脚乱离线跑通了心里才有底。希望这套拆解能帮你少走点弯路把毕设或者课设顺利跑起来。本文还有配套的精品资源点击获取