☰
跌倒检测实战:基于YOLO与姿态估计的实时防摔倒预警系统
2026/10/2 8:35:05 网站建设 项目流程

简介:基于计算机视觉的目标检测与姿态分析实时防摔倒预警系统,是一份面向计算机相关专业在校学生、教师及从业者的课程设计资料包。项目融合目标检测与人体姿态分析技术,可实现实时防摔倒预警,代码完整且经过验证,稳定可靠,适合用于毕业设计、课程设计、大作业或初期项目立项演示,也支持在此基础上二次开发。压缩包共1794个文件,容量约230.34MB,内含Python源码、C++头文件与实现、JSON配置、文本说明、AVI示例视频及实验报告等,目录结构清晰,便于按模块检索学习;其中Python与C++代码覆盖模型加载、推理、姿态关键点提取等环节,实验报告则提供完整设计说明与实现思路。已有225人学习下载,可作为计算机视觉入门进阶的参考项目,帮助理解从模型调用到实时推理的完整流程。

1. 实时防摔倒预警系统:一套能跑出告警框的课设源码包

跌倒检测在计算机视觉里是个看起来简单、上手才知道水深的课程设计题。这套「基于计算机视觉的目标检测与姿态分析实时防摔倒预警系统」Python 源码包,除了主程序,还带着实验报告和几段 avi 演示视频,整体走的是先做人形目标检测、再做姿态分析、最后用几何特征判断摔倒的路线。很多初学交上用背景差分 + 框中心位移的版本,人一蹲下捡东西就误报;而这个方案把“是否摔倒”落在躯干倾角、重心速度和目标框宽高比上,答辩时更容易把逻辑讲圆。适合计算机视觉课程设计、毕设题目,也适合想快速看一条完整 CV demo 怎么串起来的同学。如果你正准备复现它,先记住一个硬规矩:解压后路径必须改英文。

2. 目标检测与姿态估计选型:为什么坚持 YOLO 框人再加关键点

这套源码里最核心的链路是“检测 → 姿态 → 判断”。先框出人,再提取人体关键点,最后用关键点之间的几何关系判断摔倒。这个顺序不是随手定的,而是把两个任务的定位分清了:目标检测负责回答“人在哪”,姿态估计负责回答“人是什么姿势”。现在的课程设计里,还有同学用帧差或背景建模来做前级,那样人一旦静止、或者背景里有窗帘晃动,误检率就会很失控,所以我拆这类源码包时,会先看它是不是用了基于深度检测的路线;是的话,后面调参才有意义。

2.1 目标检测层:先确定人在哪,再做后续姿态

目标检测我习惯在这类课设里用 YOLOv5s 或 YOLOv8s。原因很简单:YOLOv5s 的部署资料最多,出了环境问题随便一搜就有答案;YOLOv8s 的接口更现代,而且ultralytics库把训练和推理都封好了,适合课程设计快速展示效果。两者都够轻量,CPU 也能跑到看视频不卡的程度。如果你拆到的源码是 YOLOv5 的老代码,注意detect.py里的参数名可能是--weights、--source,而 YOLOv8 的入口通常直接在代码里调model.predict()。

import cv2 from ultralytics import YOLO model = YOLO("models/yolov8s.pt") cap = cv2.VideoCapture("data/data (1).avi") while cap.isOpened(): ret, frame = cap.read() if not ret: break # 统一缩放到 640,YOLO 默认输入尺寸,明显能降低 CPU 推理耗时 frame_small = cv2.resize(frame, (640, 640)) results = model.predict(frame_small, imgsz=640, conf=0.35, classes=[0]) for box in results[0].boxes: x1, y1, x2, y2 = box.xyxy[0].tolist() if box.conf[0] > 0.5: cv2.rectangle(frame, (int(x1), int(y1)), (int(x2), int(y2)), (0, 255, 0), 2) cv2.imshow("detection", frame) if cv2.waitKey(1) & 0xFF == ord("q"): break

这段代码里classes=[0]是关键,COCO 数据集中 0 号类别就是 person,限制它能让网络不去检测桌子和椅子,减少后续误判。conf=0.35是模型输出框的置信度下限,调低了会把墙上的影子框成人,调高了人侧躺时可能漏检。我一般在演示视频上先用 0.35 跑一遍,看漏检多还是误检多,再上下浮动 0.05。

