从事智能安防和智慧养老方向开发的同行们,应该都遇到过类似的诉求:能不能在老人跌倒之后几秒内就告警?能不能在动作发生之前就发现异常趋势?这个标题里的“摔倒与预测系统”其实就是把“事后检测”往前推了一步,变成“事后检测+事前预警”。它的核心不再是简单地判断“摔没摔”,而是通过深度学习模型持续分析人体姿态和运动轨迹,在摔倒动作发生的瞬间甚至发生前几帧给出信号。这篇文章我会从整体技术选型、模型结构、数据标注、训练部署到踩坑实录,完整拆解一套可落地的行人摔倒检测与预测系统实现方案。无论你是做社区监控、养老院看护,还是刚入门计算机视觉想找个完整项目练手,都可以参考这套思路。
1. 整体设计与技术路线拆解
1.1 问题定义:检测与预测是两件事
很多人一开始把“摔倒检测”想简单了,以为就是一个动作分类器,看到“摔倒”这个动作就报警。实际上摔倒不是一个瞬态动作,而是一个有前兆、有过程、有结果的事件链。正常的行走状态,重心比较高,躯干基本垂直于地面;而摔倒的前兆往往伴随着重心快速下移、躯干倾斜角度突变、腿部弯曲异常等信号。
所以这个项目要解决的是两个不同粒度的问题:
- 检测问题:在连续视频帧中识别出人体已经处于“倒地”或“正在跌倒”的状态;
- 预测问题:利用摔倒前若干帧的轨迹特征,提前判断出动作趋势,在完全倒地之前给出预警。
检测依赖空间特征,预测依赖时序特征。只做检测,告警往往滞后;只做预测,误报率会非常高。系统必须两个都做,而且要把两者串联在一条推理链路里。
1.2 为什么不直接用单阶段动作识别模型
市面上确实有直接基于视频流的动作识别模型,比如3D CNN、SlowFast、Video Swin Transformer这类。它们输入一段连续帧,直接输出动作类别。但我在实际项目里没有选它们,主要有几个原因:
实时性差。这类模型计算量非常大,尤其是3D卷积和视频Transformer,即使有GPU推理,也很难在普通边缘设备上跑到25FPS以上。而摔倒检测系统往往部署在监控室服务器或嵌入式盒子(Jetson、RK3588)上,算力有限。
样本依赖太重。视频级动作分类需要大量完整的摔倒视频片段,标注工作量和数据采集难度都高。相比之下,单帧目标检测和姿态估计的公开预训练模型非常多,迁移成本低。
检出结果不可解释。直接输出“摔倒”这一个标签,无法告诉运营人员“为什么判定摔倒”——是重心下降了,还是姿态角度突变?这在安防场景里很致命,误报之后没法排查。
所以最终方案采用了更工程化的两阶段结构:目标检测+姿态估计+时序分类。第一阶段定位人和关键点,第二阶段基于关键点序列做摔倒判别与预测。每一阶段的输出都有明确含义,出了问题也好定位。
1.3 整体系统架构
系统整体的数据流是这样的:
摄像头RTSP视频流拉取后,先做抽帧,再送入人体检测模型得到每个人的边界框;对边界框区域做姿态估计,提取33个骨骼关键点;把连续多帧的关键点坐标组合成时间序列,送入时序模型(LSTM或GRU),输出当前状态属于“正常行走”、“疑似跌倒趋势”、“正在跌倒”、“已经倒地”中的哪一类;最终由规则引擎结合连续帧投票和置信度,决定是否推送告警。
前端展示层支持实时视频标注、告警截图留存、历史轨迹回放。告警通道可以接微信、钉钉机器人,也可以接现场声光报警器。
这套架构的好处是每一层都可以独立替换。检测模型可以换更强的,姿态模型可以换更快的,时序模型可以调整窗口长度,规则引擎可以根据不同场景灵活配置阈值。对项目迭代和维护来说,这种模块化解耦非常重要。
2. 核心细节解析与关键模型选型
2.1 人体检测:用YOLOv8还是其他方案
人体检测是整个链路的第一环,它直接决定了后续姿态估计的输入质量。我选用的是YOLOv8n和YOLOv8s两个版本组合:边缘设备上用n,服务器上用s。
YOLOv8n在640×640输入下,单帧推理可以做到10ms-20ms(取决于具体硬件),精度在COCO人体类别上足够用。如果你有自建场景的数据,建议在公开权重基础上用你场景里的样本微调50-100个epoch,尤其要补充俯视摄像头、夜间红外画面、轮椅/拐杖行人这类特殊样本。
检测置信度阈值默认设0.5。但在场景实测中,0.5阈值会出现漏检半遮挡行人的情况,我最终调到0.35。原因是摔倒行为发生时常伴随身体重叠、遮挡,宁可多检出几个候选框交给姿态模型过滤,也不能漏掉真正的目标。
2.2 姿态估计:关键点不是越多越好
姿态估计我对比过三个方案:MediaPipe BlazePose、RTMPose、HRNet。
MediaPipe BlazePose输出33个关键点,运行速度极快,CPU上也能跑实时。但它在严重遮挡和侧卧姿态下,某些关键点的置信度会骤降,且33个点里包含大量面部点,对摔倒判别有用的主要是躯干和四肢点。
RTMPose是百度开源的,基于Transformer结构,精度和速度平衡很好,在AI推理框架(如OpenMMLab)生态里用起来很顺。RTMPose-m在单GPU上每秒可以处理几百帧姿态估计,对边缘设备也算友好。
HRNet精度高但对算力要求高,只适合服务器端离线分析。
实际项目里我推荐RTMPose-m作为主力,MediaPipe作为降级备用方案。关键点坐标在送入时序模型之前要做归一化:以髋部中心为原点,按边界框高度做尺度归一化。这样能消除行人远近和画面尺寸的影响。
2.3 核心判别特征:什么样的状态算摔倒
这是整个项目里最有价值的部分。姿态估计给了一堆坐标点,怎么把它们变成“摔倒”的判断依据?我从四个维度构建特征:
重心高度变化。取髋部中心点的y坐标(图像坐标,向下增大),计算它在滑动窗口内的变化速度和变化量。正常行走时髋部高度只有周期性小幅波动,幅度在0.1个身高以内。摔倒时,髋部在0.4-0.8秒内下移超过半个身高。
边界框宽高比突变。人在直立时,检测框高大于宽,宽高比在0.3-0.5之间。倒地时,框的宽度接近甚至超过高度,宽高比会大于1。这是一个非常强鲁棒的特征,即使关键点检测质量差,边界框比例依然可信。
躯干倾斜角度。通过双肩中心点和双髋中心点连线方向计算躯干与竖直方向的夹角。正常站姿下夹角小于30度,走路时不超过40度。摔倒时躯干角会在数帧内超过60度。
关键点置信度质量。摔倒动作发生过程中,身体与地面接触,部分关键点会被身体其他部分遮挡,置信度明显下降。这个信号可以作为辅助判据。
这四个特征会组合成一个特征向量,连同原始关键点坐标一起送入时序模型。为什么不直接把坐标丢给模型?因为纯坐标特征对场景变化太敏感,加入手工设计的高层特征后,模型泛化能力明显提升。
2.4 时序模型:LSTM、GRU还是TCN
摔倒检测本质上是时序分类,可选模型有LSTM、GRU、TCN(时序卷积网络)。我在不同数据量下对比过效果:
LSTM和GRU结构相近,GRU参数少一些,在小数据集上更稳。我用的是两层GRU,隐藏层维度128,输入序列长度20帧(约1秒),输出四个类别的概率分布。
TCN在长序列上表现更好,训练速度也快,但部署时需要额外处理感受野对齐,在工程上稍微复杂。
如果只做检测不做预测,单帧特征加规则就够用了;但要做预测,必须依赖时序上下文。我的经验是:先用规则生成预标注,再用预标注微调GRU,两者相互校验,比直接端到端训练效果更好。
3. 实操过程与核心环节实现
3.1 环境准备与依赖安装
我用的是Python 3.9 + PyTorch 2.0 + CUDA 11.8的组合。如果你用conda,环境配置可以这样:
conda create -n fall_detect python=3.9 conda activate fall_detect pip install torch torchvision --index-url https://download.pytorch.org/whl/cu118 pip install ultralytics opencv-python numpy pandas pip install onnxruntime tensorrt姿态估计部分如果直接用RTMPose,最简单的方式是通过OpenMMLab的pip包安装:
pip install mmcv==2.0.1 mmengine mmdet mmpose如果觉得OpenMMLab依赖太重,也可以用onnx部署RTMPose。我最终部署阶段是把姿态模型导出为ONNX,再用ONNX Runtime推理,放弃了MMLab的原生推理接口,因为后者依赖太多、版本容易冲突。
3.2 数据准备:公开数据加自采数据混合
训练摔倒模型最头疼的就是数据。公开的跌倒数据集数量有限,场景单一,角度多是侧视角。我主要用了两类数据的组合:
第一类是公开数据集。这类数据的典型场景是实验室环境,志愿者模拟摔倒,视角固定,背景简单。优点是动作标注干净,类别明确;缺点是场景太少,白天光线好,没有夜间和复杂监控场景。
第二类是自采数据。我在办公区走廊和一间模拟老年公寓的房间里,用两个不同角度的摄像头,让志愿者按脚本模拟:正常行走、弯腰捡东西、坐在椅子上、慢慢滑倒、突然摔倒、倒地后爬起。每个动作重复20次,共采集了约6小时视频。
标注格式上,我不用逐帧手工标注,而是用一种半自动策略:
- 先用检测+姿态模型跑一遍所有视频,生成关键点序列;
- 用预设规则(比如髋部高度骤降超过阈值)自动给视频片段打上“疑似摔倒”伪标签;
- 人工快速浏览伪标签片段,修正错误的帧区间和类别。
这样把标注成本降低了至少一半。最终训练集包含约800条正常视频片段、600条摔倒相关片段,每个片段长度在1-3秒。
3.3 模型训练细节
时序模型的输入是一个特征序列。每个时间步的特征包括:归一化后的髋部中心坐标、髋部高度变化速度、边界框宽高比、躯干倾斜角、所有关键点的置信度均值。特征维度总共约40维。
窗口长度选择:20帧。以25FPS计算,约0.8秒。这个长度既能覆盖完整的前兆动作,又不会引入太多历史冗余。窗口太长会影响预警速度,太短则预测不准。
标签设计:我划分为4类——normal(正常)、pre_fall(疑似前兆)、falling(正在倒地)、fallen(已倒地)。其中pre_fall和falling就是“预测”部分对应输出的类别。
训练参数参考如下:
| 参数 | 数值 |
|---|---|
| 输入序列长度 | 20帧 |
| 特征维度 | 40维 |
| GRU隐藏层 | 128 |
| GRU层数 | 2 |
| 全连接层 | 128 -> 64 -> 4 |
| 优化器 | AdamW |
| 初始学习率 | 0.001 |
| Batch Size | 64 |
| Epoch | 80 |
| Dropout | 0.3 |
| 损失函数 | CrossEntropyLoss |
训练时我做了seq-level的数据增强:对序列做时间轴小幅缩放、添加关键点坐标高斯噪声、随机丢弃个别帧。这些增强让模型的时序鲁棒性提升了不少。
核心训练代码片段:
import torch.nn as nn class FallPredictor(nn.Module): def __init__(self, input_dim=40, hidden_dim=128, num_classes=4): super().__init__() self.gru = nn.GRU(input_dim, hidden_dim, num_layers=2, batch_first=True, dropout=0.3) self.fc1 = nn.Linear(hidden_dim, 64) self.fc2 = nn.Linear(64, num_classes) self.relu = nn.ReLU() self.dropout = nn.Dropout(0.3) def forward(self, x): out, _ = self.gru(x) # x: [B, T, D] last = out[:, -1, :] # 取最后一个时间步输出 out = self.relu(self.fc1(last)) out = self.dropout(out) return self.fc2(out)训练完成后,模型在测试集上的分类准确率约94%,其中fallen类别的准确率最高,pre_fall类别的准确率约87%。后者偏低是正常的,因为前兆动作本身存在很大的个体差异,同一个“即将跌倒”的状态,不同的人表现完全不同。
3.4 规则引擎与告警逻辑
模型输出的是每个类别的概率,但实际判断不能只看单帧,否则误报率会高得离谱。我加了一个滑动窗口投票机制:
- 维护最近10帧模型输出的概率序列;
- 当falling或fallen类别的平均概率超过0.6时,触发“紧急告警”;
- 当pre_fall的平均概率超过0.5且持续5帧以上时,触发“早期预警”。
另外设置了两个规则约束:
地面距离约束。如果系统识别到画面背景中有多个固定参考点(比如床、地面边界),可以结合透视关系估算出人体中心点与地面的距离。当距离低于某个阈值且持续超过30帧时,标为“已倒地”。这个约束能过滤掉“弯腰捡东西但并没有倒地”的误报。
去向选择约束。如果时序模型输出pre_fall类别概率较高,但同时检测框显示人体正在移向椅子且高度下降平缓,可以判定为“坐下的过程”,不触发告警。这个逻辑需要用场景语义去补充,比如提前标定画面中的座椅区域。
告警推送我用的是HTTP Webhook,直接发JSON到告警网关,网关再转微信或短信。截图留存用OpenCV的imwrite在触发时保存原始帧和标注帧,文件名带上设备ID和时间戳,方便事后排查。
3.5 推理性能实测
在服务器端(一张普通专业显卡),整个推理链路:YOLOv8s检测约8ms,RTMPose-m姿态估计约12ms,GRU推理约2ms,加上图像预处理和通信开销,单路视频流整体推理延迟约25ms,折算吞吐约40FPS。
边缘设备上(如Jetson Orin Nano),我做了INT8量化后,YOLOv8n大约15ms,RTMPose大约20ms,整体能稳定跑25FPS。CPU设备(x86工控机)上只能跑到15FPS左右,作为备用部署方案。
4. 常见问题与排查技巧实录
4.1 弯腰捡东西被误判为摔倒
这是频率最高的误报。弯腰时髋部高度也会下降,躯干倾斜角也会变大,和摔倒前兆很接近。我的处理方法是组合两个信号:
第一个是“高度变化速度”。正常弯腰捡东西,髋部高度下降速度不会特别快,整个过程在0.5秒以上;而摔倒时速度和加速度都明显更大。我用滑动窗口内髋部高度的一阶差分均值来判定,速度超过0.3个身长/秒时才考虑摔倒。
第二个是“髋部与地面距离”。弯腰时髋部虽然降低,但通常不会降到很低的位置;而摔倒时髋部最终会接近地面。结合地面参考线,可以把两者区分开。
4.2 夜晚红外画面检不出关键点
夜间摄像头切红外后,画面变成黑白,对比度下降,人体边缘模糊。YOLO检测框还能出来,但姿态估计的关键点置信度明显降低。
我的方案是给姿态模型补充红外样本做微调。用已部署场景的红外录像,截取5000帧画面,用人工修正的关键点作为监督,微调RTMPose的最后一层。注意这是一个持续迭代过程,不是一次就能解决。另外一个土办法:如果红外画面实在太差,就只依赖检测框宽高比和重心高度两个特征做规则判断,不依赖姿态模型,牺牲一点预测能力,至少保证检测可用。
4.3 多人同时出现在画面里导致误关联
监控画面里经常有护工和老人同时在画面中的情况。摔倒识别特征必须绑定到同一个人的行为轨迹上,而不是把所有检测框的特征混在一起。
我用ByteTrack做多目标跟踪,给每个被跟踪的人分配一个track_id,每个track维护自己的特征序列。这样即使两个人交叉走过,时序模型处理的是各自的轨迹,不会串特征。
4.4 侧卧入睡被识别为已倒地
养老院和医院场景里,老人可能在床上侧卧休息,画面看起来像“倒地”。这需要结合设备安装场景来处理:在标定阶段,把床位区域标定为“允许躺卧区”,该区域内的倒地判定不做告警;同时把远离床位的区域标定为“严格监控区”,那里的倒地行为直接告警。这个先验信息不需要复杂的自动算法,简单手动画区域就能解决95%的误报。
4.5 关键点跳变导致时序模型输出抖动
姿态估计偶尔会出现某几帧关键点前后跳变,导致特征序列里出现尖锐噪声,GRU输出也跟着乱跳。我加了两个处理:第一个是特征平滑,对关键点坐标做指数移动平均(EMA),alpha取0.3,不要太大,太大会把真实的摔倒趋势也抹平;第二个是输出平滑,对模型输出的概率做Carbon平滑重叠加权,只有连续若干帧概率都高才触发告警。
4.6 问题排查速查表
| 现象 | 可能原因 | 处理方案 |
|---|---|---|
| 频繁误报 | 阈值设置过低 | 提高投票帧数和概率阈值 |
| 经常漏检 | 置信度阈值过高 | 将检测阈值降到0.35 |
| 夜间漏报 | 红外样本不足 | 补充红外数据微调姿态模型 |
| 告警延迟高 | 推理队列积压 | 缩短窗口长度或升级硬件 |
| 多人串号 | 缺少跟踪模块 | 集成多目标跟踪维护独立轨迹 |
| 卧床被误报 | 场景区域未标定 | 增加允许躺卧区域设置 |
5. 经验补充与后续扩展方向
在部署这套系统的过程中,我踩过比较深的一个坑是:不要在训练阶段追求过高的准确率,而要在真实场景里花时间做负样本挖掘。模型在测试集上98%的准确率,放到真实监控画面里可能只有85%的可用度,因为真实世界的负样本太丰富了——有人伸懒腰、有人在镜头前挥衣服、有人推着轮椅经过,这些动作跟摔倒的某些中间状态很像。所以训练数据里一定要大量加入场景负样本,而不是只加正样本。这类负样本不一定要人工标注,“看到摔倒后多记录几小时场景原始视频”就行,让模型在未标注负样本上做对比学习或伪标签纠正。
关于“预测”功能的落地,我的建议是第一条预警不要直接推送给人,而是先推给策略中心做二次确认。因为时序模型的pre_fall类别误报率还是比较高,直接推给管理员容易产生“狼来了”效应。可以把pre_fall预警定义为一个de-risk信号,只给现场工作人员发提示,等fallen类别触发后再发正式告警。这样操作体验会好很多。
后续还可以做的是:把音频信号加入判断,比如摔倒伴随的撞击声作为多模态辅助特征;加入光流信息提高遮挡场景的鲁棒性;用联邦学习在多个养老院场景之间共享模型参数而不共享原始数据,解决数据隐私问题。这些都是能在这个框架上平滑扩展的方向。
说实话,摔倒检测与预测系统做到最后,拼的不是模型有多强,而是对场景细节的理解有多深。技术链路已经不是最大的门槛,真正拉开差距的是数据组织和规则设计。希望这篇拆解能给正在做类似项目的朋友一些参考。