跌倒判定状态机:从几何信号到"要不要问一句"
检测与姿态(EXP-05)给出每帧关键点后,谁来拍板"这算不算跌倒"?答案是 fall/state_machine.py 的六状态机:IDLE→FALLING→LYING→ASK→ALERT/CANCELLED,每人一个实例。本实验吃透躯干角>50°、宽高比<0.5、髋速尖峰 0.25×框高三大判据与 1.5s/3.0s/12s/60s 四个时间窗,跑通"救命→ALERT、没事→CANCELLED"两条应答路径,并用单变量调参感受误报/漏报的权衡。
01问题引入:弯腰捡鞋为什么不能算跌倒
护理场景里最怕两件事:老人真跌倒了系统没吭声(漏报),或老人只是弯腰捡只鞋就被追问"您还好吗"(误报)。前者致命,后者扰民 —— 都会让照护者不再信任这套设备。可是从关键点坐标到"跌倒"二字之间,隔着一连串判断:单帧像躺着就够吗?躺一下马上起来算吗?要不要先问一句再报警?
02原理通俗讲解:像"问诊确认流程"一样的状态机
把状态机想象成分诊台的问诊流程 —— 病人(画面里的人)在不同队列之间流动,每个队列有自己的"放行条件":
形态 + 速度双条件同时成立:躯干角>50°或宽高比<0.5(像躺),且髋部速度>0.25×框高(正在快速倒下)。进入 1.5s 观察窗。
观察窗内持续满足躺姿判据 → 确认躺地;若 1.5s 内人又站起来了 → 回 IDLE(瞬时异常,正确免疫)。
持续躺地超过 lying_time 3.0s 视为"起不来",进入 12s 应答窗口,TTS 播报"您还好吗?需要帮助吗?"。cooldown 60s 内不重复触发。
确认词(救命/帮/疼/起不来/摔/救)→ ALERT 告警;取消词(没事/不用)→ CANCELLED 取消;12s 超时无人应答 → 保守升级 ALERT。
两个容易混淆的"刹车"机制要分清:
| 机制 | 时长 | 挡什么 | 不挡什么 |
|---|---|---|---|
| cooldown 冷却期 | 60s(进 ALERT/CANCELLED 时起算) | 挡住 LYING→ASK 的重复询问,防止把老人问烦 | 不挡新跌倒:60s 内再次满足形态+速度 → 照样重进 FALLING(防漏报关键设计,state_machine L215-L220) |
| 恢复判据 _is_recovered | 持续 2s | —— | bbox_aspect>1.5 或角<25° 持续 2s 才回 IDLE,防起身瞬间抖动立刻重入告警 |
03关联知识点:三大几何判据与六状态机
| 知识点 | 内容 | 源码锚点 |
|---|---|---|
| 5.1.1 几何特征 | 躯干角 θ=arctan2(|dx|,|dy|)>angle_thr 50°(0° 站立、90° 躺平);bbox 高/宽<aspect_thr 0.5;髋中点帧间位移>0.25×bbox_h(归一化,与远近无关);FallbackCue:vis_ok=False 且宽高比<0.45 持续 2s → FALL_CUE_LOW(conf 0.6 直进 ASK) | geometry.py L45-L80(fall_features)、L58-L60(arctan2)、state_machine.py L111-L138(_is_lying / _speed_ok) |
| 5.1.2 六状态机 | IDLE/FALLING/LYING/ASK/ALERT/CANCELLED 每人一实例;时间窗 falling_window 1.5s、lying_time 3.0s、ask_timeout 12s、cooldown 60s(config L49-L70) | state_machine.py L34-L83(每帧推进)、L134-L230(各状态分支);事件分发 monitor.py L340-L383(_dispatch) |
| 5.1.3 IoU 关联 | 贪心匹配 IoU≥0.3 关联、max_age=3s 释放 ID;每 ID 独立 FSM;多人 ASK 由全局 _ask_session 静默合并(只播一次询问) | assoc.py L28-L67;monitor.py L351-L354(会话合并)、L206-L208(on_asr 单次应答 break) |
| 应答词分类 | classify_response 先匹配 confirm_words 再 cancel_words("不用救我"含歧义 → 判 ALERT,安全优先);ALERT 态只认窄表"没事/不用" | interaction.py L12-L20;state_machine.py L246-L256(respond) |
| 前置依赖 | 关键点来自 EXP-05(BlazePose 33 点肩11/12髋23/24 或 COCO17 肩5/6髋11/12),全部在原图像素坐标系计算 | geometry.py L26-L30(joint_indices 按点数选索引) |
04实验目标
- 能解释躯干角 / 宽高比 / 髋速尖峰三大判据的物理含义,并说明为什么必须"形态∧速度"双条件(防弯腰误报)
- 能默画六状态机,标注每条转移的时间窗与几何条件(1.5s / 3.0s / 12s / 60s)
- 能独立完成 救命 → ALERT 与 没事 → CANCELLED 两条端到端路径,读懂 EVENT 日志
- 能区分cooldown(只挡询问不挡跌倒)与恢复判据的不同职责
- 掌握"单变量调参 → 观察 → 记录"方法论,感受阈值与误报/漏报的权衡
05实验环境与准备
06动画讲解:六状态流转与时间窗
07互动讲解:状态机事件步进台
08实操步骤:跌倒→询问→应答全流程
state.cooldown;也要记得实验结束把 yaml 恢复默认值。09重点与易错点
- 三判据的与逻辑:_is_lying(形态)and _speed_ok(速度)才进 FALLING —— 只有其一都不算
- cooldown ≠ 恢复:冷却挡的是"重复询问",恢复判据(aspect>1.5 或角<25° 持续2s)才负责回 IDLE
- 单次应答只推进一人:on_asr 命中即 break(monitor L208),防一个"没事"触发 3 次 CANCELLED
- 确认优先:classify_response 先 confirm 后 cancel —— "不用救我"判 ALERT,歧义时安全优先
| 现象 | 原因 | 解决 |
|---|---|---|
| FALLING→LYING 未触发就回 IDLE | 跌倒后 <1.5s 迅速起身,观察窗超时(state_machine L176-L178) | 正确行为——falling_window 本就用来过滤瞬时姿态异常 |
| 第二次跌倒 ASK 不触发 | cooldown 60s 冷却期内 LYING→ASK 被挡(L189-L190) | 等冷却结束或临时调小 state.cooldown;FALLING/LYING 仍会正常推进 |
| 输入"救命"无反应 | 已超过 ask_timeout 12s,系统已自动升级 ALERT | 在 ASK 窗口 12s 内输入;超时即告警属设计行为 |
| ALERT 态说"还好/不需要"没取消 | ALERT 只认窄表"没事/不用"(L248),宽表含"没有"易被闲聊误触发 | 输入精确词"没事"或"不用"——收紧是防误取消告警 |
| 多人 ASK 重复播报 | 正常应由 _ask_session 静默合并 + on_asr break 保护 | 看日志"ASK 会话静默合并"确认;不生效则检查 monitor 版本 |
| 调参后全程不触发 | 阈值调太高(如 angle_thr: 80),实际 ang 达不到 | 看画面叠加 ang=XX 实测值再定阈值,单变量回调 |
| 演示视频多人场景无法复现 | testdata 为单人视频,IoU 关联看不出效果 | 读 assoc.py L48-L67 理解贪心匹配;或换多人素材 |
| 改了 yaml 没生效 | 改错副本或改完未重启 | 确认路径 config/fall_config.yaml,重启无 config error |
10总结与思考
跑通 fall_video_demo --text,观察完整 FALLING→LYING→ASK 流转;验证救命→ALERT、没事→CANCELLED 两条路径;完成至少一次单变量调参对比。
理解"形态∧速度"与逻辑的抗误报本质,四个时间窗(1.5/3.0/12/60s)各自过滤哪类噪声,冷却只挡询问不挡跌倒的安全考量。
每帧流水线(YOLO→IoU→姿态→特征→状态机)数据流;单变量调试→记录 EVENT 序列→分析误报/漏报的实验方法。
没触发?先查冷却 → 再查时间窗 → 最后对照画面 ang=/spd= 实测值核阈值。应答没反应?查 12s 窗口与窄/宽词表。