多模态理解:让机器人看着人,说出一句判断
前五页都在处理“话”,这一页处理“画面 + 话”。VLM 把一张裁剪图和一句提问变成一句回答, 但那个 0.8 的置信度不是模型给的,是解析代码里写死的 —— 这是本页最容易被误读的一处。
01目标:让机器人看着人说一句判断,并说清它凭什么
提问:图中的人是否摔倒了?
label=not_fall confidence=0.8
vision 1.03s / llm 4.22s
板上真跑的一次完整输出(含模型加载那几行):
I rkllm: loading rkllm model from …/Qwen3.5-0.8B_w4a16_g128_rk3576.rkllm I rkllm: rkllm-toolkit version: 1.3.0, max_context_limit: 4096, npu_core_num: 2, target_platform: RK3576 [I][vlm] VLM 就绪 [I][vlm] VLM: vision 1.03s, llm 4.22s, answer='否' label=not_fall confidence=0.8 reason=否
学完你该能做出这三件事
- 跑通一次 VLM 判断:给一张图,拿到
label与reason。 - 说清图上信息怎么变成模型能读的输入(裁剪 → 编码 → 图像向量)。
- 识破“confidence=0.8” 这类看起来像概率、其实来自解析规则的数。
_parse() 里写死的常量:
答“是”→0.8、答“否”→0.8、答“不确定”→0.5、失败→0.0。它衡量的是解析分支,不是模型信心。🔑 一句话:这一页要分清两个来源 —— 哪句是模型说的,哪个数是代码给的。
02问题:它凭什么敢说“没摔倒”
跌倒看护、手势控制、情绪安抚 —— 这些场景光靠声音不够:同一句“我没事”,站着说和躺在地上说, 意思完全不同。要判断,就得让它看。
模型吃的是 token,不是像素。一张 448×448 的图,怎么变成它能读的东西?
直接把整张监控画面丢进去问“有人摔倒吗”,不行吗?
结果只有一个词,下游状态机凭什么相信它?
🔑 一句话:让它看图不难,难的是知道那一眼有多可靠。
03现象:真跑出的一次 VLM 判断
用 vlm_demo.py 指定一张图,程序会加载视觉编码器与大模型,然后打印两段耗时、一句回答、
三行结构化结果。实测这张“站着的人”被判为 not_fall,方向正确。
| 输出项 | 实测值 | 谁给的 |
|---|---|---|
| answer | 否 | 模型生成 |
| vision 耗时 | 1.03 s | 视觉编码器 |
| llm 耗时 | 4.22 s | RKLLM 生成 |
| label | not_fall | _parse() 关键词匹配 |
| confidence | 0.8 | _parse() 里的常量 |
怎么跑出来
VLM 默认是关的(model.vlm.enabled: false)。用一份打开开关的配置副本跑,别动原配置:
终端一 · 看图判断
cd ~/ros2_ws/src/xflz_eai_car1/app/elderly_companion python3 apps/vlm_demo.py --config /tmp/fall_vlm.yaml --image testdata/stand_up.jpg
🔍 看什么:三处。
- 看两段耗时是分开的 —— vision 1.03 s(看)与 llm 4.22 s(想)不是一回事。
- 看 answer 只有一个字 —— 提示词要求“再写一句理由”,这次连理由都没给。
- 看 confidence 恰好 0.8 —— 换一张图它还是 0.8,这就是第 04 节要揭的盖。
🔑 一句话:现象里最该警觉的是那个“太整齐”的 0.8。
04原理:一帧图到一句判断的六格
先把“哪一块是重点”裁出来,再把这块图变成一串数字,最后让语言模型结合一句提问说话。 就像给医生看片子:先把病灶区域框出来放大,再问“这是不是骨折”。
| ② 的三个疑问 | 在 04 的哪几格回答 |
|---|---|
| 疑问 1 图片怎么进模型 | 步骤 3~4(裁剪 → 编码成向量) |
| 疑问 2 为什么先裁人 | 步骤 2(姿态框) |
| 疑问 3 一个词够用吗 | 步骤 6(关键词解析 + 常量置信度) |
1为什么不把整张监控画面直接丢进去?
吃什么:原始帧。做什么:先定位人。吐什么:人体框。 动参数会怎样:不裁直接问,模型要在一整张背景里找人,远小的人容易看不见 —— 等于让它在合影里判断最角落那个人的姿势。
2裁多大才合适?
吃什么:人体框。做什么:按框裁剪并缩放到编码器要求的 448×448。吐什么:标准小图。 动参数会怎样:裁太松 → 背景占比大;裁太紧 → 手脚本被切掉,而“摔倒”恰恰要看四肢。
3图怎么变成模型能读的东西?
吃什么:448×448 的图。做什么:视觉编码器(.rknn)把它切成小块逐个编码。
吐什么:一串向量,也就是模型眼中的“这张图”。
这一步在 _encode_image() 内部完成,实测 1.03 s。它和后面的语言模型是两个模型,各加载各的。
4提示词为什么要写死选项?
吃什么:向量 + 提示词。做什么:生成。吐什么:一句话。 动参数会怎样:源码的提示词是「图中的人是否摔倒了?只回答'是'或'否'或'不确定',然后写一句理由」。 不给选项,模型会写一段散文,第 6 格的关键词匹配就没法用。
5为什么“看”和“想”要分开计时?
吃什么:图与提示词。做什么:分别统计 vision 与 llm 两段。
吐什么:两个耗时数。动参数会怎样:提高分辨率或加长提示词,先变的是 llm 那段。
6“是/否”怎么变成 label,0.8 又从哪来?
吃什么:一句回答。做什么:按关键词判:含“不确定”→ uncertain(0.5); 含“是”且不含“否”→ fall(0.8);含“否”→ not_fall(0.8);其余→ uncertain(0.5)。吐什么:三元组。
confidence 是解析分支给的常数,不是模型概率。
源码 vision_judge.py 的 _parse() 里写死 0.8 / 0.5 / 0.0。
要真概率,得让模型输出数值再解析,或用多次采样统计一致性。每格答全的五列表
| 格 | 吃什么 | 做什么 | 用什么 | 吐出 | 动参数会怎样 |
|---|---|---|---|---|---|
| 1 | 原始帧 | 取一帧 | 摄像头或图片路径 | BGR 图 | 分辨率高 → 编码更慢 |
| 2 | 帧 | 找人体框 | 姿态模型(rtmpose) | 框坐标 | 无人体 → 直接不判 |
| 3 | 框 + 帧 | 裁剪缩放 | cv2.resize(448×448) | 标准小图 | 裁太紧 → 丢四肢 |
| 4 | 小图 | 编码成向量 | _encode_image | 图像向量 | 分辨率↑ → 耗时↑ |
| 5 | 向量 + 提示词 | 生成一句回答 | _run_llm(rkllm) | 文本 | 不给选项 → 散文 |
| 6 | 文本 | 判类 + 定置信度 | _parse() | 三元组 | “否”优先于“是” |
| 术语 | 一句话 | 什么时候失效 |
|---|---|---|
| 视觉编码器 | 把图压成模型能读的向量 | 输入尺寸不符会直接报错 |
| 图像向量 | 模型眼中的“这张图” | 裁剪错位置 → 描述的是背景 |
| 提示词选项 | 限定回答的词表 | 不给选项时回答不可解析 |
| 关键词解析 | 把一句话映射成枚举 | “不是摔倒”含“是”也含“否”,靠“否优先”兜住 |
🔑 一句话:模型只负责在第 5 格说话;前四格决定它看到什么,第 6 格决定你怎么理解它。
05实践:三步跑通一次看图判断
- 确认两个模型在位 约 10 分钟
看
~/models/rkllm/下有Qwen3.5-0.8B_vision_rk3576.rknn与…w4a16…rkllm两个文件。 - 开一份临时配置 约 5 分钟
把
model.vlm.enabled改成 true 并另存/tmp/fall_vlm.yaml,原配置保持关闭。 - 跑图并核对三段输出 约 15 分钟
先跑
stand_up.jpg(应得 not_fall),再换fall_1.png对比,看 label 与耗时是否变化。
对账表:命令 → 函数 → 文件
| 格 | 命令 / 配置 | 函数或变量 | 文件 |
|---|---|---|---|
| 1 | --image | 读图(cv2.imread) | apps/vlm_demo.py |
| 2 | (上游) | 姿态关键点 → 人体框 | fall/geometry.py |
| 3~4 | model.vlm.vision_model_path | _preprocess → _encode_image | fall/vision_judge.py:327,392 |
| 5 | model.vlm.llm_model_path | _run_llm(提示词 PROMPT) | fall/vision_judge.py:25,394 |
| 6 | (自动) | _parse | fall/vision_judge.py:404 |
| 开关 | model.vlm.enabled | 关时用 FakeVisionJudge(恒 uncertain) | config/fall_config.yaml:13 |
踩坑速查
| 学员原话 | 原因 | 怎么办 |
|---|---|---|
| “提示请在 config 开启 model.vlm.enabled” | 开关默认 false | 用临时配置副本打开,别改原文件 |
| “第一次跑等了好久” | 要先加载 vision .rknn 与 .rkllm | 正常;就绪后单次约 5 秒 |
| “总是 uncertain” | 开关没开,走的是 FakeVisionJudge | 确认配置真的生效(看日志有没有 “VLM 就绪”) |
| “label 对了但 confidence 一直是 0.8” | 它是解析常量,不是模型输出 | 别拿它当概率用(第 04 节步骤 6) |
| “模型没给理由” | 提示词要求写一句,但它只回了一个字 | 属实测现象;reason 就等于原话,可能很短 |
confidence 一栏的“谁给的”写的是 _parse() 里的常量 ——
这正是第 04 节步骤 6 那五行分支的直接后果。🧪 Vibe Coding 复盘
| 想要 | 提示词 | 验收判据 |
|---|---|---|
| 模块职责 | 「这个文件里哪段做图像预处理、哪段做编码、哪段做生成?各自的耗时打点在哪?」 | 点出 _preprocess / _encode_image / _run_llm 与 t0 t1 t2 |
| 控制流 | 「画出 judge() 的所有提前返回分支」 | 至少三条:加载失败、异常降级、空回答 |
| 时序 | 「从拿到裁剪图到返回三元组,哪些步骤阻塞、各多久?」 | vision 与 llm 两段分别计时 |
| AI 常见说法 | 源码实际 |
|---|---|
| “confidence 是模型输出的概率” | _parse() 里写死 0.8 / 0.5 / 0.0 |
| “一个模型既看图又说话” | 视觉编码器与大模型是两个文件、两次加载 |
| “整张图直接喂进去” | 先靠姿态框裁剪,再缩放到 448×448 |
🔑 一句话:先看日志有没有“VLM 就绪”,再看耗时分配 —— 顺序对了不会白查。
06交互:关键词与置信度的真相
📌 掌握检查:模型回答“不是摔倒”,会判成哪一类、置信度多少?先答出来再点。
三个观察点:
- 回答切成“不确定” → confidence 掉到 0.5,低于 0.70 阈值,下游应该拒判。
- 回答切成“不是摔倒” → 同时含“是”与“否”,按“否优先”判 not_fall。
- 裁剪边长调小 → 视觉那段会更快,但四肢细节丢了,“摔倒”更容易判错。
可信度说明:解析分支与常量直接照 _parse() 实现;耗时按 448 实测 1.03 s 线性折算,
只用于比较趋势。
🔑 一句话:阈值能筛掉“不确定”,但筛不掉“自信的错” —— 因为 0.8 恒等于 0.8。
07验证:三道题
Q1(原理)视觉编码器那一步输出的东西是什么?
判断是大模型在第 5 格生成的;框是第 2 格姿态模型给的。
Q2(实践)日志一直打印 uncertain,最可能的原因?
开关关掉时用占位实现,恒返回 uncertain。
Q3(边界)confidence=0.8 能当模型把握用吗?
源码三档:0.8(是/否)、0.5(不确定)、0.0(失败)。
✅ 目标对账
| ① 的目标 | 对应题号 |
|---|---|
| 跑通一次 VLM 判断 | Q2(开关与占位实现) |
| 说清图怎么变成模型输入 | Q1 |
| 识破看起来很真的假置信度 | Q3 + 互动 6-1 |
🔑 一句话:三题分别考「第 4 格吐什么 / 开关在哪 / 那个数是谁给的」。
08总结:图给证据,规则给边界
知识点关系表
| 概念 | 它解决了什么 | 和别的关系 |
|---|---|---|
| 姿态框 | 把注意力聚到人身上 | 是裁剪的前置,缺它就得整图判断 |
| 视觉编码器 | 图 → 向量 | 与大模型分开加载、分开计时 |
| 提示词选项 | 让回答可解析 | 不限定就只能得散文 |
| 关键词解析 | 回答 → 枚举 | 决定 label 与“假置信度” |
| FakeVisionJudge | 开关关时的占位 | 恒 uncertain,用于跑通主流程 |
两个反直觉
- 直觉是“置信度来自模型”,实际是解析分支写死的 0.8 / 0.5 / 0.0。
- 直觉是“多模态就是一个模型”,实际是视觉编码 + 语言模型两段,各占一半耗时(1.03 s / 4.22 s)。
注意事项
- 数值陷阱:0.8 不是概率,用它做阈值筛选只能筛掉“不确定”,筛不掉“判错但很自信”。
- 开关默认关闭,改动请用配置副本,别把发布配置打开。
- 裁剪框由姿态结果决定:姿态没检到人时,根本不会走到 VLM。
- “否优先”是有意设计,为了兜住“不是摔倒”这类否定句。
前置依赖
| 类别 | 具体 | 缺了会怎样 |
|---|---|---|
| 硬件 | 摄像头(或一张测试图) | 没有输入,judge 不会被调用 |
| 模型 | Qwen3.5-0.8B_vision_rk3576.rknn + …w4a16…rkllm | 加载失败 → 恒 uncertain 并记录原因 |
| 库 | RKNN 与 RKLLM 运行时库(lib_dir) | ctypes 加载失败,直接降级 |
| 配置 | model.vlm.enabled: true 与两个模型路径 | 开关 false 时用占位实现 |
| 环境 | NPU 可用(npu_core_num: 2) | 推理报错,返回 uncertain |
这套东西用在哪儿
- 老人看护:规则判跌倒后,VLM 二审减少误报,再决定是否呼救。
- 作业质检:拍一张装配照片,问“螺丝是否到位”,输出通过/不通过。
- 情绪安抚:看着对方面部与姿态,配合语音语气决定回话方式。
🔑 记忆点:模型说的那句话是证据,代码给的那个数是边界 —— 两者绝不能混为一谈。