再聊一聊行人摔倒与预测系统
我做这个项目最深的感受是:摔倒检测做得再好,本质上还是"事后通知"。摄像头拍到人已经躺在地上了,系统再准确地把这条消息推给护理员,中间损失的这几秒钟可能就决定了后续处理的难度。所以当有人跟我提"摔倒与预测系统"这个概念的时候,我觉得真正有分量的其实是后半截——"预测"。检测是兜底,预测才是真正的刚需。
这篇文章想把我在实际搭建这套深度学习系统时的一些方案、思路和踩过的坑梳理一遍,给正在做类似监控项目、智慧养老项目或者安防行为分析的朋友一个参考。我会尽量把一个项目从选型、数据处理、模型设计到部署的完整链路说清楚,也会告诉你哪些地方是最容易翻车、最容易被忽略的。
1. 项目定位重新理清:检测是兜底,预测才是刚需
1.1 姿态估计为什么成了主流方案
最早做摔倒检测的时候,很多人会直接用视频帧做行为分类:拿一段监控视频的连续几帧图像喂给3D卷积网络,让网络学出来"这是摔倒"还是"这不是摔倒"。理论上说得通,实际跑起来问题很多。首先,监控摄像头安装角度五花八门,有的在墙角高处俯拍,有的装在走廊天花板正上方,有的放在桌面上平视。同一个摔倒动作,在不同视角下的RGB图像长得完全不一样,模型学到的很多其实是背景和光照特征,换个场景就失效了。其次,端到端的视频分类模型计算量大,对部署设备的要求高,在边缘设备上很难做到实时。
我现在基本不用"图像直接分类"的路数,而是采用两段式方案:先用深度学习模型从每一帧图像里提取人体骨架关键点,再把关键点坐标序列作为行为分析的数据源。骨架关键点是一组低维数据,天然地屏蔽了背景、颜色、光照、视角带来的干扰,而且极其适合做时序建模。
具体说,选姿态估计模型时要考虑清楚自己的部署条件:
- 单路摄像头:走Top-down流程。先用行人检测框把人找出来,再对每个检测框做单人关键点检测,精度可控,流程也清晰。
- 多路摄像头集中处理:优先选轻量级的Bottom-up方案,或者用输入分辨率打折扣的单人关键点模型,否则算力扛不住。
- 实时性要求很高:宁可牺牲一点关键点精度,也要保证帧率稳定,因为时序模型很怕输入帧间隔抖动。
1.2 "检测"解决的是"有没有摔","预测"解决的是"要不要摔"
检测任务本质上是一个分类问题:给定从开始到当前时刻的一段姿态序列,判断人是不是已经处于摔倒后的状态。这个任务在工程上其实已经比较成熟了,难的不是算法本身,而是如何压低误报。弯腰捡东西、蹲下系鞋带、扶着墙慢慢坐到地上,这些动作和摔倒的几何形态非常像,模型很容易被带偏。
预测任务的难度比检测高一个量级。它要求你在摔倒动作还没有完全表现出来的时候,就从姿态序列的早期征兆里判断出"这个人的重心正在失控,接下来很可能会摔倒"。这有点像自动驾驶里的碰撞预警——等碰撞已经发生了再触发安全机制没有意义,要在碰撞发生前几百毫秒甚至一两秒给系统留出反应时间。
基于这个定位,我把系统做成了两条并行通道:
- 检测通道:持续识别"已经摔倒"的事件,一旦触发就生成高优先级告警;
- 预测通道:持续输出一个"摔倒风险分数",分数超过一定阈值时提前预警,提醒护理人员关注,而不是直接判定摔倒。
两条通道用同一个骨架关键点数据源,只是下游的特征设计和标签定义不同。
1.3 这个系统到底给谁用
做项目之前最好先想清楚使用方是谁,因为不同角色对"告警"的容忍度完全不同。
如果是养老机构的护理站,护理员一天要处理大量琐事,频繁的假警报会让他们直接把系统关掉。所以面向护理站,预测通道的阈值要保守,宁可漏报几次预测,也不要让护理员觉得"这个系统一天到晚在乱叫"。如果是独居老人家属,需要的是每天一次的"风险日报"而不是每秒钟的推送,那么预测通道的风险分数就可以做日聚合,给出"今天出现过5次高风险姿态,建议家属留意老人在夜间起床时段的行动"。如果是医院康复科,医生反而希望系统能记录康复者从坐到站的过渡过程,预测通道的参数可以调得更敏感,用来生成康复评估数据。
这些需求差异会直接影响阈值设置、告警通道、数据留存方式。我见过不少项目在一开始就只盯着"把准确率提高",结果做完了发现在实际场景里根本没法用,核心原因就是没有先回答"谁在用、怎么响应"这个问题。
2. 第一块地基:骨架关键点序列是摔倒分析的主要数据源
2.1 关键点选型与坐标系扰动问题
市面上的姿态估计模型输出的关键点个数有差异,有的输出17个点,有的输出33个点。对摔倒分析来说,我建议至少覆盖以下点位:
- 头部(或鼻子、颈部);
- 双肩、双肘、双手;
- 骨盆中心(或左右髋部中点);
- 双膝、双脚。
头部和骨盆中心是计算重心与倾角的核心,膝盖和脚踝则用来判断"是否站立"以及步态情况。如果你的应用场景是老年人居所,遮挡问题会比较常见:老人坐在床边、盖着被子、侧身躺下,这些情况下下半身的关键点很容易丢失。所以选模型的时候,要关注它对遮挡关键点的回归能力,而不是只看整体AP指标。
关键点坐标的噪声会让时序模型抓狂,这一点经常被忽略。姿态估计模型在每一帧都会产生微小抖动,如果直接把这些抖动后的坐标输入到LSTM或Transformer里,模型会把抖动误认为动作特征,误报率会明显上升。我常用的处理方式是:
- 对关键点坐标做指数移动平均或卡尔曼滤波,把高频抖动滤掉;
- 把连续帧的关键点坐标做一阶差分得到速度,再做一次平滑,得到的加速度特征不要直接用二阶差分,而是先用平滑后的速度再求差分。
一个很实用的类比:这就像你想判断一辆车是否在急刹车,不能看仪表盘一秒前的读数,也不能只看瞬时加速度,你要把车窗外的景物、车里人的前倾幅度、刹车灯的时序变化放在一起看。骨架关键点同理,单帧的绝对坐标意义不大,关键的是"趋势"和"变化率"。
2.2 坐标归一化不能只做一次
摄像头画面里存在近大远小的透视效应:同一个老人,站在镜头远处和近处,关键点坐标的像素位移差别很大。如果不做归一化,模型会学到"靠近镜头就是摔倒"这种荒谬的规则。
我习惯做两级归一化:
- 尺度归一化:以人的躯干长度为基准,把所有关键点坐标除以肩-髋距离(或双肩距离)。这样无论人离摄像头远近,骨架的尺寸都被拉到一个相似的量级。
- 位置归一化:以骨盆中心为原点,把所有关键点坐标转换成相对骨盆的偏移量。这样人能站在画面任何一个位置,坐标特征不会因为绝对位置而漂移。
同时,要保留骨盆中心在图像坐标系中的绝对位置变化速度。为什么?因为摔倒的一瞬间,人的骨盆会迅速下坠,这个"绝对运动速度"是摔倒检测里最有力的特征之一。如果你把坐标全归一化到原点,反而把这个核心线索抹掉了。所以我的特征向量通常是:归一化后的关键点相对坐标 + 骨盆中心的绝对像素速度 + 骨盆中心的绝对像素加速度。这两部分拼接起来,才是一个完整的特征。
2.3 几何特征值比原始坐标更好解释
虽然模型理论上能从原始关键点序列里自己学到特征,但实际项目中,我还是会手工构造一组高层几何特征。好处有三个:一是模型输入维度更低,训练更容易收敛;二是当模型输出一个风险分数时,你能解释是哪个特征在起作用,方便定位误报;三是规则逻辑和模型逻辑可以互相兜底。
我一共构造了7个核心特征,每个特征在摔倒瞬间或摔倒前的表现都非常清晰:
| 特征名 | 计算方式 | 摔倒时的典型表现 |
|---|---|---|
| 骨盆高度 | 骨盆中心y坐标(归一化后) | 快速下降 |
| 骨盆垂直速度 | 骨盆高度的一阶差分 | 负值增大,即快速下坠 |
| 躯干倾角 | 肩部中点和骨盆中点连线与垂直轴的夹角 | 从接近0°迅速增大 |
| 头部垂直速度 | 头部y坐标的一阶差分 | 急剧负向增大 |
| 外接框宽高比 | 人形外接矩形的宽/高 | 由小于1变为大于1 |
| 重心水平偏移速度 | 骨盆中心x坐标的一阶差分 | 快速增大,说明横向失控 |
| 膝盖弯曲度 | 髋-膝-踝三点夹角 | 摔倒前常出现异常弯曲 |
这里多说一句:外接框宽高比是一个很直观但容易误伤的特征。老人正常坐下时宽高比也会变大,所以宽高比只能作为辅助信号,不能单独做判定。真正要判断的是"宽高比的变化速度"和"骨盆高度的下降速度"是不是同时发生。如果两者同步发生且持续,才可能是摔倒动作;如果只是某个瞬间的比值突变,大概率是坐下或弯腰。
3. 双通道架构设计:用两条路径分别处理检测与预测
3.1 检测通道:一个务实的时序分类器
检测通道的输入是一段固定长度的关键点特征序列。我的默认配置是:
- 帧率:10 FPS(如果原视频是25/30 FPS,先抽帧到10 FPS,减少冗余计算);
- 窗口长度:32帧,大约是3.2秒;
- 窗口滑动步长:8帧,约0.8秒;
- 标签:三分类,分别是正常、过渡动作、摔倒。
窗口的时序建模我用GRU。为什么不首选LSTM?因为GRU参数量更少,在摔倒这种长度短、模式明显的序列任务上,GRU和LSTM的准确率差距很小,但推理速度更快,部署友好。如果你更倾向于把关键点序列当作带位置编码的Token序列,也可以用一个小型Transformer,但我实测在样本量不大的场景下,GRU的泛化稳定性更好。
检测通道的分类阈值不要直接取0.5。我通常的做法是:
- "摔倒"类的置信度超过0.75才认为发生摔倒;
- 连续2个窗口(也就是约0.8秒)都给出高置信度,才触发确认逻辑;
- 一旦确认摔倒,系统直接发最高优先级告警。
为什么要求连续两个窗口?因为一个窗口里的"高置信"完全可能是姿态估计模型偶发跳变造成的假象,连续窗口确认可以显著抑制这类误报。代价是告警延迟增加不到1秒,对已经发生的摔倒来说完全可接受。
3.2 预测通道:输出的是风险分数,而不是"会不会摔"
预测通道是整个系统的技术核心,也是和市面上大多数摔倒检测方案拉开差距的地方。
常规想法是直接训练一个二分类模型,预测"未来1秒内是否摔倒"。这个思路有一个严重问题:摔倒是个极端稀疏事件,训练数据里"即将摔倒"的样本本来就少,二分类模型输出一个0到1的概率,但这个概率根本没法指导调度——0.6和0.9到底应该怎么响应?所以我换成了风险评分模式。
预测通道的输入和检测通道相同,但标签设计不同:我把摔倒发生时刻作为锚点,将这个时刻之前的0.5秒到1.5秒这段区间标注为"高风险前兆区",输出目标是回归一个0到1的风险分数,高风险前兆区内的标注分数按时间接近程度从0.3线性升到1.0,正常行为则全部标为0。
这样做之后,模型的训练目标就变成了"估计当前姿态处于摔倒事件时间轴上的哪个位置"——越接近摔倒点,分数越高。模型结构上,我用了稍长一点的窗口:48帧,约4.8秒,同样10 FPS。因为预测更依赖早期趋势,窗口太短不利于模型捕捉重心缓慢偏移的渐进过程。GRU输出后接一个全连接层,focal loss在我的实验里比MSE损失效果好,因为样本里高风险段占比极低,focal loss可以抑制"全预测为0"的懒解。
最终,预测通道的输出不是简单的一个数,而是两条信息:
- 当前风险分数(0到1);
- 预估风险升级方向(用最近20帧的风险分数斜率表示,指示分数正在快速上升还是平稳)。
3.3 双通道联动策略
两个通道单独跑,最后如何决定是否告警、以什么级别告警?我设计了一张简单但实用的决策表:
| 检测通道 | 预测通道 | 系统行为 |
|---|---|---|
| 高置信摔倒 | 高风险 | 立即触发最高级告警,同时推送姿态截图和一段10秒回放 |
| 高置信摔倒 | 低风险 | 立即触发最高级告警(可能是快速摔倒,预测通道来不及反应) |
| 低置信 | 高风险且分数上升中 | 触发提前关注级预警,通知护理人员"建议关注",不清真 |
| 低置信 | 高风险但分数平稳 | 记录风险事件,暂不推送,若持续超过30秒再升级 |
| 低置信 | 低风险 | 无响应 |
这个联动的核心思想是:检测通道决定"要不要救人",预测通道决定"要不要看人"。两件事的响应策略完全不同,绝对不能混为一谈。
4. 训练与数据:时序窗口、标签设计和类别不平衡的应对
4.1 公开数据集和自建数据的取舍
说实话,公开的摔倒数据集做学术对比还行,做真实项目远远不够。它们普遍有几个问题:
- 摄像机安装位置固定,视角种类有限,真实场景里摄像头常常是小角度俯拍甚至顶装;
- 动作演员多为年轻志愿者,摔倒模式过于典型,没有真实老人的缓慢、笨拙式摔倒;
- 样本量太少,跌倒样本几十上百段,正常行为多样性完全不够。
我的建议是"公开数据做预训练、自建数据做微调"。自建数据采集时,一定要找不同年龄、不同体型、不同着装的人来模拟摔倒,并且覆盖几种显著不同的摔倒类型:
- 向前趴倒;
- 侧向倒地(最常见于老年人髋部骨折);
- 向后坐倒;
- 站立不稳后缓慢滑坐到地上;
- 从椅子上起身时失衡摔倒。
上一类"从椅子起身摔倒"一定要单列,因为它的起始姿态是坐姿,骨盆高度本来就低,用绝对高度阈值很容易漏报。
我自己还引入了一个很笨但有效的方法:逆向标注。先让人模拟各种动作(包括摔倒和各类容易误报的动作),然后在视频里用人工把"摔倒发生时刻"标记出来,再按照这个时刻自动往前回溯生成高风险前兆标签。整个过程不需要逐帧标注,效率高很多。
4.2 窗口大小、标签边界和滑窗推理
在窗口设置上,不同任务的最优窗口长度不一样:
- 检测通道的窗口要短,32帧左右即可,能快速响应;
- 预测通道的窗口要长,48到64帧更好,让模型看到足够长的渐进变化过程。
帧率统一抽帧到10 FPS,窗口重叠步长固定为8帧。样本增强方面,对关键点序列做四种增强最有效:
- 随机缩放:整体坐标乘以0.9到1.1之间的随机系数;
- 时间缩放:把时间轴压缩或拉伸0.85到1.15倍;
- 关键点丢失模拟:随机屏蔽腿部两个关键点,模拟遮挡场景;
- 坐标噪声:给坐标添加高斯噪声,标准差取归一化尺寸的2%。
其中时间缩放往往被忽略,但它极其重要。同一个微弱的重心偏移,慢速发生是正常起身,快速发生是摔倒前兆。时间缩放可以让模型学到"关键是变化率而不只是位移量"。
4.3 类别不平衡导致模型"躺平"的解法
时序训练集里正常样本和摔倒样本的比例经常是几十比一。模型学到最后会倾向于把所有输入都判为正常,因为这样总损失最小。我用了三个手段叠加:
- 加权交叉熵,给摔倒类和高风险前兆类更高的权重;
- focal loss中Gamma取2.0,聚焦于难分类样本;
- 分段采样:训练时每个batch里必须包含至少一个摔倒样本,保证模型每个训练步都能看到正样本。
后处理方面,不要用逐帧逻辑。我采用"事件级判定":一个事件必须在连续N个滑窗中都得到高置信,才算作一次有效事件。事件结束条件是连续M个窗口不再满足阈值。这个方法会带来零点几秒的延迟,但能把误报降低一个量级。
5. 部署落地最容易翻车的四个实际问题
5.1 延迟预算:从摄像头到告警的端到端时间
很多人在实验环境里跑得很好,一到现场就发现告警总是慢半拍。问题出在哪?端到端延迟不是单次模型推理时间,而是一条链路的累加:
| 环节 | 常规耗时 |
|---|---|
| 摄像头推流与抽帧 | 100ms |
| 行人检测 | 10ms |
| 姿态估计 | 20ms |
| 特征提取与窗口拼接 | 5ms |
| 检测通道推理 | 3ms |
| 预测通道推理 | 3ms |
| 告警推送 | 50ms |
看起来每个环节都不慢,加起来却到了190ms左右。如果摄像头帧率不稳定,或者姿态模型GPU占用率打满导致排队,几百毫秒甚至一秒的延迟完全可能。我的经验是:部署后一定要测试"从摔倒动作发生到收到告警推送"的整链路延时,而不是只看模型推理延时。如果整链路延时超过1秒,先检查是不是抽帧逻辑做了同步等待,或者是不是每帧都做了一次不必要的行人检测。
5.2 误报抑制:场景里最隐藏的杀手
真实场景里最常触发误报的动作不是摔倒,而是:拖地、弯腰收拾茶几、系鞋带、坐到地板上逗孩子、从较低的床上快速起身。
我刚开始用规则做检测时,误报率惨不忍睹。后来模型化之后好了一些,但真正有效的是三个策略叠加:
- 置信度时序平滑:把模型输出的置信度做指数移动平均,平滑系数取0.3。这样单帧的随机跳变会被压掉,真实摔倒带来的持续高置信不会被掩盖。
- 非人体目标过滤:有些静止物体形状像人(比如立式衣架、轮椅、床头柜上堆的衣物),行人检测模块偶尔会误检。需要在检测框跟踪里用一个最小持续时间,过短的检测框直接抛弃。
- 场景区域掩膜:设置主要活动区域作为有效判断区,椅子、床铺区域可以单独调低风险阈值,但走廊中部的异常低速重心下降则提高到最高优先级。
5.3 低算力设备上的INT8量化一定要留后路
边缘盒子算力有限,量化几乎是必经之路。但姿态估计模型量化后有一个特殊风险:它会改变关键点热图的峰值位置,导致关键点坐标出现系统性偏移。偏移量不大,可能只有一两个像素,但送入时序模型后,这种偏移会变成"伪速度",进而抬高误报率。
我的处理办法是分层量化:
- 行人检测模型:直接INT8量化,影响不大;
- 姿态估计模型:先用FP16跑通,再尝试INT8,但关键点的坐标输出必须使用原始浮点后处理头,只对卷积部分量化;
- 时序分类模型:参数极少,保留FP16或干脆FP32。
如果INT8量化后关键点抖动明显变大,不要强行调阈值去压,应该降低姿态估计模型的输入分辨率,找到那个"分辨率降低但关键点抖动还可接受"的平衡点。我实测过多次,很多时候降低输入分辨率对关键点精度的损失比INT8量化还小,但计算收益更大。
5.4 隐私保护决定了这个项目能不能真正上线
很多人把隐私当做一个可有可无的合规问题,但实际做项目时,它直接决定用户愿不愿意用你的系统。
我这里的处理方式是完全本地化推理:摄像头画面只在本地设备上做姿态估计,原始视频不传云端、不留长期存储,只保留以下内容作为告警证据:
- 告警时刻的脱敏骨架图,即只显示关键点和连线的二维图像;
- 一段持续10秒的关键点坐标序列日志;
- 一张人脸打码的告警截图,方便护理员判断人员身份。
有人担心骨架图信息不够,看不出是谁。我的经验是配合颜色标签和场景位置编码,比如"走廊3号摄像头,靠近窗户区域",配合护理员对老人的行动习惯,完全能可靠对应到人。这比直接存储原始监控画面安全得多,也更容易说服用户在隐私承诺上签字。
对这个项目,我最后想分享两条真实体会。
第一,预测通道的难点从来不在模型结构,在于怎么定义"即将摔倒"这个地面真值。如果你只是简单地把摔倒前1秒标为正样本,模型学到的可能只是"骨盆开始下降"这个单一信号,误报会很高。我建议多花时间去观察真实监控里那些"没摔但让人捏一把汗"的姿态,把它们作为难负样本单独收集。这个工作比调任何网络结构都值钱。
第二,别把系统做成只会报警的工具。我在实际使用中发现,把预测通道的风险分数按小时聚合导出成"风险曲线"之后,护理院的管理人员会拿着曲线来找我讨论,哪些时段老人起夜最频繁、哪些药物调整之后夜间风险明显下降。这个附加价值完全超出最初的预期。所以如果还有余力,给系统加一个简单的日报告模块,把风险分数、事件次数、时间分布可视化成一张表,你会发现用户的认可度明显上一个台阶。