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,都要回答三个问题:

  1. 位姿相对于谁?
  2. 原点放在哪里?
  3. 这个坐标是否又被父级变换了一次?

模型错误经常不是某个数字明显离谱,而是同一个偏移分别写在 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 模型能显示,只说明文件被解析并绘制出来;轮子能转,只说明某个关节或插件正在工作。它们都不能单独证明机器人拥有正确的几何、质量、约束、接触、坐标和控制闭环。

一个可用于导航验证的模型,至少要回答六个问题:

  1. 模型树与参考坐标是否清楚;
  2. visual、collision、inertial 是否各司其职;
  3. 质量、惯量、重心与支撑区域是否合理;
  4. 几何轴、关节轴和驱动参数是否一致;
  5. 接地点、摩擦和物理引擎是否匹配;
  6. Gazebo 世界、二维地图和控制闭环是否对齐。

电厂机器狗案例中,一个 90° 旋转错误最终影响了 GPS、定位和导航。主要根因不在 Nav2 参数,而在轮子几何;同一修复批次还调整了惯量方向、Link/Joint 位姿和定位配置。静态审计、直行、转向、停车和导航回归共同验证的是这批修复后的系统状态,而不是对某一行修改的隔离实验。

你在 Gazebo 建模时,最先遇到的是碰撞体、惯量,还是关节方向问题?

下一篇,我们将进入机器人系统最容易“看起来正常、实际上已经失效”的基础设施:仿真时间、消息时间戳和 TF 坐标树。

Logo

DAMO开发者矩阵,由阿里巴巴达摩院和中国互联网协会联合发起,致力于探讨最前沿的技术趋势与应用成果,搭建高质量的交流与分享平台,推动技术创新与产业应用链接,围绕“人工智能与新型计算”构建开放共享的开发者生态。

更多推荐