十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

语义动作接口:让VLM稳定操控机械臂的工程实践

语义动作接口:让VLM稳定操控机械臂的工程实践 我最早有让 VLM 直接操控机械臂这个想法是被一次实验打击出来的。当时拿一个开源视觉语言模型接 UR 机械臂走了最直白的路子把相机画面丢给模型让它输出抓取点的三维坐标。结果模型给我返回了一个小数点后六位的坐标精度唬人手爪落点却偏出去二十多厘米差点把旁边一杯咖啡撞翻。后来我意识到问题不在模型不行而在你把一个预测任务错当成了控制任务。VLM 擅长的是理解画面里有什么、该干什么而不是精确回归连续坐标。Show-Harness 这个项目核心思路就是把机械臂能执行的所有操作抽象成一套语义动作接口让 VLM 在动作空间里做选择再由控制器负责把动作翻译成关节指令。这篇内容就围绕这套方案展开为什么会想到语义动作这条路、接口怎么设计、链路怎么打通、实际跑起来会踩哪些坑。写这篇文章的目的是想给正在做具身智能、机器人视觉抓取、或者正在被VLM 输出坐标很不稳定困扰的人一个可以借鉴的框架。无论你手上是 UR、Panda 这类工业臂还是幻尔、OpenArm 这类教育级开源机械臂这套思想都适用。文章会偏工程实践代码骨架和排查经验都会给不搞纯学术那套。1. 连续坐标输出的坑VLM 天生不是干这个的1.1 坐标误差只是表面泛化失败才是本质让 VLM 直接输出末端坐标表面看是精度不够实际是任务定义错了。VLM 的训练数据以图文理解为主它见过的坐标形式多样且极其混乱——有人用百分比、有人用像素、有人用经纬度式写法。你让它输出相机系下的毫米坐标它对这种数值空间的先验知识几乎为零。更关键的是坐标数值空间的微小变化对模型来说没有语义区分度在语义空间里向左挪五厘米和向左挪二十厘米是两个概念但在数值回归里它们只是光溜溜的数字。VLM 的 Transformer 结构对 token 级别的数值预测天然不擅长输出 0.324 还是 0.325 对它来说没有本质区别可到了机械臂这里就是几毫米的差别。我做过一个对照组实验同一个 VLM在同一个桌面场景里让它直接输出放置点坐标做将积木放到右下角连续跑 20 次坐标分布的标准差接近 8 厘米。把它换成从三个预定义放置位置里做选择准确率从六成跳到九成。这个结果基本验证了一个方向——不要让模型做回归让它做分类。1.2 一次失败实验的完整复盘项目初期我搭了一条这样的流水线RGB-D 相机采集图像用 Grounding DINO 做开放词汇检测把目标物像素中心映射到机械臂基座坐标系再由 VLM 直接输出基座系坐标。听起来每一步都有现成工具实际上跑起来灾难现场坐标系映射误差积累像素误差经过相机内参、外参、手眼矩阵层层放大2 个像素的偏差到基座坐标系变成 1.5 厘米。VLM 对数值的一本正经胡说八道它经常输出图像里根本不存在的位置比如墙面、桌子边缘以外。缺乏物理合法性约束模型不知道机械臂的关节限位输出的坐标可能在机械臂根本够不到的工作空间外。这次实验让我彻底想明白一件事机械臂控制是一个从语义到数值的翻译过程。语义部分该抓哪个、该放哪是 VLM 的主场数值部分关节角多少、轨迹怎么插值是控制器的强项。强行让 VLM 跨界做数值预测是拿它的短板去拼别人的长板。1.3 连续动作与语义动作的差异对照从实际操作体验来看两种思路的差异可以明确列出来维度连续坐标输出语义动作接口模型任务类型数值回归VLM 弱项选项分类VLM 强项输出形式浮点数坐标动作 ID 结构化参数泛化能力换场景精度崩坏预定义动作任意场景复用物理合法性无法约束可能超出工作空间动作库与控制器共同保证安全干预点只有坐标校验一道防线动作名 参数双重校验失败恢复需要重新给定新坐标只需选择另一个动作这个表格不是理论推演是方案的可行性判断。当你把问题从让模型猜数字换成让模型做选择VLM 的容错空间会大很多控制侧的兜底能力也能真正落下去。2. 语义动作接口的核心动作原语、参数化与白名单2.1 把连续空间切成离散原语Show-Harness 的第一步是建立一套机械臂的动作原语库。所谓原语即不可再拆分的基本动作单元。比如对一个桌面抓取任务我会定义 pick、place、push、pour、stack、inspect 这几个原语。每个原语对应一个可执行的控制器函数VLM 不需要知道函数内部怎么算逆解、怎么规划轨迹只需要输出动作 ID。这里有个关键设计取舍动作原语一定要少而精。初期我把动作定义得太细比如单独定义了抓取顶部和抓取侧面模型经常选错因为语义边界太模糊。后来改成动作只保留粗粒度意图精细参数由控制器通过附加信息计算效果明显变好。原理也很好解释VLM 擅长区分抓和推这种大方向的动作但不擅长区分以 45 度还是 60 度姿态抓取这种细微的操作差异。2.2 参数化动作 ID 不是终点还需要结构化参数光有动作 ID 不够丢给了控制器一个pick控制器也一头雾水——抓哪个从哪个位置抓机械臂末端用什么姿态所以每个动作原语都要配套一个参数化接口。Show-Harness 里的做法是让 VLM 不仅输出动作 ID同时输出一组结构化参数统一采用 JSON 格式{ action: pick, target: red_cube, approach_height: 0.15, grasp_width: 0.05, timeout: 5.0 }注意这里有个反直觉的设计approach_height、grasp_width 这类连续参数我一开始想交给 VLM 输出后来发现完全没有必要甚至有害。VLM 输出 0.15 还是 0.20 米的意义不大而且数值波动会影响系统稳定性。最终方案是动作参数里的连续值大部分由控制器根据目标物体几何属性自动计算VLM 只输出语义级参数——比如目标物体名、放置区域名、力度档位轻/中/重、速度档位慢/正常/快。这样设计的好处有两个。其一真正落到模型输出端的几乎全是离散 token 或语义 token符合 VLM 的能力边界。其二连续参数的计算集中在控制器里可以用传统视觉算法精确获得不依赖模型状态系统复现性好。2.3 白名单机制用规则给大模型上紧箍咒VLM 再聪明也会偶尔开脑洞输出一个不存在的动作名或者给动作配一个完全离谱的参数。所以 Show-Harness 在模型输出和控制器执行之间加了一道白名单校验层。所有从模型返回的动作元组必须满足三个条件才能进入执行动作名称必须存在于注册表中否则丢弃并反馈 bad_action。动作参数中的实体名称如目标物体名必须出现在当前感知列表里防止模型编造一个不存在的目标。动作参数的数据类型和取值范围必须合法——力度档位只能是轻/中/重速度只能从枚举值中选。这道校验在调试阶段帮我拦下了大量奇葩输出。最典型的一次VLM 输出一个 dance 动作注册表里根本没有校验层直接拦截避免了一次莫名其妙的机械臂运动。从安全角度看这道白名单的价值甚至大于 VLM 本身的能力——因为模型能力再强也无法保证 100% 不出错但规则可以把错误限制在可控范围内。3. Show-Harness 的完整链路图像到关节指令怎么流转3.1 感知层视觉上下文与可交互实体列表整个链路的起点是相机。Show-Harness 里推荐使用 RGB-D 相机我用的是 RealSense D435因为它能同时提供颜色和深度信息对目标定位和几何尺寸估算很关键。感知层要做两件事第一把目标物体的位置、类别和几何属性提取出来第二把这些信息整理成 VLM 能看懂的结构化文本。我采用 Grounding DINO 做开放词汇检测把有没有目标物体这个问题交给传统视觉工具解决而不是让 VLM 在图像上画框。得到每个目标物体的 2D 检测框后用深度图取框内深度中值结合相机内参计算出物体在相机坐标系下的三维位置再经手眼矩阵变换到机械臂基座系。这一步的精度直接决定了后面动作执行的成功率我一般要求像素误差尽量在 2 个像素以内。检测完成后一切信息被序列化成一段 Prompt 上下文交给 VLM。这个上下文格式要设计得足够固定否则模型输出会飘当前可执行动作pick, place, push, pour, stack 当前可交互实体red_cube(位置: 0.32, 0.45, 0.02), green_cylinder(位置: 0.51, 0.36, 0.02), blue_container(位置: 0.28, 0.62, 0.02) 任务指令将红色立方体放入蓝色容器 请输出动作指令JSON3.2 推理层VLM 的输出约束与解析策略VLM 推理这一步最怕的是模型输出不规范 JSON。实际测试中即使系统明确要求输出 JSON模型偶尔也会夹杂解释性文字、换行符缺失、键名拼写差异等问题。所以推理层不仅仅是一个 API 调用还包括一个强健的输出解析模块。我用了三个措施来保证解析稳定。第一在 Prompt 末尾加入 few-shot 示例给一个正例和一个反例输出可靠性会明显提升。第二解析时用正则把模型输出里的代码块标记剥离保留 JSON 主体部分。第三JSON 解析失败时不会直接放弃而是进入重试模式——将报错信息反馈给模型并让它重新输出最多重试两次。实测下来这套组合策略能把一次解析成功率从七成多提升到九五成以上。关于推理端模型选型我用了 Qwen2-VL-7B 作为主力模型同时给一个可插拔的接口方便替换成 LLaVA、Phi-3-Vision 等。如果场景要做私有化部署或者任务有强领域特征还可以用 LLaMA-Factory 对 VLM 做轻量微调把动作库的格式规范直接内化到模型参数里减少对提示工程的依赖。3.3 执行层动作解析、安全校验与 ROS 2 异步控制模型输出解析完成后执行层是链路中最体现工程性的一环。我在 Show-Harness 里把执行层包成一组 ROS 2 动作库每个动作原语对应一个 action client 调用向机械臂控制节点发送目标位姿。机械臂这边我用的是 UR5e控制端跑 ur_robot_driver配合 MoveIt 2 做运动规划。整个控制链路是语义动作 → 目标末端位姿 → 逆解 → 轨迹规划 → 关节插值 → 指令下发。执行层必须做异步控制不能同步阻塞等待。比如吸盘抓取动作机械臂到位、下压、吸取、抬升这套流程每一步之间都有反馈等待同步写法会让整个系统卡住。我改成状态机驱动的异步流程——每个动作内部维护一个状态机收到反馈后自动切换下一步同时通过 ROS 2 topic 把状态发出来给上层监听。执行层还有一个容易忽略的问题动作超时与机械臂偏差。工业臂的末端绝对定位精度虽然能达到亚毫米级但在视觉引导抓取场景里实际偏差往往来自物体尺寸估计不准、抓取姿态与物体局部几何不匹配。所以我在动作执行前会加一次预演校验——在仿真里跑一遍目标位姿的可行性检查确认逆解存在且无碰撞再下发真实指令。3.4 反馈层文本化回环让 VLM 知道自己干得怎么样纯开环的语义动作方案是不完整的。VLM 选错了动作、抓空了、或者目标被碰倒系统必须能感知并进入纠正逻辑。Show-Harness 在反馈层做了两件事执行结束状态上报以及失败信息的语义化。机械臂执行动作后控制节点会返回 success 或 failure 及失败原因。但这些底层状态不能直接丢给 VLM得先做语义化。比如move_group 规划失败会被翻译成目标位置不可达gripper 未检测到力反馈变化会被翻译成抓取失败目标物体不在预期位置。翻译后VLM 拿到这些文本反馈下一轮决策才能有的放矢。这个设计让我想到了人干活时的自我纠错你伸手去拿杯子没拿到你会先看到自己的手在什么位置再重新伸手而不是无视结果继续做。VLM 也一样——当它知道上一个动作失败的具体原因它更有可能在下一步选择正确的动作。我在长任务测试里看到过很漂亮的自我纠正行为第一次 pick 失败后模型看到目标物体位置与预期不符的反馈没有再次 pick而是改为 inspect 动作重新扫描桌面后再决定整个流程非常接近人的思考节奏。4. 环境搭建与选型ROS 2、UR5e 与开源 VLM 的组队方案4.1 仿真先行Gazebo Harmonic 里把链路跑通再上真机做机械臂控制类的项目我的经验是一定要仿真先行。你不可能真机一遍遍试错撞一次桌子就是一台机械臂的钱。我在 Ubuntu 24.04 上搭了一套 ROS 2 Jazzy Gazebo Harmonic UR5e 的仿真环境先把 Show-Harness 的完整链路在虚拟环境里跑通再迁移到真机。仿真环境的搭建有几个坑要提前说。首先是 URDF 文件的获取UR5e 的官方描述文件可以从 ros2_ur_robot_driver 或描述包直接拉取但要注意版本匹配——ROS 2 Jazzy 和 Gazebo Harmonic 对机器人描述格式的解析方式有变化旧版本的 URDF 可能因为缺少gazebo扩展标签导致物理属性不生效。其次是 MoveIt 2 的配置生成 moveit_config 时一定要选对使用的运动规划库我一般用 OMPL 的 RRTConnect在抓取这种短距离移动场景下速度快且稳定。仿真里跑通后再切真机能省掉大量调试时间。不过仿真也掩盖了很多问题仿真中的物体不会因为抓取姿态不对就滑落真实物理中这种失败非常常见。所以仿真验证的结论要打折扣它只能证明链路通不能证明方案稳。4.2 ROS 2 action 与 service 的取舍Show-Harness 里的通信层我看过一个容易踩的坑到底该用 ROS 2 action 还是 service。两者都是请求-响应模式但语义差别很大——action 适合时长较长、可中断、有中间反馈的任务service 适合短平快、一次响应的任务。机械臂执行一次 pick 动作动辄需要几秒钟期间还可能被取消或失败重试天然契合 action 模型。所以我在控制层统一使用 ROS 2 action 包一层接口。得益于这个选择我实现了很关键的运行中取消功能。当 VLM 决策后发给控制器一个动作但控制器在执行中检测到异常比如碰撞检测触发action 可以被客户端取消机械臂立即停止而不是等整个动作跑完才反馈。这在调试阶段救了我很多次——有一次规划出的轨迹会撞到旁边的立式支架碰撞检测触发后我通过取消 action 让机械臂停在了半空避免了事故。4.3 VLM 推理方案API 调用还是本地部署VLM 推理可以走云服务 API也可以本地部署两种方式我都试过。云 API 的优势是模型版本新、开箱即用缺点是延迟不可控一次推理可能要 2~5 秒而且数据要出内网存在敏感信息泄露风险。本地部署推荐用 vLLM 或 llama.cpp 做推理加速Qwen2-VL-7B 在单张 24 GB 显存的卡上配合 vLLM 能做到毫秒级首 token 延迟整体端到端决策时间控制在 1 秒以内明显更适合做机器人任务。如果任务有很强的领域属性比如机械臂要在特定产线上分拣固定几类零件我建议用 LLaMA-Factory 做 LoRA 微调。微调时只用几百条样本就够重点是让模型学习动作库的格式约束和任务语义而不是从头学视觉理解。这样做之后Prompt 可以简化很多模型输出格式的稳定性也有明显提升同时对 Prompt 注入攻击的抵抗力也会增强。5. 实操走通让 UR5e 完成一次有纠错能力的抓取5.1 最小可跑骨架从相机到动作执行的代码组织Show-Harness 的代码组织不复杂核心是三层perception、decision、execution。我给出一个最小可跑骨架的关键部分语言用 Python基于 rclpy# decision/vlm_policy.py import json, re from transformers import AutoModel, AutoProcessor class VLMPolicy: def __init__(self, model_pathQwen/Qwen2-VL-7B-Instruct): self.processor AutoProcessor.from_pretrained(model_path, trust_remote_codeTrue) self.model AutoModel.from_pretrained(model_path, trust_remote_codeTrue, device_mapauto) def decide(self, image, prompt): messages [{role: user, content: [{type: image}, {type: text, text: prompt}]}] text self.processor.apply_chat_template(messages, tokenizeFalse) inputs self.processor(text[text], images[image], return_tensorspt) outputs self.model.generate(**inputs, max_new_tokens256) raw self.processor.decode(outputs[0][inputs[input_ids].shape[1]:], skip_special_tokensTrue) return self._parse_json(raw) def _parse_json(self, raw): match re.search(r\{.*\}, raw, re.DOTALL) if not match: return None try: return json.loads(match.group(0)) except json.JSONDecodeError: return None# execution/safety_checker.py ALLOWED_ACTIONS {pick, place, push, pour, stack, inspect} ALLOWED_ENTITIES {red_cube, green_cylinder, blue_container} def safety_check(action_data, detected_entities): if action_data is None or action not in action_data: return False, bad_action if action_data[action] not in ALLOWED_ACTIONS: return False, unknown_action target action_data.get(target, ) if target and target not in detected_entities: return False, unknown_target return True, ok这个骨架已经把系统的关键思路全部体现了模型只负责输出语义动作和实体名安全校验收口控制动作由执行层负责。5.2 手眼标定与坐标系转换精度问题的重灾区视觉引导机械臂的精度问题一半以上出在坐标系转换。手眼标定分为眼在手上和眼在手外我常用的是眼在手外——相机固定在工作空间上方。标定方法用的是棋盘格法把棋盘格放在机械臂末端移动机械臂到多个不同位姿同时记录棋盘格在相机坐标系下的位姿和机械臂末端在基座坐标系下的位姿通过求解 AXXB 得到相机到基座的变换矩阵。标定误差在 2 毫米以内算是可接受水平。实际操作中要注意机械臂的运动范围不要过小至少覆盖工作空间三分之二区域棋盘格的拍摄角度要多样化不要只在正下方拍。还有一点经常被忽略——标定完成后的相机固定稳定性相机支架哪怕松一点点整个手眼矩阵就失效了我之前被这种问题折磨过两次后面干脆在支架上做了记号每次实验前先对照一下位置。坐标系转换这块目标物体的三维位置从相机系转到基座系是这么算的P_base T_base_camera * P_camera其中 T_base_camera 是手眼标定得到的 4x4 齐次变换矩阵。注意这里不能只用平移向量旋转部分错 1 度在 1 米外会带来约 1.7 厘米的误差直接保证了抓取失败。5.3 我踩过并修好的几个坑调试这套系统的过程中有几个问题非常典型特别值得写出来当反面教材。坑一VLM 输出格式不稳定。命令要求输出 JSON但它经常输出 markdown 代码块或者解释文字和 JSON 混在一起。解决办法是 few-shot 示例加正则提取加失败重试三层兜底。后来微调模型之后这个问题基本消失了。坑二目标物体位置的语义匹配失败。感知层检测出物体后我用 Grounding DINO 的类别名作为实体名比如 cube。但 VLM 的输出是 red_cube两者匹配不上导致白名单校验拦截掉本该执行的动作。解决方式是引入别名映射表同一实体允许多种名称写法——cam_0 和 red_cube 都指向同一个实体 ID。坑三动作执行超时导致的假失败。机械臂运动规划偶尔会因为奇异点问题卡住动作超时后被判定失败但其实换个路径就能到达。我在执行层增加了对 MoveIt 规划失败的反馈类型区分不可达和超时是不同情况反馈给 VLM 时措辞会相应变化引导模型换一个动作或换一个目标位置。坑四机械臂偏差累积。UR5e 重复定位精度不错但长时间运行后末端执行器的抓取姿态会因为夹具磨损产生微小变化导致夹取位置逐渐偏离。这个坑在仿真里完全看不出来只能靠真机实验发现。解决思路是定期做一次末端力觉自检用轻微接触检测校准夹具零点。5.4 一整套抓取任务的完整流程演示最后放一个完整的演示流程。场景是桌面上有红色立方体和蓝色容器任务指令是将红色立方体放入蓝色容器。RGB-D 相机拍摄当前场景感知层检测出 red_cube 和 blue_container 的位置。系统构造 Prompt包含可用动作列表、可交互实体列表和任务指令送进 VLM。VLM 输出 {action: pick, target: red_cube, speed: normal}。安全校验通过后控制器计算 red_cube 的三维位置规划抓取姿态下发动作给 UR5e。机械臂移动到物体上方下降闭合夹爪抬起。执行层反馈 pick 成功。但此时 VLM 不急于执行下一步而是先进入评估流程——感知层重新拍照确认 red_cube 已在夹爪中。确认成功后VLM 输出 {action: place, target: blue_container, speed: slow}控制器执行放置动作。放置完成后感知层再次复核确认任务完成。这 8 步看起来简单但每一步之间的反馈通道是整个系统稳定运转的关键。尤其是第 6 步的暂停评估设计虽然多花了一次推理和拍照的时间但大幅减少了连续动作中的错误累积。6. 一些我对这套方案的补充看法把这套 Show-Harness 方案从零搭起来再在真机和仿真上反复跑任务之后我的体会是语义动作接口并不是一种妥协它恰恰是让大模型进入机器人控制的正确姿势。VLM 的价值在于它具备常识推理和语义理解能力这些能力在任务规划、物体识别、失败恢复上能发挥巨大作用而精确的数值计算、底层执行和安全性保障留给控制工程师和传统算法反而更可靠。如果你正准备开始做类似的项目我建议不要一上来就追求端到端——把 VLM 的输出直接映射成关节角。那是一条极其依赖数据、容错率极低的路大多数团队不具备那个条件。语义动作接口的思路门槛低、见效快、可逐步演进你可以在动作原语库里慢慢增加新动作也可以逐步把更多决策权交给模型我很看好这个方向在实际项目里的落地潜力。
返回列表