综合项目 / 老人跌倒监护 / EXP-06
EXP-06 · 第 2 章 · 综合性 · 4 学时

跌倒判定状态机:从几何信号到"要不要问一句"

检测与姿态(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"两条应答路径,并用单变量调参感受误报/漏报的权衡。

难度 ⭐⭐⭐ · 4 学时 几何三判据六状态机 cooldown 60sIoU 关联

01问题引入:弯腰捡鞋为什么不能算跌倒

护理场景里最怕两件事:老人真跌倒了系统没吭声(漏报),或老人只是弯腰捡只鞋就被追问"您还好吗"(误报)。前者致命,后者扰民 —— 都会让照护者不再信任这套设备。可是从关键点坐标到"跌倒"二字之间,隔着一连串判断:单帧像躺着就够吗?躺一下马上起来算吗?要不要先问一句再报警?

1只看单帧 → 弯腰、下蹲都会"看起来像躺",误报满天飞
2加入速度尖峰 → "像躺"且"正在快速倒下"才算跌倒萌芽
3加观察时间窗 → 持续躺 1.5s 才确认、躺满 3.0s 视为"起不来"
4先问一句 → 12s 应答窗口内问明白,救命才告警、没事就取消
💡 一个类比这就像机场安检:形态可疑(躺姿)是初筛,速度异常是触发警报,观察窗是复核,ASK 询问是最后的人工确认——层层过滤,才敢按下红色按钮。

02原理通俗讲解:像"问诊确认流程"一样的状态机

把状态机想象成分诊台的问诊流程 —— 病人(画面里的人)在不同队列之间流动,每个队列有自己的"放行条件":

IDLE → FALLING
"疑似摔倒,先盯住他"

形态 + 速度双条件同时成立:躯干角>50°或宽高比<0.5(像躺),且髋部速度>0.25×框高(正在快速倒下)。进入 1.5s 观察窗。

FALLING → LYING
"确实躺了 1.5 秒"

观察窗内持续满足躺姿判据 → 确认躺地;若 1.5s 内人又站起来了 → 回 IDLE(瞬时异常,正确免疫)。

LYING → ASK
"躺了 3 秒还没起来,问一句"

持续躺地超过 lying_time 3.0s 视为"起不来",进入 12s 应答窗口,TTS 播报"您还好吗?需要帮助吗?"。cooldown 60s 内不重复触发。

ASK → ALERT / CANCELLED
"听他怎么说"

确认词(救命/帮/疼/起不来/摔/救)→ 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,防起身瞬间抖动立刻重入告警
🔔 人文关怀与隐私底线ASK 的存在本身就是关怀设计:先给老人 12 秒自己解释的机会,而不是跌倒瞬间就呼叫子女/急救。且全部判定在端侧完成、只在 ALERT 时落证据三件套 —— 隐私数据不出家门,告警有据可查。

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实验环境与准备

1硬件:实验箱 B2 / 小车 C1(RK3576 + NPU)—— 本实验纯离线视频,无需麦克风/扬声器
2软件:Ubuntu 22.04 · Python 3.10;models/ 下 yolov8n.rknn 与 RTMPose 权重就位(env_check 通过)
3素材:testdata/fall_slow_x264.mp4(慢速跌倒视频,本地已有)
4核心模块:fall/monitor.py · state_machine.py · geometry.py · assoc.py · interaction.py;配置 config/fall_config.yaml 的 state / interaction 两段
5改参数前备份 fall_config.yaml,实验结束恢复默认;用例参照 docs/08_视觉测试用例.md

06动画讲解:六状态流转与时间窗

IDLE 待机 FALLING 观察窗1.5s LYING 躺3.0s ASK 窗口12s ALERT 确认词/超时 CANCELLED 取消词 形态∧速度 持续躺1.5s 躺满3.0s 救命/超时12s 没事/不用 cooldown 60s 只挡 ASK,不挡新跌倒 恢复: aspect>1.5或角<25° 持续2s cooldown结束→IDLE 帧特征: ang=57 > 50° 且 spd=0.31 > 0.25×h → FALL_CUE 六状态机 · 每人一实例 · 纯逻辑无IO · IoU≥0.3 保持 person_id
动画 6-1:六状态主链(实线正向 / 虚线取消与恢复)——"形态∧速度"触发 1.5s 观察窗,躺满 3.0s 进入 12s 询问窗;确认词或超时升 ALERT、取消词进 CANCELLED(灰框);冷却期只挡 ASK、恢复判据需持续 2s。底部进度条示意单帧三判据的与逻辑。

07互动讲解:状态机事件步进台

🕹️ 六状态机事件步进台 · FSM Bench —— 按事件逐个点,观察状态与日志如何推进
🔔 试试连点点「救命」后再点「输入救命」—— ALERT 态只认窄表"没事/不用",确认词已无处可去;而 ASK 阶段先点「没事」再想告警,会落到 CANCELLED + 60s 冷却,这正是"取消容易、告警谨慎"的不对称设计。

08实操步骤:跌倒→询问→应答全流程

1启动离线调试(--text 用 stdin 代替麦克风)
2观察 EVENT:IDLE→FALLING→LYING→ASK,画面框色 黄→橙→紫,叠加 ang=/spd=
3ASK 窗口内 stdin 输入「救命」→ ALERT,证据三件套落盘 runs/evidence/
4重新运行、改输「没事」→ CANCELLED,播报"好的,请注意安全",框变灰
5单变量调参:angle_thr 50→30、speed_factor 0.25→0.10,对比误报变化后恢复
6按 docs/08 T8-4/T8-5 逐场景断言(正面跌倒/弯腰/多人/起身)
# ① 启动(终端1):离线视频 + 文本模式 cd ~/ros2_ws/src/xflz_eai_car1/app/elderly_companion python3 apps/fall_video_demo.py --video testdata/fall_slow_x264.mp4 --text --mjpeg # ② 预期 EVENT 日志(真实样例) [I][sink] EVENT {'person_id': 1, 'from': 'IDLE', 'to': 'FALLING', 'confidence': 1.0} [I][sink] EVENT {'person_id': 1, 'from': 'FALLING', 'to': 'LYING', 'ts': 1787305706.34} [I][sink] EVENT {'person_id': 1, 'from': 'LYING', 'to': 'ASK', ..., 'vlm': {'label': 'uncertain'}} [I][tts] [TTS] 您还好吗?需要帮助吗? [I][monitor] ASK 会话已存在(person 1),person 2 进入 ASK 静默合并 # ③ 终端1 stdin 输入(12s 窗口内) 救命 # → EVENT from=ASK to=ALERT,日志 ALERT: {...},画面 ALERT=True 没事 # (需重启)→ EVENT from=ASK to=CANCELLED + "好的,请注意安全" # ④ 单变量调参:编辑 config/fall_config.yaml state 段(改前备份!) # angle_thr: 30.0 # 50→30 更敏感,弯腰是否误入 FALLING? # speed_factor: 0.10 # 0.25→0.10,正常行走是否误触发? python3 apps/fall_video_demo.py --video testdata/fall_slow_x264.mp4 --text # ⑤ 收尾清理(Ctrl+C 后) pkill -f fall_video_demo; pkill -9 aplay
💡 快速测试提示cooldown 60s 会挡住同一进程内的第二次 ASK —— 想反复触发就重启程序或临时调小 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 窗口与窄/宽词表。

🟣 思考题 1把 angle_thr 从 50° 调到 30°,哪些日常动作可能进入 FALLING?结合 state_machine L111-L126(_is_lying)与 L127-L138(_speed_ok)分析:为什么单纯调低角度阈值还不够,必须保留速度尖峰作为第二条件?若去掉速度条件,弯腰捡物会怎样?
🟣 思考题 2monitor.py L206-L208 的 on_asr 为什么在命中一次后就 break?若去掉它,两人同时处于 ASK 时输入一个"没事"会发生什么?结合 L351-L354 的 ASK 全局会话合并,说明系统如何做到"只播一次询问、只开一个应答窗口、一次应答只推进一人"。