Gazebo 里能显示,不等于模型是对的:机器狗建模的 6 个检查项
Gazebo 里能显示,不等于模型是对的:机器狗建模的 6 个检查项
系列 03/10:从一个 90° 轮轴错误出发,建立可用于导航验证的 Gazebo 机器人建模检查表
定位:以真实电厂机器狗巡检项目为贯穿案例,提炼可迁移的机器人巡检仿真开发方法。
适合读者:机器人、自动化、计算机、电子信息等专业学生,以及准备进入 ROS 2、机器人软件、仿真、导航或测试方向的求职者。
上一篇:机器人巡检仿真如何形成闭环:从任务到评测的完整数据流
下一篇:机器人为什么会“穿越时间”:ROS 2 仿真时钟与 TF 排错实战
机器狗已经出现在 Gazebo 里,外观尺寸正常,导航系统也在尝试控制它向前运动。
但机器人就是走不对:本来应该沿正前方直行,Gazebo 报告的模型姿态却出现约 48° 的异常侧倾,Y 和 Z 方向也产生了不该有的位置偏移。
异常没有停留在画面和物理层。GPS 发布器读取了错误的模型世界位姿,融合定位据此计算出错误偏移,最后 Nav2 得到错误的机器人位置,控制机器人朝错误方向运动。
这就是项目中后来被称为“90° Bug”的故障现象:一条轮子旋转配置写错了轴,表面看只是模型几何问题,实际却沿着“物理位姿 → GPS → 定位 → 导航”一路传播。
这类问题最容易让人先怀疑规划器、控制器或定位参数。可在我们的电厂机器狗案例中,主要根因最终落在一行看起来很普通的模型配置上。
SDF 的 <pose> 依次包含六个字段:
x y z roll pitch yaw
前三项是位置,后三项是以弧度表示的旋转角;roll 绕 X 轴、pitch 绕 Y 轴、yaw 绕 Z 轴。原配置把 1.5708 写在了第五个字段:
<!-- 错误:pitch = 90°,圆柱轴从 Z 转到 X -->
<pose>0 0 0 0 1.5708 0</pose>
轮子的关节轴却是 Y:
<axis>
<xyz>0 1 0</xyz>
</axis>
圆柱几何轴与关节旋转轴互相垂直。模型仍然能加载、能显示,插件也能收到速度指令,但它已经不是我们以为的那个物理系统。
轮轴旋转的核心修正,是把 90° 放到 roll:
<!-- 正确:roll = 90°,圆柱轴从 Z 转到 Y -->
<pose>0 0 0 1.5708 0 0</pose>
不过,现有回归结果不能被解释为“只改这一行就证明所有问题已经解决”。同一修复批次还调整了轮惯量方向、显式 Link/Joint 位姿、导航参数和里程计坐标。轮轴旋转被回归报告认定为主要根因,但动态回归验证的是整个修复批次。
一个参数为什么能从轮子一直影响到导航?因为机器人模型并不是一张三维图片,而是几何、质量、约束、接触、传感器和控制共同组成的因果系统。
这篇文章不追求列完所有 SDF 标签,而是给出六个真正能发现问题的检查项。
一、先看懂 Gazebo 模型的五层结构
在检查参数前,先把几个经常混在一起的概念拆开。
World:机器人所处的世界
World 定义地面、墙体、设备、光照、重力、物理引擎和仿真步长。案例中的电厂场景为 30 m × 30 m,使用名为 DART 的物理引擎,最大步长为 0.002 秒,目标更新率为 500 Hz。
机器人参数完全不变,换一个物理引擎、地面摩擦或仿真步长,运动结果也可能变化。因此,不能只保存机器人模型而不记录 World 配置。
Model:一个完整实体
Model 把机身、轮子、传感器和插件组织成一台机器人。它有自己的世界位姿,也可以包含多个 Link 和 Joint。
Link:具有物理属性的刚体
Link 不只是“零件名称”。它通常同时包含三类描述:
visual:渲染器画什么;collision:物理引擎拿什么发生接触;inertial:物体有多重,质量如何分布。
Joint:两个 Link 之间怎样运动
Joint 定义父子关系、旋转轴、运动范围、阻尼和摩擦。驱动轮通常使用 revolute(旋转)关节,传感器通常通过 fixed(固定)关节安装,万向支撑则可能使用 ball(球形)关节。
Plugin:模型具有什么行为
DiffDrive 插件把速度指令转换为左右轮运动,并发布里程计与 TF。传感器系统、姿态发布和接触检测也由相应插件或系统完成。
还要区分 SDF 与 URDF。简单来说,SDF 更直接地描述 Gazebo 世界、物理模型、传感器和插件;URDF/Xacro 常用于 ROS 侧的机器人结构、TF 和 RViz 显示。项目同时维护两份描述时,不能默认轮距、半径、坐标原点和传感器外参——也就是传感器相对机身的安装位置与方向——会自动保持一致。
二、检查一:模型树和参考坐标是否清楚
第一项不是检查模型好不好看,而是画出父子树:
world
└── dog_kinematic model
├── base_link
├── left_wheel ← left_wheel_joint
├── right_wheel ← right_wheel_joint
├── caster_wheel
├── lidar_link
├── imu_link
├── rtk_link
└── camera_link
对每个 Link 和 Joint,都要回答三个问题:
- 位姿相对于谁?
- 原点放在哪里?
- 这个坐标是否又被父级变换了一次?
模型错误经常不是某个数字明显离谱,而是同一个偏移分别写在 Link 和 Joint 中,被重复应用;或者开发者以为位姿相对 base_link,解析器实际按另一语义处理。
案例修复时,我们把左右轮和支撑轮的位置显式放到 Link 上,并把对应 Joint 位姿设为 (0,0,0)。这样关节位于子 Link 原点,减少物理引擎解析隐式偏移的空间。
如果项目还维护 URDF/Xacro,应建立一张跨文件参数清单,至少核对:
base_link原点:SDF、URDF 和插件中的robot_frame分别指向哪里;- 轮半径:SDF 接触几何、URDF 显示几何和插件
wheel_radius是否一致; - 左右轮距:SDF 两个 Link 的 Y 坐标差、URDF 两个 Joint 的 Y 坐标差和插件
wheel_separation是否一致; - 传感器外参:SDF Sensor Link、URDF TF 和消费节点期望的 frame 是否一致。
这不是只针对别人的检查表。它在当前案例中确实发现了尚未清理的描述漂移:Gazebo SDF 与 DiffDrive 使用 0.50 m 轮距,URDF/Xacro 中的轮距仍为 0.40 m;URDF 的圆柱惯量宏仍把轴向惯量放在 Z,而轮子显示几何已经旋转到 Y。Gazebo 当前直接加载 SDF 执行物理仿真,因此这两项不会改变本文引用的 DART 物理结果,但会让 ROS 侧描述与 Gazebo 模型不一致,后续仍应修正。
另外两类差异需要单独判断。当前 SDF 使用“圆柱外观 + 球形碰撞体”,URDF 使用圆柱碰撞体;URDF 碰撞体不参与当前 Gazebo 物理,因此不会改变本文的物理结果,但这究竟是有意分工还是尚未同步,仍应在模型契约中写清。支撑轮在两份描述中的最终接地高度均为 0.03 m,并不存在高度漂移。诚实记录这些差异,才能避免“Gazebo 里在这里,RViz 里却在那里”的双模型问题。
三、检查二:visual、collision、inertial 有没有各司其职
这是“能显示不等于模型正确”的核心。
visual 只决定看起来像什么
可以使用精细网格、材质和装饰腿,让模型接近真实机器狗。但 visual 本身不阻止穿墙,也不决定机器人怎样倒下。
collision 决定物理世界碰到什么
碰撞体通常应该比 visual 更简单。复杂网格会增加接触计算成本,还可能产生尖角、凹面和细碎接触。
案例中机身 visual 是 0.60 × 0.30 × 0.35 m 的盒体;用于物理碰撞的机身盒体更矮,并抬离地面,避免机腹抢先与地面接触。轮子外观看起来是圆柱,但当前碰撞几何采用球体。
这不意味着“球形轮一定更稳定”。项目做过 20 次圆柱轮和 20 次球形轮的冷启动 A/B 测试,在 DART、500 Hz、摩擦系数 1.0 的测试配置中,两者的前进距离、转向角和停车漂移结果相同。该测试包含约 0.5 m 的短距离直行,结果标准差为 0,说明确定性测试在这组工况下没有观测到差异,并不代表已经覆盖长距离、复杂地面或其他物理参数。
因此,能够支持的结论只有:
在已经测试的配置中,两种碰撞体表现等价。
不能把它外推成“球形轮在其他场景更鲁棒”。物理建模中的很多经验判断,都应该接受这种实验约束。
inertial 决定它怎样响应力和约束
质量和惯量不会改变模型外观,却会影响加速、转向、振荡、倾倒和接触稳定性。随手填一个很小的正数,只能让解析器不报错,不能让物理成立。
所以第二项检查不是确认三个标签都存在,而是确认:
- visual 是否服务于表达;
- collision 是否服务于稳定、足够真实的接触;
- inertial 是否与质量和几何方向一致。
四、检查三:质量、惯量和重心是否合理
规则几何体的惯量可以直接计算。以长宽高分别为 a、b、c、质量为 m 的盒体为例:
Ixx = m(b² + c²) / 12
Iyy = m(a² + c²) / 12
Izz = m(a² + b²) / 12
案例机身为 0.60 × 0.30 × 0.35 m、质量 20 kg,对应:
Ixx ≈ 0.3542
Iyy ≈ 0.8042
Izz ≈ 0.7500
轮子更容易出错,因为惯量方向必须跟着几何轴一起旋转。半径 0.08 m、长度 0.03 m、质量 0.3 kg 的圆柱轮,轴沿 Y 时:
Iyy = 0.5mr² ≈ 0.000960 # 轴向
Ixx = Izz = m(3r²+h²)/12 ≈ 0.000503
如果几何已经从 Z 转到 Y,惯量仍按 Z 轴填写,模型就会在“看起来正确”的同时拥有错误的质量分布。
除了单个 Link,还要检查整机重心。案例采用左右驱动轮加前方支撑轮的三点支撑:
左轮 (-0.15, 0.25)
右轮 (-0.15, -0.25)
支撑轮 (0.20, 0.00)
静态审计计算出的整机重心投影约为 (-0.003, 0),位于支撑三角形内部,距离最近边约 0.118 m。
如果重心投影落在支撑区域之外,模型即使初始时被放正,也可能在仿真开始后自行倾倒。此时继续调 Nav2 参数没有意义。
五、检查四:几何轴、关节轴和驱动参数是否一致
回到开头的 90° Bug。
SDF 中圆柱默认沿 Z 轴延伸。我们希望轮子绕 Y 轴转动,因此需要绕 X 轴做 roll 旋转 90°:
圆柱默认轴 Z
→ roll 90°
→ 几何轴 Y
→ 与 joint axis (0,1,0) 平行
原配置使用 pitch 90°,把 Z 轴转到了 X,导致几何轴与关节轴相差 90°。回归报告记录到的可观测结果,是模型实体出现约 48° 的异常 roll,以及 Y、Z 方向的位置偏移。
一个合理解释是:物理引擎需要同时处理关节约束和错误的接触几何,约束冲突最终反映到了模型位姿上。但现有证据只验证了错误配置与异常现象的对应关系,没有单独证明“为什么恰好形成 48°”,因此这里应把物理机理视为推断,而不是已经完成的因果实验。
更隐蔽的是,这个错误继续沿系统传播:
轮轴配置错误
→ Gazebo 模型实体位姿异常
→ GPS 发布器读取错误世界位姿
→ 定位融合计算错误偏移
→ Nav2 获得错误位置
→ 机器人朝错误方向运动
这就是为什么排查导航问题时不能只看 Nav2 日志。
轮轴修正后,还要核对 DiffDrive 插件:
<left_joint>left_wheel_joint</left_joint>
<right_joint>right_wheel_joint</right_joint>
<wheel_separation>0.50</wheel_separation>
<wheel_radius>0.08</wheel_radius>
其中轮距必须等于左右轮中心的 Y 坐标差,轮半径必须与实际接触几何一致,关节名称必须指向真正的驱动关节。否则机器人可能可以直行,却在转向尺度、里程计或轨迹跟踪上持续产生系统误差。
六、检查五:接地点、摩擦和物理引擎是否匹配
一个稳定模型首先应该站在地面上,而不是悬空、穿透或让机身代替轮子接触。
案例中:
- 左右轮中心高度为 0.08 m,半径为 0.08 m,接地点恰好为
z=0; - 支撑球中心高度为 0.03 m,半径为 0.03 m,接地点同样为
z=0; - 机身碰撞体底部为
z=0.20 m,不会与地面争夺接触。
这三个数应该通过脚本检查,而不是凭 Gazebo 视图估计。画面中的透视、阴影和 visual 偏移很容易掩盖毫米级穿透或悬空。
摩擦参数也必须与实际物理引擎对应。某些 SDF 标签只对特定引擎有效;写入文件不代表当前使用的 DART 一定读取了它。最可靠的方法不是猜测,而是做受控实验:只改变一个摩擦参数,比较直行滑移、横向漂移、转向角和停车距离。
物理步长同样属于模型契约。一个在 0.002 秒步长下稳定的接触系统,不应未经验证就声称在更大步长或另一物理引擎中同样稳定。
七、检查六:世界、地图和控制闭环是否对齐
机器人自身正确,还不够。Nav2 使用的是二维占用地图,Gazebo 执行碰撞的是三维 World。如果两者不一致,就会出现两种典型错误:
- 地图显示可通行,但 Gazebo 中实际有墙;
- 地图显示障碍,但 Gazebo 中机器人面对的是空地。
案例的场景范围为 x、y ∈ [-15,15] m,地图大小为 600 × 600,分辨率 0.05 m/格,地图原点为 [-15,-15,0]。世界坐标到栅格坐标的基本转换是:
col = (x - origin_x) / resolution
row = height - 1 - (y - origin_y) / resolution
这里的行翻转很关键:世界坐标 Y 向上,而 PGM 图像行号从上向下。
更可靠的做法,是让 World 障碍物和地图生成脚本共享同一组几何定义,并在 RViz 中叠加检查墙体、设备和狭窄通道。路线点也必须用同一个 map 坐标系验证是否可达。
最后,模型必须通过控制量驱动,而不是在正式导航中直接修改位姿:
/cmd_vel
→ DiffDrive
→ 轮子与地面接触
→ 物理运动
→ 里程计、TF、IMU、GPS、LiDAR 更新
Teleport 可以用于某些项目的初始位置设置、重置或独立调试,但不能代替正式导航。本项目的正式运行链路不使用 Teleport,机器人只通过速度控制和 DiffDrive 产生运动。否则轮轴、摩擦、碰撞、加速度、停车和传感器反馈全部被绕过,一个错误模型也可能完成“完美演示”。
八、不要用“看起来没问题”验收模型
完成六项静态检查后,还需要按风险从低到高建立动态验收阶梯。
1. 文件与静态几何审计
解析 SDF,自动检查左右对称、轮轴夹角、惯量关系、关节位姿、接地点和重心投影。
当前模型的审计结果是 23 项通过、2 项被标记为 issue,脚本退出码为 1。两项 issue 分别来自左右轮:圆柱 visual 带有 roll 旋转,而球形 collision 没有位姿旋转。球体旋转后几何不变,因此这是“圆柱外观 + 球形碰撞体”设计产生的可解释差异;但当前审计工具还没有豁免或人工确认机制,仍会把它们计入失败。这说明审计结果既要如实报告,也需要结合设计意图解释,不能把红色输出直接改写成“全部通过”。
2. 静止测试
让机器人在无控制指令下运行 60 秒,检查:
- 模型是否倾倒或漂移;
- 姿态是否接近单位旋转;
- 里程计是否保持稳定;
- 传感器坐标是否随模型一致更新。
3. 直接控制测试
暂时不启动 Nav2,只发送可控的速度指令:
- 正线速度是否沿机体前方运动;
- 正角速度是否朝约定方向旋转;
- 左右轮是否对称;
- 停止发布后是否在门限内停车;
- 实际位移和转角是否与指令量级一致。
4. 传感器与真值一致性
比较 Gazebo 世界位姿、里程计、IMU 和 GPS。重点不是要求它们数值完全相同,而是确认坐标方向、原点、时间和数据来源符合设计,避免所谓“独立传感器”实际上复制了同一份错误数据。
5. 导航回归
最后再运行单点、五航点、绕障、狭窄通道和重复稳定性测试。只有低层物理检查通过后,导航失败才值得优先从地图、定位、规划和控制层继续排查。
这一顺序的价值,是让故障尽可能在最便宜、最接近根因的测试中暴露。
结语
Gazebo 模型能显示,只说明文件被解析并绘制出来;轮子能转,只说明某个关节或插件正在工作。它们都不能单独证明机器人拥有正确的几何、质量、约束、接触、坐标和控制闭环。
一个可用于导航验证的模型,至少要回答六个问题:
- 模型树与参考坐标是否清楚;
- visual、collision、inertial 是否各司其职;
- 质量、惯量、重心与支撑区域是否合理;
- 几何轴、关节轴和驱动参数是否一致;
- 接地点、摩擦和物理引擎是否匹配;
- Gazebo 世界、二维地图和控制闭环是否对齐。
电厂机器狗案例中,一个 90° 旋转错误最终影响了 GPS、定位和导航。主要根因不在 Nav2 参数,而在轮子几何;同一修复批次还调整了惯量方向、Link/Joint 位姿和定位配置。静态审计、直行、转向、停车和导航回归共同验证的是这批修复后的系统状态,而不是对某一行修改的隔离实验。
你在 Gazebo 建模时,最先遇到的是碰撞体、惯量,还是关节方向问题?
下一篇,我们将进入机器人系统最容易“看起来正常、实际上已经失效”的基础设施:仿真时间、消息时间戳和 TF 坐标树。
- 上一篇:机器人巡检仿真如何形成闭环:从任务到评测的完整数据流
- 下一篇:机器人为什么会“穿越时间”:ROS 2 仿真时钟与 TF 排错实战
DAMO开发者矩阵,由阿里巴巴达摩院和中国互联网协会联合发起,致力于探讨最前沿的技术趋势与应用成果,搭建高质量的交流与分享平台,推动技术创新与产业应用链接,围绕“人工智能与新型计算”构建开放共享的开发者生态。
更多推荐


所有评论(0)