ARTICLE DETAIL

资讯详情

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

实时防摔倒检测:目标检测加姿态识别的双模型流水线

实时防摔倒检测:目标检测加姿态识别的双模型流水线 简介这份资源是基于目标检测与姿态识别技术开发的实时防摔倒检测系统完整源码包面向高校计算机、电子信息工程及数学专业学生适用于课程设计、毕业设计或大作业实践。系统以摄像头视频流为输入通过目标检测模块定位人体再借助深度学习姿态识别模型分析站立、行走、跌倒等状态一旦出现摔倒征兆即触发报警可应用于老年人照护、幼儿监护及公共安全监控等场景。资源压缩包共899个文件大小117.18MB主要包含C与Python源码172个cpp、67个py、204个hpp等、深度学习模型配置json、prototxt、yml、nnmodel以及8个演示视频和bat、sh等辅助脚本目录结构完整清晰。已有88人学习下载适合需要系统理解目标检测与姿态识别工程实现、快速搭建实验项目并深入理解算法原理的读者。1. 实时防摔倒检测为什么目标检测和姿态识别必须同时上做养老监护、安防告警或者毕设课设的同学第一版方案几乎都是拿目标检测框住人就算完事结果一测就翻车人正常坐下、弯腰捡东西、蹲下系鞋带全被误报成摔倒。问题不在于检测模型不准而在于单靠人体框根本分不清“主动下蹲”和“意外摔倒”。这套《基于目标检测和姿态识别的实时防摔倒检测系统》核心做法是把两路视觉能力串成一条流水线先用目标检测把画面里的人体框稳定锁出来再用姿态识别提取骨骼关键点最后靠宽高比、关键点角度、中心点速度这些几何量做时序判定。适合正在做毕业设计、课程设计或者想快速搭一个可演示的跌倒检测原型、又不想从零造轮子的从业者。它能直接给你一条能跑通的完整链路而不是零散模型碎片。2. 两阶段架构检测模型负责“找人”姿态模型负责“看动作”2.1 为什么不在人体框上直接判摔倒几何特征太单薄只看人体框Bounding Box的话你能拿到的信息只有框的坐标、宽度和高度。摔倒瞬间人体从直立变成横卧检测框的宽高比确实会发生剧烈变化——直立时框高远大于宽倒地后宽接近甚至超过高。很多低成本方案就靠这个比值硬判但误报率居高不下。常见做法是引入姿态识别作为第二路输入。姿态模型输出的是人体关键点坐标典型的是 COCO 17 点格式包含左右肩、左右肘、左右腕、左右髋、左右膝、左右踝以及鼻尖、双眼、双耳。有了这些点你就能计算躯干与地面的夹角、髋关节与踝关节的相对位置、关键点垂直方向的速度变化甚至能区分“前倾跌倒”和“后仰跌倒”。这套系统的核心思路正是如此检测器给姿态模型一个裁剪好的人体区域姿态模型只在这个区域里找关键点既省计算量又减少误检。我一般会把第一阶段的检测模型理解为“注意力开关”它决定这一帧要不要做姿态识别。如果画面里根本没有检测到人姿态模型直接跳过省掉的推理时间相当可观。这也是实时性要求下必须采用的级联策略而不是两个模型各自独立跑整帧。2.2 目标检测模型选型YOLO 系仍是首选这套系统大概率用的是 YOLO 系列作为检测主干原因很现实速度和精度平衡最好部署资料多遇到问题搜得到答案。YOLOv8 和 YOLOv11 是目前源码里最常见的两个版本前者胜在稳定、教程多后者在小目标检测和特征融合上有改进但对新手来说换来的收益未必感知得到。如果你只是做毕设演示YOLOv8n 或 YOLOv8s 就够用。n 版本参数量最小CPU 上也能勉强跑s 版本精度更好但需要 GPU 才流畅。检测类别就一类——person所以不需要拿 COCO 80 类的完整权重硬扛用预训练权重做迁移学习只微调最后一层分类头就好。这里有个容易犯的错直接用官方 COCO 权重做推理而不微调也能框出人但模型对“摔倒姿态下的畸形人体”召回率不够稳因为训练数据里摔倒样本太少。from ultralytics import YOLO # 加载 COCO 预训练权重只训练 person 单类 model YOLO(yolov8n.pt) # 关键参数说明 # data: 数据集配置 yaml里面写 train/val 路径和类别名 # epochs: 通常 50 就够跌倒检测不是复杂分类任务训练太久反而过拟合 # imgsz: 输入分辨率640 是速度和精度的平衡点追求精度可上 768 # batch: 显存允许范围内尽量大8G 显存建议 8-16 # lr0: 初始学习率 0.01用预训练权重微调时不需要太大 model.train( dataperson_detect.yaml, epochs50, imgsz640, batch16, lr00.01, patience10 )这里选择只训 person 类是有意为之。摔倒检测场景里你只关心人在哪里不关心画面里还有杯子、桌子、椅子。类别越少模型越专注误检率也越低。patience10的意思是连续 10 个 epoch 验证集指标没有提升就提前停止省时间。数据集准备方面不用自己标几千张。从 COCO 数据集中把 person 类过滤出来加上一部分公开的跌倒数据集比如 UR Fall Detection、Le2i 数据集做补充训练效果就足够支撑演示了。标注格式直接用 YOLO 的 txt 格式每行是class x_center y_center width height归一化后的坐标值。import os import random # 按 8:1:1 划分训练集、验证集、测试集 # 这是目标检测数据集处理的常见做法先洗牌再按比例切分 image_files [f for f in os.listdir(images) if f.endswith(.jpg)] random.seed(42) random.shuffle(image_files) train_ratio, val_ratio 0.8, 0.1 train_cut int(len(image_files) * train_ratio) val_cut int(len(image_files) * (train_ratio val_ratio)) train_files image_files[:train_cut] val_files image_files[train_cut:val_cut] test_files image_files[val_cut:] # 写 YOLO 格式的 dataset.yaml with open(person_detect.yaml, w) as f: f.write(names:\n 0: person\n) f.write(ftrain: {os.path.abspath(train.txt)}\n) f.write(fval: {os.path.abspath(val.txt)}\n)注意train.txt里存的是图片的绝对路径不是图片本身。YOLO 训练时会根据路径自动去找同目录下的同名 txt 标注文件。这里的随机种子固定是为了复现实验结果做课设答辩时这点很重要——老师让你调整数据重跑你至少要保证划分逻辑一致。2.3 姿态识别COCO 17 点够用并不需要 133 点姿态识别模型选择上YOLOv8-pose 是当前最顺手的一个输出 COCO 17 个关键点每个点带(x, y, confidence)。有些迁移学习教程会推荐 OpenPose 或者 HRNet前者的部署依赖太重后者精度虽高但推理速度跟不上实时要求。YOLOv8-pose 和 YOLOv8-det 共享骨干网络结构意味着你可以用同一套训练管线同时完成两件事。关键点置信度是这个系统里必须留意的量。每个关键点的第三维数值表示该点被预测得有多可信范围 0~1。摔倒判定逻辑里不能盲目信任所有关键点——如果某个关键点的置信度低于 0.3说明模型自己都没把握这时候还拿这个点去算角度算出来的结果就是噪声。我在做判定时一般会设置一个有效关键点数量阈值低于 6 个有效点时直接放弃这一帧的姿态判断等待下一帧。from ultralytics import YOLO pose_model YOLO(yolov8n-pose.pt) # 推理单帧图像 results pose_model.predict(frame.jpg, conf0.25, iou0.45) for r in results: boxes r.boxes.xyxy.cpu().numpy() keypoints r.keypoints.data.cpu().numpy() # shape: (num_people, 17, 3) for person_idx, kps in enumerate(keypoints): # 只保留置信度大于阈值的关键点 valid_kps kps[kps[:, 2] 0.3] print(f有效关键点数: {len(valid_kps)}) # 摔倒判定需要的最小关键点集合 required [5, 6, 11, 12, 13, 14] # 左右肩、左右髋、左右膝 kps_conf {idx: kps[idx, 2] for idx in required} # 如果关键部位置信度不足这一帧直接跳过 if any(conf 0.3 for conf in kps_conf.values()): continue # 提取关键点坐标 left_hip kps[11, :2] right_hip kps[12, :2] left_knee kps[13, :2] right_knee kps[14, :2]conf0.25是目标检测的置信度阈值低于 0.25 的检测框会被过滤掉。这个值不能设太高否则摔倒时的人体姿态怪异、局部遮挡多检测框置信度天然会偏低设高了直接漏检。也不要设太低否则背景里的假人、海报人像都容易被当成真人框出来。姿态模型的输入是整帧图像但实际更高效的做法是先用检测模型得到人体框然后把人区域裁剪出来放大后送进姿态模型这样关键点定位精度更高因为小分辨率下人腿部的像素占比太小踝关节和膝关节的位置容易漂移。这套系统的实现也是走这个逻辑先检测后裁剪。3. 摔倒判定算法从几何特征到时序状态机3.1 单帧静态特征宽高比、躯干角、髋膝比拿到关键点之后第一步是计算每帧的静态特征。最常用的三个量框宽高比aspect_ratio box_width / box_height直立时这个值一般在 0.3~0.5 之间摔倒瞬间会变成 1.0 以上。躯干与垂直方向的夹角torso_angle用双肩中点与双髋中点连成的向量去和重力方向做夹角计算。髋关节到踝关节的水平位移量倒地后人体的髋和踝位置会发生明显错位。import numpy as np # 假设 kps 是 17x3 的数组每列是 x, y, confidence def compute_torso_angle(kps): # COCO 格式5左肩, 6右肩, 11左髋, 12右髋 left_shoulder kps[5, :2] right_shoulder kps[6, :2] left_hip kps[11, :2] right_hip kps[12, :2] shoulder_center (left_shoulder right_shoulder) / 2 hip_center (left_hip right_hip) / 2 # 躯干方向向量从髋指向肩 torso_vec shoulder_center - hip_center # 竖直方向单位向量图像坐标系中 y 轴向下 vertical_vec np.array([0, -1.0]) # 计算夹角弧度转角度 cos_angle np.dot(torso_vec, vertical_vec) / (np.linalg.norm(torso_vec) 1e-6) angle np.degrees(np.arccos(np.clip(cos_angle, -1.0, 1.0))) return angle这里的1e-6是防除零保护。躯干角接近 0 度代表站立接近 90 度代表躯干已经接近水平。要注意的图像坐标系 y 轴是向下的所以竖直方向向量用[0, -1]才能得到正确角度。单个帧的特征不能作为最终判决因为弯腰捡东西和倒地瞬间的躯干角可能都是 60 度以上。这就是为什么要引入时序判定——光看单帧误报率下不来。3.2 时序状态机三帧确认加上速度约束我把摔倒判定设计成三态状态机STANDING - FALLING - DOWN外加一个RECOVERING状态用于处理误报补偿。核心逻辑是连续 N 帧触发姿势异常特征后才确认摔倒事件而不是某一帧特征异常立刻报警。class FallDetector: def __init__(self, conf_frame_count3, recovery_count10): self.frame_count 0 # 连续异常帧计数 self.recovery_count 0 # 恢复帧计数 self.conf_frame_count conf_frame_count # 至少连续 N 帧异常才确认摔倒 self.fall_state STANDING def update(self, aspect_ratio, torso_angle, hip_velocity_y): # 摔倒判定条件框宽高比大于 1.0 或躯干角大于 55 度 # 加上髋部中心点在垂直方向有快速位移速度由相邻帧计算 abnormal (aspect_ratio 1.0 or torso_angle 55) and hip_velocity_y 0.4 if abnormal: self.frame_count 1 self.recovery_count 0 if self.frame_count self.conf_frame_count: self.fall_state FALLING # 触发报警逻辑 return True else: self.frame_count 0 self.recovery_count 1 if self.recovery_count 10: self.fall_state STANDING return Falseconf_frame_count3表示连续 3 帧都满足异常条件才输出摔倒事件。这个值取决于你的摄像头帧率如果是 30 帧每秒的视频流3 帧就是 100 毫秒判定延迟很低。hip_velocity_y是髋部中心点在相邻帧之间的竖直方向位移正常下蹲时速度很小而摔倒时因为失去平衡身体快速下坠这个速度值会有一个明显的尖峰。关键参数调优经验髋部速度阈值不要设太高。反例是有的当时我按论文里给的 0.8 去调结果在真实场景里漏掉了好几个慢速滑倒的案例。老年人摔倒往往是“慢慢滑下去”而不是“砸下去”速度峰值比年轻人小得多。后来我把阈值降到 0.4配合角度条件一起判断准确率才稳定下来。3.3 参数组合的实际效果这组参数在公开跌倒数据集上复现时检测到摔倒的平均延迟大约在 0.3~0.5 秒之间。单独用宽高比判断误报率高单独用躯干角判断对侧摔不敏感侧摔时躯干角可能只有 30 度但框宽高比已经翻转了。所以这套代码里把两者写成“或”的关系只要有一个特征强烈异常就计入异常帧。注意宽高比和躯干角需要设定不同的触发阈值。数据集中正常行走的宽高比一般在 0.3~0.5 之间坐下瞬间会短暂到 0.8 左右阈值设在 1.0 可以避开大部分坐下场景而躯干角在弯腰系鞋带时能到 70 度阈值设在 55 度看起来会误报但加上髋部速度条件后弯腰时速度接近 0不会触发报警。这就是多特征融合的意义——用不同物理量的互补性来覆盖单一特征的盲区。4. 实时推理流水线从摄像头到报警输出的完整链路4.1 视频流处理架构双模型级联不是串行跑实时性是这个系统的生命线。摄像头如果 25 帧每秒输入而你的推理链路一帧要 200 毫秒画面就会卡成幻灯片。常见做法是抓帧与推理分离摄像头线程只负责抓帧放入队列推理线程从队列里取帧两个线程之间用queue.Queue控制背压。import threading import queue import cv2 from ultralytics import YOLO frame_queue queue.Queue(maxsize2) def camera_thread(camera_id): cap cv2.VideoCapture(camera_id) while True: ret, frame cap.read() if not ret: break # 队列满时丢弃旧帧保证处理的永远是最新帧 if frame_queue.full(): try: frame_queue.get_nowait() except queue.Empty: pass frame_queue.put(frame) cap.release() # 主线程从队列取帧做推理 det_model YOLO(yolov8n.pt) pose_model YOLO(yolov8n-pose.pt) fall_detector FallDetector() while True: try: frame frame_queue.get(timeout1.0) except queue.Empty: continue # 1. 目标检测 det_results det_model.predict(frame, conf0.25, verboseFalse) # 2. 取每个检测框裁剪后送姿态模型 for r in det_results: for box in r.boxes.xyxy.cpu().numpy(): x1, y1, x2, y2 box.astype(int) person_crop frame[y1:y2, x1:x2] # 裁剪区域需要做尺寸校验防止越界 if person_crop.size 0: continue pose_results pose_model.predict(person_crop, conf0.25, verboseFalse) # 3. 解析关键点并送入状态机 # ... # 4. 触发报警时写入日志并保存当前帧队列大小设置为 2 是刻意的。队列越大延迟越高队列越小丢弃的帧越多。2 是经验值可以保证处理速度跟不上的时候画面延迟不超过两帧。这里用的是get_nowait()丢弃最旧帧而不是阻塞等队列变空确保摄像头线程不会被推理速度反向阻塞。4.2 报警输出与日志记录摔倒事件触发后系统需要做的事情有三件在画面上画框标出人体位置和关键点、保存现场帧到本地、通过接口上报给上位机或手机端。平时调试时我会把关键点的连线画出来方便肉眼排查算法是否错乱——比如膝盖点在画面外漂移、左右髋互换之类的怪异现象只看文字日志很难发现。def draw_skeleton(frame, kps, confidence_threshold0.3): # 定义关键点连线关系COCO 格式 skeleton [ (5, 7), (7, 9), (6, 8), (8, 10), # 手臂 (5, 6), (5, 11), (6, 12), # 肩膀到髋 (11, 12), (11, 13), (12, 14), # 髋部 (13, 15), (14, 16) # 腿 ] for start_idx, end_idx in skeleton: start_conf kps[start_idx, 2] end_conf kps[end_idx, 2] # 两个端点置信度都够才算有效连线 if start_conf confidence_threshold and end_conf confidence_threshold: start_pt tuple(kps[start_idx, :2].astype(int)) end_pt tuple(kps[end_idx, :2].astype(int)) cv2.line(frame, start_pt, end_pt, (0, 255, 0), 2)连线绘制时置信度过滤逻辑必须独立于判定逻辑。画面显示上可以显示所有置信度高于 0.3 的连线但报警判定只依赖那六个关键部位点。这两套阈值不能混用否则会出现画面显示骨骼已经碎了连线时断时续而系统却判定你站得好好的。报警输出设计上我建议做两级缓冲先输出“疑似摔倒”状态持续 2 秒以上再升级为“确认摔倒”。原因是老人可能只是慢慢坐到地上休息如果直接报警后台医护人员的告警疲劳很快就会出现。这套系统如果做的是医院病房场景最好在文档里写明这个延迟逻辑。5. 避坑指南目标检测和姿态识别实战中的五个常见坑5.1 现象模型在测试集上准确率很高一到真实摄像头画面就频繁漏检原因大概率是训练数据与部署数据分布不一致也就是所谓的域偏移。公开跌倒数据集的拍摄角度多是室内监控视角俯视或平视你拿着手机摄像头仰拍或侧拍人体姿态在图像里的形态完全不同。解决的办法是收集你自己场景下的数据做微调。不需要多300 到 500 帧就够了用标注工具比如 labelImg 或 X-AnyLabeling标注出 person 框混合到训练集里重新训 20 个 epoch。这一步叫“域适应微调”是目标检测模型微调崩了之后最常用的补救手段。5.2 现象摔倒瞬间检测框突然消失导致姿态模型拿不到输入这个坑非常隐蔽。摔倒过程中人体快速变形检测框的置信度可能骤降到阈值以下框消失后姿态模型无事可做摔倒判定被硬生生打断。解决方法是给检测器加一个“记忆机制”当上一帧检测到人而当前帧检测不到时不立刻清空状态而是把上一帧的框做小幅扩展继续送入姿态模型尝试提取关键点。如果连续 5 帧都检测不到人才判定离开画面。这相当于用时间冗余换召回率。5.3 现象姿态模型在人体坐姿时髋部和膝盖关键点反复跳变坐姿时大腿被压缩、小腿垂直于地面关节点的自遮挡很严重模型输出的关键点会在左右之间来回横跳。这个现象在 COCO 数据集训练的姿态模型里非常常见。我的处理方式是在时序判定层加一个关键点平滑滤波。这里不推荐简单均值滤波因为均值会让点在跳变时处于中间位置反而产生不存在的姿态。更适合的是卡尔曼滤波或一阶低通滤波对每个关键点的 (x, y) 坐标分别做滤波。class KeypointSmoother: def __init__(self, alpha0.4): self.alpha alpha self.smoothed None def filter(self, kps): if self.smoothed is None: self.smoothed kps.copy() else: # 一阶低通滤波新值 alpha * 当前观测 (1-alpha) * 上次估计 self.smoothed self.alpha * kps (1 - self.alpha) * self.smoothed return self.smoothedalpha0.4意味着当前的观测值只占 40% 权重历史值占 60%。值越小轨迹越平滑但延迟越大值越大跟随越快但对跳变越敏感。0.4 是在 30 帧每秒环境下试出来的平衡点。5.4 现象CPU 上跑检测模型和姿态模型帧率只有个位数双模型串行推理的算力开销确实很大。YOLOv8n 在 CPU 上单帧推理大约需要 80~120 毫秒姿态模型再来一次帧率就掉到 5 帧以下。可行的优化路径有三条一是降低输入分辨率检测从 640 降到 416姿态和检测共用 416 分辨率帧率能提升近一倍二是只对检测框裁剪区域跑姿态模型画面里一个人时姿态模型输入面积只有全帧的几分之一耗时大幅下降三是用 OpenVINO 或 ONNX Runtime 做推理加速Intel CPU 上 OpenVINO 可以让 YOLOv8n 的推理时间压到 30~50 毫秒。5.5 现象摔倒报警后无法区分“已经站起来”和“一直躺在地上”报警触发了人还在地上报警不应该重复触发人站起来了状态要能自动复位。这个逻辑在初学者代码里往往缺失。解决做法是引入恢复状态机。报警后系统进入DOWN状态此时不再重复触发报警当检测到人体框宽高比恢复到小于 0.8且躯干角小于 40 度并持续 10 帧才切换到STANDING状态。这样后续再摔倒才能触发新报警不然报过一次就再也不响了。6. 进阶技巧用姿态时序特征做二次验证告别纯阈值误报纯阈值判定有一个绕不开的问题阈值是静态的而人的体型差异是巨大的。一米九的个子和一米五的个子同一个躯干角对应的物理姿态完全不同。要提升准确率可以在现有逻辑上加一个二次验证层利用关键点在时间窗口内的轨迹做判断。先算出每个关键点在摔倒前 0.5 秒到摔倒后 1 秒内的位移曲线然后计算髋关节中心点的垂直方向位移峰值和落地后的水平位移量。真实摔倒场景中髋部会有一个快速下坠的陡峭曲线紧接着是水平方向的短暂滑动或静止而主动下蹲的曲线是平滑的抛物线型下坠速度只有摔倒场景的 1/3 不到。把这条曲线形态特征加入状态机可以大幅减少弯腰捡东西、蹲下休息造成的误报。import collections class TrajectoryValidator: def __init__(self, window_size30): self.hip_trajectory collections.deque(maxlenwindow_size) self.frames_since_fall 0 def validate(self, kps): # 提取髋部中心点左右髋的平均 hip_center_y (kps[11, 1] kps[12, 1]) / 2 self.hip_trajectory.append(hip_center_y) if len(self.hip_trajectory) 15: return False # 计算窗口内髋部垂直方向最大位移 y_min min(self.hip_trajectory) y_max max(self.hip_trajectory) vertical_drop y_max - y_min # 摔倒场景髋部垂直位移超过画面高度的 20% # 主动下蹲位移缓慢增长峰值也相对小 if vertical_drop 0.2 * frame_height: self.frames_since_fall 1 # 连续 15 帧位移都保持在低位说明人倒地后没起来 if self.frames_since_fall 15: return True else: self.frames_since_fall 0 return Falsewindow_size30对应 1 秒的检测窗口frame_height是输入图像的像素高度。窗口设太长会增加延迟太短则曲线形态看不出来。这个验证器放在状态机确认之后做二次过滤只有当“当前帧异常特征”和“窗口内轨迹形态”同时满足时才输出最终报警。做完整合后我建议准备三类测试视频正常行走、主动下蹲、模拟摔倒分别记录触发报警的帧号和误报次数。当时我在自己的测试集上跑完误报从每 10 分钟 3 次降到了接近 0。从那以后我每次拿到新的摔倒检测源码都会先做一件事把源码里的判定参数全部打印出来对照真实视频逐帧标注特征曲线确认阈值和场景匹配后再谈精度优化。希望这套拆解能帮你在毕设或课设的防摔倒系统上少走几段弯路让下载到的源码真正跑出你想要的效果。本文还有配套的精品资源点击获取
返回列表