人员跌倒检测是视觉识别里非常有代表性的一类落地场景:目标小、动作快、姿态变化大、环境干扰多,而且误报代价极高。这几年很多人都在做这个方向,但真正能跑到“可用”状态的不多。我也在这个方向折腾了大半年,最终的路线是基于Faster R-CNN做改进,而不是直接上YOLO或者更重的两阶段网络。这篇文章就从我的角度,把这套系统的设计思路、模型改进、数据处理和训练调优全盘拆开讲,给正在做类似目标检测或者跌倒识别的人一个可参考的完整方案。
这算是这个系列的第一篇,重点放在模型结构和训练环节。
1. 跌倒检测为什么难:先看清问题再做方案
1.1 检测场景里的三个硬骨头
很多人第一次接触跌倒检测,觉得无非就是“把人框出来,判断姿态”。但真正做起来你会发现,这个任务和通用目标检测有本质差别。
第一个难点是姿态多样性。人在跌倒过程中,身体姿态是剧烈变化的:刚开始是直立,然后是前倾或后仰,接着可能蜷缩、侧躺、俯卧。同一个人的跌倒动作,在不同时刻呈现出的外形差异可能比“站着的人和躺着的人”之间的差异还要大。这对检测器来说是个令人头疼的问题,因为检测器要学的不是“人长什么样”,而是“人在异常状态下长什么样”。
第二个难点是尺度不固定。跌倒的瞬间,人体可能在画面里占的面积很小,也可能因为离摄像头近而非常大。很多跌倒数据集里,标注框的宽高比从接近1:1到接近3:1都有。如果只用单尺度特征图去检测,小目标漏检和大目标定位不准就会同时出现。
第三个难点是类间相似性。蹲下系鞋带、弯腰捡东西、坐在地上休息,这些动作在单帧画面上和跌倒几乎没有区别。如果是看单张图片,任何人都很难判断这是“跌倒”还是“蹲下”。所以单纯改进单帧检测器的精度,天花板非常低。
1.2 为什么选Faster R-CNN而不是YOLO
我听到最多的问题是:现在YOLO系列又快又准,为什么不直接拿来用?
我先说结论:可以,但我不建议作为首选。跌倒检测这类任务,对“误报”的容忍度极低,而对“速度”的要求反而不像自动驾驶那么极端。监控场景下,每秒处理3到5帧就够用了,但每帧的检测质量必须非常高。
Faster R-CNN作为两阶段检测器,最大的优势在于“粗筛+精修”的结构。RPN先给出可能包含目标的候选框,后面的检测头再对每个候选框做精细分类和回归。这样一个流程天然地比单阶段模型更容易控制误检。而且两阶段检测器在训练时,正负样本的比例是可控的,这意味着可以用比较小的数据集就训出不错的效果。
另一个原因是我要做的“改进”有明确的落点。YOLO的改进点主要围绕特征融合和多尺度,能玩的花样基本都被社区挖得差不多了。而Faster R-CNN的改进空间更大:骨干网络、特征融合、候选框生成、ROI对齐、损失函数、后处理,每个环节都可以独立优化。对于一篇文章或者一个系统性工程来说,这套框架更有拆解的价值。
提醒一下:如果你的场景是实时大流量分析,比如商场通道、地铁站台,单张显卡加单帧Faster R-CNN确实有压力。那就需要换轻量化骨干或者做检测+跟踪的裁剪。但如果你做的是居家老人监护、医院病房看护这一类小规模场景,Faster R-CNN的性价比非常高。
1.3 跌倒检测的常见落地场景
这套系统可以落地的场景大致分三类。
居家养老场景。独居老人在家中跌倒后如果不能及时发现,后果会非常严重。这类场景摄像头数量少、背景固定、计算设备相对充足,非常适合Faster R-CNN这类高质量检测模型。
医院与康复机构场景。病人在病床旁或卫生间跌倒,同样需要秒级响应。这几类场景的特点是会出现轮椅、床边护栏、输液架等干扰物,但对检测器来说仍然是可以训练应对的。
工业与公共安全场景。比如工厂车间、建筑工地,工人跌倒可能是突发疾病或者安全事故,越早发现越好。这类场景的难点在于光照复杂、遮挡多,而且经常有多个工人同时出现在画面里,相互遮挡很严重。
至于到底做单帧检测还是加上时序分析,我在后面会专门讲。这里先明确一个前提:不管后续怎么加时序逻辑,单帧检测的精度必须过硬,否则时序模块只会放大错误。
2. 改进模型的整体设计:从Faster R-CNN出发逐层升级
2.1 原始Faster R-CNN的底子回顾
改进模型之前,我先把底子讲清楚,因为后面所有改动都是基于这几个核心模块:
- 骨干网络负责从原始图像提取特征。经典Faster R-CNN用的是VGG16,最后一个卷积层的输出会同时送给RPN和后面的检测头。
- RPN在特征图上滑动,通过预先设定的锚框(Anchors)生成候选区域,并输出“是不是目标”的粗分类以及锚框的粗略修正。
- ROI Pooling把候选区域映射回特征图,然后固定成同一尺寸的特征,再送入后面的全连接层。
- 检测头对每个候选区域做更精细的分类和边框回归,输出最终的目标类别和位置。
这个流程本身没有大问题,问题出在VGG16这个骨干上。VGG16结构简单、参数量不小、感受野有限,在跌倒这种姿态变化剧烈的任务上,特征表达能力不够。所以我的改进首先就是把骨干换掉。
2.2 骨干网络替换:从VGG16到残差结构
我把骨干网络换成了带残差连接的卷积网络。为什么用残差结构?因为当网络加深到一定层数之后,梯度消失问题会让深层网络反而不如浅层网络。残差连接相当于给梯度开了一条“高速公路”,让深层网络真正能学到更丰富的特征。
在实际选择上,我对比过两个方向:一个是ResNet50,中规中矩,配套的预训练模型多,训练稳定;另一个是ResNet101,特征更强但计算量增加不少。最终的测试结果让我很意外:ResNet101在跌倒数据集上的mAP提升只有0.4到0.6个百分点,但推理时间增加了接近一倍。对于这个任务来说不划算,所以最后选了ResNet50作为默认配置。
换骨干有一个细节容易被忽略:需不需要冻结部分参数做迁移学习。我的做法是默认加载ImageNet预训练权重,然后把骨干网络的浅层(前两个Stage)冻结,只微调后面几个Stage。这样做的原因是浅层特征通常比较通用,例如边缘、纹理、颜色梯度,不用为了一个小数据集重新学习。而且冻结浅层还能减少显存占用,让batch size可以开得更大。
2.3 特征融合:给检测器一副“全景+特写”的眼睛
换完骨干之后,我紧接着遇到了尺度问题。跌倒发生的时候,如果人在画面远端,整个目标可能只有几十个像素高;如果人在摄像头正下方,目标又会非常大。只用高层特征图去做检测,小目标基本没戏。
于是我把结构从单层特征输出改成了多尺度特征融合方案。这里我选择的是在骨干网络基础上叠加FPN结构。FPN的核心思想是“自顶而下”地把深层语义信息逐层传回浅层,让每一层都同时具备语义信息和空间细节。
具体来说,我在骨干网络的不同Stage后面分别引出特征图,然后做上采样、逐元素相加。这样最终会得到多个不同分辨率的特征图。大目标从高层特征图上检测,小目标从低层特征图上检测。RPN和检测头都会在这多个特征图上共享权重地工作。
这个改动带来的提升非常直接。在我的数据集上,只加FPN,小目标AP从0.41涨到了0.53。而且代码改动并不大,在标准检测框架里只需要改一下neck部分的配置。这算是投入产出比最高的一项改进。
2.4 注意力机制:让模型更关注“人”而不是“沙发”
跌倒检测中一个非常普遍的误检来源是家具和杂物。很多失败案例里面,模型框住的不是人,而是一个形状接近躺卧人体的沙发或者堆在地上的被子。原因很好理解:在高层特征里,“躺着的形状”可能比“人的细节”更占主导。
针对这个问题,我给检测头添加了通道维度的注意力模块。它的作用是让模型在学习过程中自动调节各个特征通道的重要程度,把跟“人体轮廓”“四肢分布”相关的通道权重调高,把跟“沙发纹理”“地板颜色”相关的通道压下去。
这个模块的使用有个技巧:不是放在骨干网络的每一层后面,而是只放在检测头之前的特征输入处。放在每层后面效果虽然会更好一点点,但训练时间和显存消耗都会明显上升,性价比不高。另外,注意力模块的初始权重不要随机初始化,直接在预训练权重之上加一层缩放系数,让它在训练初期接近恒等映射,这样能避免刚开始训练时特征分布被破坏。
2.5 锚框设计的针对性调整
锚框参数是一个很多人不太重视但其实影响很大的细节。Faster R-CNN默认的锚框是3种面积配3种宽高比(1:1、1:2、2:1)。对通用目标检测来说这个设置没问题,但对跌倒检测来说并不完全匹配。
人在跌倒过程中,最常见的目标形状是宽扁的(横向躺在地上)或者窄高的(站立状态下开始倾斜)。所以我重新设计了一组锚框参数,在默认基础上增加了宽高比0.5和1.5,同时把面积从4种扩展到5种,专门覆盖从几十像素的小目标到大面积目标的变化。
这个改动不大,但实验数据说明一切:RPN的召回率提升了3.8个百分点。后面所有检测指标都是建立在这个高召回基础上的,所以这一步千万别省。
3. 数据准备与标注规范:模型天花板由数据决定
3.1 数据来源与公开数据集适配
模型设计得再好,没有合适的数据也白搭。跌倒检测能用的公开数据集并不算多,而且每个都有明显短板。我用过的其中一个公开数据集,总共只有几十段视频,标注风格偏向“人头框”而不是“全身框”。另一个数据集则是实验室环境下摆拍的,场景单一、光照稳定,用它训练出来的模型根本没有办法直接部署。
我的建议是:公开数据集只用来做预训练或做测试基准,真正训练要用自建数据集。自建数据集的核心来源分两块:
- 模拟场景拍摄。找几个不同户型的房间,用摄像头从不同角度拍摄志愿者模拟跌倒。这里要注意的是,不能只盯着“正面镜头”拍,俯视角度、斜视角度都要有,因为实际部署时摄像头通常都装在墙角。
- 真实场景素材清洗。和用户确认脱敏之后获得一些真实监控片段,人工挑出有效的跌倒瞬间并截帧。这种做法数据质量最好,但获取和标注的成本也最高。
3.2 标注规范:跌倒框到底怎么画
我在开始标注之前先定了规则,因为不同标注员对“跌倒”的理解差别很大。如果规则不统一,数据集就是一团乱麻。
我的规则是所有标注框都“紧贴人体外轮廓”,可以包含四肢张开的范围。如果身体被部分遮挡,按可见范围标注,并标记遮挡程度。这里有个必须明确的边界:如果人已经倒在地上但还能清晰看到四肢动作,比如正在挣扎着坐起来,这个状态算“跌倒”还是“未跌倒”?我的做法是:只要人体主干与地面夹角小于45度,就归为跌倒类别。这个规则不够完美,但至少标注员不会产生分歧。
还有一个容易踩的坑:蹲下和跌倒的区别。我的处理办法是把“蹲下”单独做成一个负样本类别。这里不是要模型去识别“蹲下”这个动作,而是给“跌倒”类提供大量难负样本。这样模型在训练时能更清楚地学到“跌倒”和“非跌倒”的姿态边界。
3.3 数据增强与难样本挖掘
跌倒数据集的规模一般都不大,几千到几万张就算不错了,所以数据增强不是可选项,而是必需品。
我用到的增强策略分两档。基础档包括水平翻转、小角度旋转、亮度扰动、随机裁剪和缩放。这些操作不会改变“跌倒”这个语义,但能大幅增加场景多样性。进阶档是针对这个任务的特殊增强:随机遮挡会人为地在人体区域上叠加黑色矩形块,模拟实际监控里被桌椅遮挡的情况;多尺度缩放让同一个跌倒目标在训练中以不同大小反复出现;复制粘贴增强则把跌倒人体从原图上裁下来,贴到不同背景的图片上,这种方法能在不增加采集成本的情况下扩充很多样本。
难样本挖掘也值得说。训练一段时间之后我手动查看了一批误检图片,发现错误主要集中在椅子扶手、叠放的被子、墩布桶旁边的阴影。这些图片全部加入了下一轮的训练数据。效果是立竿见影的,误报数减少了差不多三成。
常见误区:把数据增强当成“多多益善”。有些增强方式,比如大角度旋转,会把跌倒目标转成完全不像人体的形状,反而教坏模型。我的建议是旋转角度控制在正负15度以内,对“跌倒”这个语义来说是安全边界。
4. 训练核心环节与关键参数解析
4.1 训练环境与基础配置
实验用的机器就是一张当前主流的消费级显卡,显存24GB,训练框架用的PyTorch。训练时输入图片最长边统一缩放到1333像素,这个尺寸对跌倒检测够用,再大也只是增加显存压力。
训练参数上,我采用随机梯度下降优化器,初始学习率设0.01,配合线性预热策略。这里有个细节:两阶段检测器的训练和单阶段不一样,它天然就是多任务联合训练。RPN的损失、检测头的分类损失和回归损失是加在一起反传的。所以学习率的设置必须保守一点,0.01是ResNet50骨架上比较稳妥的起始值。
batch size我设成了8,这几乎是24GB显存的极限,因为FPN加注意力模块都会额外消耗显存。如果显存不够,优先减小batch size而不是缩减输入尺寸。批量太小的时候,可以适当调低初始学习率到0.005,避免训练震荡。
4.2 损失函数改进:从平滑L1到IoU感知的回归
Faster R-CNN原始的边框回归损失用的是平滑L1损失。它的问题是:平滑L1是对“偏移量”做回归,但偏移量接近的框,IoU可能差别很大。尤其在跌倒目标非常扁的时候,比如一个横向的躺倒人体,框的宽度可能是高度的三四倍。这种极端比例的框用平滑L1算,位置稍微差一点,IoU损失就会被放大,模型训练会很敏感。
我把边框回归分支的损失换成IoU感知的变体,同时在评估指标里关注IoU阈值下的AP值。这里我不展开太多公式,只说一下为什么有效。IoU损失直接优化“预测框和真实框的重叠度”,这个和在评估时用的指标是一致的。训练目标与评估指标对齐之后,模型精度的提升非常稳定,平均AP提升了约0.7个百分点。
4.3 训练过程中稳定收敛的几个技巧
在训练过程中我遇到过一个非常典型的问题:损失在某个阶段下降缓缓甚至反弹。排查之后确定是多任务之间的梯度互相干扰。
这里有个我验证过有效的方案:给RPN的分类损失和检测头的分类损失设置不同的权重系数。RPN结构相对简单,它的监督信息不应该主导整个模型的参数更新。我的最终配置是RPN部分损失权重保持1.0,检测头部分分类损失权重设为1.2。别小看这个数值,它直接决定了模型学习的重心在“找候选框”还是“做精细分类”。
另外,梯度裁剪我开了。对于两阶段检测器来说,偶尔会有个别样本的损失特别大,导致梯度爆炸。我用全局梯度范数裁剪,阈值设为5。这个操作几乎不影响收敛速度,但能省掉很多“损失突然变成NaN”的烦恼。
4.4 评估指标怎么定:别死磕mAP
跌打检测是个工程问题,不是刷榜游戏。我在评估时看的指标有三个:
- 类别平均精度(AP)在0.5 IoU阈值下的值,主要看主检测能力。
- 小目标子集的AP,专门看远处跌倒的检测效果。
- 部署场景下的每日误报率,也就是“没有一个老人跌倒,但系统一天响了多少次”。
前两项是离线指标,第三项才是线上真正重要的指标。我的经验是:模型在测试集上的mAP达到0.9以上,部署到真实环境仍然可能一个小时误报好几次。我花了很长一段时间调整误报,最后发现问题和模型结构关系不大,反而和数据增强、难负样本比例高度相关。
5. 常见问题与排查技巧实录
5.1 蹲下被误判成跌倒,怎么办
这是所有人第一个遇到的问题。单帧检测器无论如何优化,蹲下和跌倒的视觉差异在某些角度下几乎为零。
我的建议是不要死磕单帧模型,而是在检测器之后加一个轻量的时序判定模块。做法很简单:保留检测器输出的置信度和目标中心点坐标,然后在时间轴上做滑动窗口统计。如果连续5到10帧中判决为“跌倒”的帧数占比超过60%,才触发最终的跌倒警报。蹲下这个动作通常在1秒内就会恢复直立状态,这种短暂的正帧根本凑不齐时序投票的比例。
这套方法的误报率可以降低80%以上。代价是记忆、检测结果需要消耗一点内存,但这在监控系统里无足轻重。
5.2 模型在夜晚和逆光下崩掉
很多模型白天表现很好,一到晚上或者窗边逆光环境就开始漏检。原因是训练数据里根本没有覆盖这些光照条件。
解法有两个层面。第一个层面是增强数据集,把训练图片随机调暗、增加阴影、做gamma校正。不需要做得太狠,能覆盖极端光照就行。第二个层面是预处理,在推理时对输入图像做自适应直方图均衡化。这个操作会轻微改变颜色统计信息,但对检测器的影响很小,却能让模型在暗光下稳定不少。实测之后发现,逆光场景的漏检率降低了大概四成。
5.3 训练损失正常但验证指标不涨
这种“原地踏步”的情况我遇到过两次。一次是因为验证集和训练集太相似,模型其实已经在过拟合的边缘反复横跳。另一次是因为学习率下得太快,模型跳进了一个比较差的局部最优解,后面的迭代只是在那个坑里小幅振荡。
如果是第一种情况,增加验证集的场景多样性。如果是第二种,把学习率调度从阶梯式下降改成余弦退火。相比于人为预设几个下降点,余弦退火能让学习率连续变化,给模型更多跳出局部最优的机会。
5.4 实时性不足,部署压力大
Faster R-CNN的性能短板我也很清楚。如果部署端只有CPU,骨干网络加FPN的计算量根本跑不动。
两个可行方案:一是换轻量化骨干网络(比如MobileNet系列的变体结构),牺牲掉2到3个点的AP,换取接近10倍的推理速度提升;二是用TensorRT做模型加速,在GPU部署时收益很明显。
我个人的选择是留一套轻量级配置用于边缘设备,完整版配置用于机房GPU服务器。这两套配置共享相同的训练代码和数据集,切换成本只是改几行配置而已。
5.5 为什么不建议直接上姿态估计
很多人会问:既然跌倒检测的本质是姿态判断,为什么不用人体姿态估计模型直接做?
我在项目一开始试验过。姿态估计确实能输出更丰富的信息,例如关节角度、关键点位移,但它对遮挡和远距离小目标更敏感。在俯视摄像头下,很多关键点是根本看不见的,姿态估计模型的性能非常不稳定。而且姿态估计通常还需要额外的模型来计算角度和判断动作,整个流程复杂不少。相比之下,目标检测加时序投票的链路更简单,更容易维护,也更稳定。
6. 训练实验的完整记录与最终效果
6.1 消融实验:每一处改动都到底提升了多少
为了搞清楚每个模块的贡献,我把所有改进做了完整的消融实验。
结构方面,带残差的骨干网络替换掉VGG之后,mAP从0.72直接跳到0.78,这是所有改动里收益最大的一步。加上FPN之后,小目标AP从0.41涨到0.53,mAP也跟着涨到0.83。注意力模块的贡献主要集中在误检这个维度,mAP涨得不多,但误报数减少了。
锚框调整的收益也很实,RPN召回率涨了快4个点。损失函数的改进让mAP又提高了0.7个百分点。最后把难样本挖掘的数据加进去之后,AP提升不算多,但误报减少非常明显。
6.2 最终模型的表现
最终模型在自建测试集上的mAP超过0.9,小目标AP接近0.8,单帧推理时间在GPU环境下是30毫秒出头,完全能满足监控场景每秒3到5帧的处理节奏。
从误报率来看,模拟部署环境中一天的误报次数控制在个位数,相比最初随便一跑就几十次误报,已经是质变。这个水平虽然还不能说“完全自动可靠”,但配合人工复核和时序投票,已经具备上线试运行的条件。
6.3 后续扩展方向
这个项目后续有两个跟进方向。一是引入时序检测模块,从“单帧分类”升级为“短时动作识别”,主要解决蹲下、弯腰、系鞋带这类高混淆动作。二是把整套流程封装进常用的推理框架,顺便把轻量化骨干版本的性能调优做完,保证边缘设备上的实时性。
7. 写在最后的工程经验
这套系统做了大半年,我最大的体会是:跌倒检测的难点从来不在模型结构,而在对数据的理解和场景的判断。很多人一开始都想着换更高级的网络,结果绕了一大圈发现,真正让效果产生质变的,是把数据标注规则定好、把难样本补进来、把误报机制调对。
锚框参数换了、损失函数换了、骨干网络换了,这些改动单独看起来都不复杂。但把它们组合起来,整个系统就从一个“偶尔准”的玩具,变成了一个“大部分时间靠谱”的工程产品。
我个人还保留了一个非常受益的小习惯:每次训练结束,把误检图片和漏检图片都导出,按错误类型分组维护一个“失败集”。这个失败集比测试集还能说明问题,每一次模型迭代之后,拿失败集过一遍,你会非常清楚地看到改进到底发生在哪里,又是哪里还在继续犯错。这个做法成本极低,但对检测类项目的迭代效率提升非常明显。
下一篇我会接着讲这套系统在时序判定和部署端优化上的具体做法,包括如何在保持低延迟的前提下,把误报率再压一个数量级。