从性能上说,同分辨率下 Faster R-CNN 的精度不一定比 YOLO 高多少,但 CPU 单帧经常跑到 800ms 以上,课设答辩现场一卡,印象分就下来了。这也是我坚持用 YOLO 系列的原因:实时性和精度在课设场景里更平衡,而且权重是现成的,不用自己训练。

2.2 姿态估计层:关键点比目标框多了一个“姿态”维度

目标框只能给一个矩形,摔倒时框依然存在,只是变宽了;要判断摔倒,更可靠的是拿到人体的肩膀、髋部、膝盖这些关键点。这套源码里的姿态分析部分,常见实现是 MediaPipe Pose 或 OpenPose。MediaPipe 的优势是纯 Python 接口好用、自带 33 点模型,对 CPU 友好;OpenPose 精度高一些,但环境配置更折腾。下面这段是 MediaPipe 的典型调用方式:

import mediapipe as mp mp_pose = mp.solutions.pose pose = mp_pose.Pose( model_complexity=1, # 0 轻量,1 均衡,2 高精度 min_detection_confidence=0.5, min_tracking_confidence=0.5, ) frame_rgb = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB) result = pose.process(frame_rgb) if result.pose_landmarks: h, w = frame.shape[:2] for idx, lm in enumerate(result.pose_landmarks.landmark): x, y = int(lm.x * w), int(lm.y * h) cv2.circle(frame, (x, y), 2, (0, 255, 0), -1)

注意 MediaPipe 返回的坐标已经归一化到 0~1,画图前必须乘回图像的宽高,否则所有点都会挤在左上角。model_complexity=1是课设里最常见的配置,速度和精度平衡;如果你拆到的源码跑起来一波三折,可以先试着把复杂度降到 0,帧率会立刻上来,代价是角度抖动变明显。另外,MediaPipe 用的是 33 点命名,OpenPose 的 COCO 格式只有 18 点,两者肩膀、髋部的索引号不一样,改代码前先把这个对齐,不然后面的角度计算全是错的。

2.3 两段式主流程怎么串

检测和姿态估计串起来,最常见的问题是两个模型抢 CPU,导致视频一顿一顿。我一般会在主循环里加跳帧策略:不是每一帧都同时跑检测和姿态,而是每隔一帧或两帧跑一次完整的检测 + 姿态,中间帧直接沿用上一帧的关键点做摔倒判断。这样摔倒判别的频率不降,但两段模型的调用次数减半,实际帧率能提升不少。

frame_idx = 0 STEP = 2 last_pose_result = None while cap.isOpened(): ret, frame = cap.read() if not ret: break if frame_idx % STEP == 0: results = model.predict(frame, imgsz=640, conf=0.35, classes=[0]) # 只取置信度最高的一个人,避免多目标互相干扰 last_pose_result = pose.process(cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)) fall_now = fall_detector.update(last_pose_result, frame.shape[0]) frame_idx += 1

这里的fall_detector.update()就是把关键点坐标和上一帧检测框送进摔倒判别模块。跳帧参数STEP=2不是拍脑袋定的:在 30fps 的视频里,人从站到摔倒一般持续 12~15 帧,隔一帧采样仍然能采样到完整的下坠过程;但如果视频本身只有 15fps,STEP就设成 1。这个参数是这套源码里第一个值得你动手改的优化点。

3. 摔倒判断的几何算法:倾角、下坠速度与宽高比怎么联动

摔倒判断是整个系统的灵魂。很多课设死在两种极端上:要么阈值太敏感,弯腰捡个东西就疯狂报警;要么阈值太迟钝,真摔了又漏报。原因通常是只看单帧特征,或者只看一个维度。人摔倒时,躯干会从直立变成近水平,重心会快速向下移动,人体包围盒会从瘦高变扁宽。把这三个信号放到一个滑动窗口里做投票,误报率会比单帧阈值低一个量级。

3.1 先定义三个特征量

下面这个表是我拆这类源码时习惯先画在纸上的,画完再去读代码会轻松很多。

特征定义正常行走区间摔倒触发区间
躯干倾角双肩中点与双髋中点连线,和竖直方向的夹角10°~40°大于 60°
重心下坠速度髋部中心点 y 坐标的帧间平均变化小于帧高的 1%大于帧高的 3%
目标框宽高比检测框宽度除以高度小于 0.8大于 1.4

躯干倾角是最直观的信号,但单独用一定误报。人正常蹲下拿东西时,躯干也可能达到 50° 以上;所以要叠加重心速度。真正摔倒时重心是连续向下走的,而蹲下捡东西时重心是慢慢下降、再慢慢起来,平均速度远没有摔倒那么快。宽高比则是兜底信号,人已经躺到地上时,就算关键点被遮挡导致倾角算不出来,框的形状也能提示“这个人已经横过来了”。

3.2 摔倒判别的 Python 实现:滑动窗口投票

下面是这套系统里最值得抄下来的核心代码,我按常见的课设实现整理成了可运行的类。它用最近 N 帧的状态来投票,而不是看到某一帧角度超了就报警。

import numpy as np class FallDetector: def __init__(self, angle_thresh=60, vel_ratio=0.03, window=6, aspect_thresh=1.4): self.angle_thresh = angle_thresh self.vel_ratio = vel_ratio self.window = window self.aspect_thresh = aspect_thresh self.history = [] def _torso_angle(self, kp): # kp 是 MediaPipe 33 点坐标数组,这里取肩膀和髋部 shoulder = np.mean([kp[11][:2], kp[12][:2]], axis=0) hip = np.mean([kp[23][:2], kp[24][:2]], axis=0) vec = shoulder - hip if np.linalg.norm(vec) < 1e-6: return 0.0 cos_angle = np.dot(vec / np.linalg.norm(vec), [0, -1]) return np.degrees(np.arccos(np.clip(cos_angle, -1, 1))) def update(self, kp, bbox, frame_h): if kp is None or bbox is None: self.history.clear() return False angle = self._torso_angle(kp) cx = (bbox[0] + bbox[2]) / 2 cy = (bbox[1] + bbox[3]) / 2 aspect = (bbox[2] - bbox[0]) / max(1e-3, bbox[3] - bbox[1]) self.history.append((angle, cy, aspect)) if len(self.history) > self.window: self.history.pop(0) if len(self.history) < self.window: return False avg_angle = np.mean([h[0] for h in self.history]) cy_start = self.history[len(self.history) // 2][1] cy_now = self.history[-1][1] v_pix = (cy_now - cy_start) / (len(self.history) // 2) if v_pix > frame_h * self.vel_ratio and avg_angle > self.angle_thresh: return True if self.history[-1][2] > self.aspect_thresh: return True return False

这里的frame_h不是原始图像高度,而是当前目标框所在帧的高度,用来把速度阈值换算成像素值。如果你直接把速度阈值写成1.2这种绝对值,换一个分辨率完全不同的视频,整套阈值就废了。代码先取窗口中间帧和最新帧的中心 y 坐标差,除以间隔帧数,得到每帧平均下坠像素数;再和frame_h * 0.03比较。这个按帧高比例归一化的做法,是避免换视频后重调的后悔药。

window=6这个参数的意思是:必须攒满 6 帧,且 6 帧里的平均倾角大于 60°、平均下坠速度超阈值,才触发一次报警。这样单帧抖动、姿态估计的偶发跳变都被吃掉了。如果你把 window 调成 3,报警更灵敏,但弯腰捡东西也更容易误报;调成 10 则相反,可能真摔完后过两秒才报警,实时性就没了。

3.3 阈值怎么标定:对演示视频标一标,别凭感觉

拿到带 demo 的源码,第一件事不要直接跑,先把两三段 avi 里的正常走路和摔倒片段各截十几帧,把上面的角度、速度、宽高比都打印出来,看数值分布。正常走路时角度通常在 10°~40°,突然转头或者伸手够东西时会冲到 55° 左右,但下坠速度很低;真正的摔倒角度会很快超过 70°,而且髋部中心点位置是连续往下走的。宽高比兜底也有个坑:人正对镜头摔倒时框宽高比变化明显,但侧对镜头或者被桌子挡了一半,宽高比可能只有 1.2,所以别把aspect_thresh设得太高,1.4 是我试下来比较稳的值。

阈值标定这步没有捷径,就是用枚举法跑几遍:角度 50、55、60、65 各试一轮,每次记录误报和漏报次数,选一个分界线。源码包里实验报告如果写清了测试视频内容,也照着它的场景复现一遍,就能知道报告里的参数是不是真的能用。

4. 环境与复现:从解压到跑出第一段告警视频

这章的落地目标是让你在一小时内把源码跑起来,看到画面里出现绿色框和 FALL 告警。买这个源码包回来却不能运行,绝大多数不是代码问题,而是环境和路径问题。解压后先把文件夹重命名成英文,比如fall_detection_src,然后建独立虚拟环境,按依赖装库,再去看主入口。

4.1 创建虚拟环境并锁依赖

课程设计源码最怕的就是全局环境里装了一堆包,互相把版本搞坏。我习惯先建一个干净的虚拟环境:

python -m venv fall_env # Windows fall_env\Scripts\activate # Linux / macOS source fall_env/bin/activate

依赖文件在源码包里一般叫requirements.txt,如果没有,就按下面这份常用的来装:

opencv-python>=4.5.5 numpy>=1.21.0 mediapipe>=0.8.9 torch>=1.10.0 torchvision>=0.11.1 ultralytics>=8.0.0

需要注意torch默认会装 CPU 版本,对这套课设来说完全够用;如果你有 NVIDIA 显卡想提速,再单独装对应 CUDA 版本的 torch,不要直接从默认源装,否则很容易把依赖关系冲垮。装完后用pip list看一眼版本,重点确认opencv-python和mediapipe都正常 import。

4.2 目录结构:源码包拆开后建议保持这个骨架

拿到压缩包我先按下面的结构整理,这样能快速定位主程序、权重和报告:

文件 / 目录作用
main.py或detect_fall.py主入口,负责读视频、调检测和姿态、输出告警
models/YOLO 权重文件,比如yolov8s.pt、yolov5s.pt
utils/姿态关键点处理、告警日志等辅助函数
data/演示视频目录,一般放着 data (1).avi 等多段视频
实验报告.docx或.pdf课程设计报告,里面有流程图、原理说明和测试结果

怎么看出哪个是主入口?文件名里带fall、detect、main的优先打开。打开后找argparse或if __name__ == "__main__"代码块,看它接收哪些命令行参数,这决定了启动命令长什么样。有的源码把主入口写成python main.py --source data/data (1).avi,有的直接用--video,参数名不一样,先看代码再跑会少走很多弯路。

4.3 跑一段演示视频并看懂输出

确认主入口后,用这样一条命令启动:

python main.py --video "data/data (1).avi" --save "result/data1_out.mp4"

如果输出格式是 avi,也可以改成data1_out.avi。程序跑起来后,命令行会逐帧打印检测到的目标数和关键点置信度。当检测到摔倒时,窗口画面的目标框会变红,旁边出现 FALL 文本;保存下来的视频里同样能看到这些标记。如果报unrecognized arguments,说明这个入口不叫--video,回去看argparse里的定义,换成它认的参数名即可。

提示:视频路径里如果带空格或括号,一定要用引号包起来。源码里的data (1).avi文件名本身带空格和括号,直接裸敲进命令很容易被 shell 拆掉。

跑通第一段视频后,再跑 data (3).avi、data (5).avi 这些不同场景,观察同一个阈值下有没有误报或漏报。这一步其实是变相验证实验报告里写的准确率是不是真的,也为你后面调参提供了原始依据。

5. 避坑实测:路径中文、视频读空、误报漏报的五个典型翻车

每个课设源码包都有几处“第一眼看不出来,跑起来才知道是坑”的地方。下面这五条是我拆这类项目时真实踩过的,每条都按现象、原因、解决整理好,你对照着排查可以省下不少时间。

5.1 环境问题:路径中文和依赖互踩

现象:解压后文件夹名叫“基于计算机视觉的目标检测与姿态分析实时防摔倒预警系统”,运行后报ModuleNotFoundError,甚至 torch 加载权重时报No such file or directory。

原因:MediaPipe、PyTorch 对非英文路径的支持很差,很多课设代码里还写死了相对路径。中文路径会导致模型文件找不到,或者 Python 的编码解析直接把路径变成乱码。

解决:解压后立刻重命名成纯英文,比如fall_detection_src,项目路径里也不要出现任何中文目录。代码文件本身如果有中文注释,文件头加上# -*- coding: utf-8 -*-,否则 Python 3 在特定系统区域设置下读源码也可能崩。

现象:装完 requirements.txt,import cv2 正常,但 import mediapipe 时报 DLL load failed,或者一段时间后 numpy 版本被某个库悄悄改了,导致计算报错。

原因:全局环境里以前装过 TensorFlow、OpenPose 之类的库,它们对 numpy、protobuf 的版本要求和 mediapipe 冲突,pip 安装时只能保住最后装的那个,前面的就坏了。

解决:用第 4 章的虚拟环境重来一遍,别在全局环境里硬怼。装依赖时按torch → opencv → mediapipe → ultralytics的顺序装,每装一个就 import 一次,能快速定位是谁把环境搞坏的。

5.2 推理问题:视频读空和无休止误报

现象:cap.read()不报错,但返回的 frame 是 None,程序跑几秒就闪退;或者打开 data (1).avi 时直接是黑屏。

原因:OpenCV 构建时不一定带全所有解码器,老式 avi 容器里的 MJPEG、DV 编码在某些 Windows 版本下读不出来。很多课设源码用了cv2.imshow,一旦读到空帧就会触发!_src.empty()断言直接退出。

解决:先用 ffmpeg 把 avi 转成 mp4,再改代码里的视频路径:

ffmpeg -i "data/data (1).avi" -c:v libx264 -crf 23 "data/data1.mp4"

转完再跑,或者代码里cap = cv2.VideoCapture("data/data1.mp4")。如果不想转码,也可以在read()后面加一行判断,空帧直接 break,至少不会闪退。

现象:人在画面里正常走,弯腰系鞋带时触发了 FALL 报警;真摔倒了反而没反应。

原因:阈值只用了单帧倾角,或者窗口帧数太少。弯腰时躯干角度能轻松到 50°以上,和摔倒早期的角度曲线非常像;而真摔时,人倒地瞬间姿态估计会失败,关键点突然消失,窗口里的角度数据变成乱跳,导致漏报。

解决:用第 3 章的滑动窗口投票,并叠加重心速度条件;关键点消失时不要清零历史窗口,而是保留最近一次有效角度和当前框宽高比做兜底。遇到漏报,先看日志里倒地那几帧的关键点置信度是不是明显低于 0.5,是的话就把角度阈值放宽几度,让宽高比兜底尽早介入。

现象:人还没倒地,程序已经报警,而且触发点总是在走路摆动幅度比较大的时候。

原因:可能是目标框把两个人一起框进去了,姿态网络返回的关键点横跨两个人,肩膀和髋部连线是对角线,倾角自然很大。

解决:目标检测阶段只保留置信度最高、面积最大的人框,避免多人互相干扰。如果源码里对每个人框都跑姿态,那先写一个“每帧只处理最大框”的过滤逻辑,课设场景一个人摔倒就够演示了,不需要多人跟踪那套复杂度。

6. 进阶调优:四个验证手段让课设从“能跑”到“能答辩”

源码跑通只是第一步,真正让老师认可的是你能说清楚“它为什么准、误报从哪来、参数为什么这么设”。我拆完这套源码后,会在实验报告之外再补四个验证手段。

6.1 给演示视频做定量统计

对每一段 avi,人工把“真摔倒”的时间段标出来,再对比程序告警时刻,统计出四类结果:正确报警、漏报、误报、提前报警。做成一张表,比任何截图都有说服力。

# 统计口径:把告警帧和人工标注的真摔时间段做交集 # tp: 告警帧落在真摔区间内 # fp: 告警帧落在正常走路区间内 # fn: 真摔区间内没有任何告警帧

课设答辩时老师最常问的就是“准确率怎么算的”,别回答“我觉得挺准”。用这个统计口径分别跑三段正常视频和三段摔倒视频,得到的准召率就是报告里硬指标的数据来源。

6.2 把报警结果落成日志和轨迹文件

程序里每次触发 FALL 时,把当前帧号、躯干角度、重心速度、目标框宽高比写进一行 CSV。调参时回看这些数据,能直接找到误报那几帧到底卡在哪个阈值上。实验报告里附上角度曲线和速度曲线截图,会显得你确实研究了核心逻辑。

6.3 用曲线定阈值,而不是靠感觉

把正常动作和摔倒动作各跑一遍,导出每一帧的角度和速度,用 Excel 或 matplotlib 画两条折线。正常动作的曲线峰和摔倒的曲线峰之间的分界,就是最合适的阈值;如果两条曲线有交叠,说明单靠角度不够,必须加速度条件。这个验证方法对任何阈值型算法都适用。

从那以后,我每次拿到课程设计源码包,都强制走一遍“先锁依赖版本、再裸跑原视频、最后才动阈值”的流程。源码包的代码再花哨,跑不通全是零;反过来,只要流程跑顺了,阈值和可视化都是可以迭代的问题。希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询