欢迎光临
我们一直在努力

我把 Gazebo 和实机打通了:一套 ROS 2 系统,仿真跑完直接上车

项目已开源至Github,欢迎Star

GitHub – Ikunio/Lidar_nav2_ws: 基于 Livox MID-360 3D LiDAR 的 ROS 2 自主导航工作空间,集成 LIO 里程计、重定位、Nav2 导航,支持仿真与实机部署。 · GitHub

最近开源了一个基于 ROS 2 的 3D LiDAR 自主导航项目:Lidar_nav2_ws。

这个项目的核心并不是“我又调通了一套 Nav2”,而是我想解决一个更工程化的问题:

能不能让 Gazebo 仿真和真实机器人共用同一套导航系统?

很多机器人项目在前期开发时都会先做仿真。Gazebo 里能跑,RViz 里能看,Nav2 Goal 一点,机器人也能规划路径,看起来很美好。

但是一上实机,问题就来了:

仿真一套 launch,实机一套 launch; 仿真一套 URDF,实机一套 URDF; 仿真一个话题名,实机一个话题名; 仿真能跑的 TF 树,实机直接断成几截; 最后代码越改越乱,系统越接越像“祖传插线板”。

所以这个项目主要想做的事情,就是把仿真和实机之间的差异尽量收敛,让它们共用同一套 LIO、重定位、Nav2 和 TF 结构。

简单说就是:

Gazebo 负责模拟传感器和机器人,实机负责提供真实传感器数据;但进入导航系统之后,二者尽量长得一样。


1. 项目整体做了什么?

Lidar_nav2_ws 是一个基于 ROS 2 Humble 的 3D LiDAR 自主导航工作空间,主要面向四轮滑移转向机器人。

系统以 Livox MID-360 这类 3D LiDAR 和 IMU 为核心传感器,整体链路大概是:

LiDAR / IMU

LIO 里程计

标准 odom / TF / registered_scan

3D 点云重定位

3D 点云切片成 2D LaserScan

Nav2 导航

也就是说,这套系统不是直接把 Nav2 单独拉起来,而是把前端里程计、点云地图、重定位、TF 桥接、2D 代价地图输入全部串了起来。

其中比较关键的部分包括:

FAST-LIO2 / Point-LIO → 提供 LiDAR-Inertial Odometry
lio_interface → 把 LIO 输出转成标准 odom 坐标系
sensor_scan_generation → 发布 /odom、/registered_scan 和 odom→base_footprint
small_gicp_relocalization → 基于先验 PCD 的 3D 点云重定位
KISS-Matcher → 无初值全局粗配准
pointcloud_to_laserscan → 3D 点云切片成 2D /scan
Nav2 → 路径规划与局部避障

这套链路的好处是很明显的:

仿真和实机只要能提供一致的数据入口,后面的系统就不用大改。


2. 仿真和实机最容易乱在哪里?

仿真切实机,最容易炸的地方一般有四个:

第一,传感器来源不同

Gazebo 里没有真实 Livox 雷达,所以需要用 Gazebo 插件模拟 LiDAR 数据。

实机上则要使用真实的 Livox MID-360 驱动,例如 livox_ros_driver2,从真实硬件读取点云和 IMU。

也就是说:

仿真:Gazebo LiDAR 插件
实机:Livox MID-360 硬件驱动

传感器来源不同,但系统后面希望看到的东西应该一致,比如:

/livox/lidar
/livox/imu
/cloud_registered
/registered_scan
/odom
/scan
/tf

只要后端订阅的话题和 TF 结构不乱,仿真和实机就不会变成两套完全不同的工程。


第二,机器人模型不同

仿真环境需要完整的 Gazebo 模型,包括碰撞体、惯性参数、轮子、雷达插件、仿真世界等。

实机则更关注真实机器人描述,比如底盘尺寸、雷达安装位置、相机安装位置、base_link、base_footprint、livox_frame 等静态 TF。

所以项目里做了区分:

仿真:get_urdf
实机:gld_robot_description

这其实是合理的。

因为 Gazebo 的 URDF/SDF 往往需要服务于物理仿真,而实机 URDF 更需要服务于 TF 关系、传感器外参和导航坐标系。

但注意,虽然模型文件不同,最终要输出给导航系统的坐标树应该尽量一致。

例如:

map
└── odom
└── base_footprint
└── chassis
└── livox_frame

这个 TF 树就是仿真和实机共用系统的基础。

TF 一旦统一,Nav2、重定位、点云切片、RViz 可视化才不会到处适配。


第三,时间源不同

Gazebo 仿真通常会用仿真时间:

use_sim_time = true

实机一般用系统时间:

use_sim_time = false

这个地方非常容易出问题。

如果某些节点用了仿真时间,某些节点用了系统时间,TF 查询就可能出现 extrapolation error,表现出来就是:

TF 查不到
点云不显示
Nav2 costmap 空白
机器人位姿跳变

所以在仿真和实机自由切换时,时间源一定要明确。

在这个项目里,仿真模式主要让 LIO 管线使用仿真时间,而导航部分保持稳定配置。这样可以减少仿真和实机之间的参数漂移。


第四,启动流程不同

如果每次切换仿真和实机都要手动开十几个终端,那这个系统基本没有工程可维护性。

所以项目把常用流程脚本化:

./mapping_sim.sh
./nav2_sim.sh
./mapping_real.sh
./nav2_real.sh

含义也很直接:

mapping_sim.sh → Gazebo 仿真建图
nav2_sim.sh → Gazebo 仿真导航
mapping_real.sh → 实机建图
nav2_real.sh → 实机导航

这其实是我比较喜欢的设计。

因为它把“开发阶段”和“部署阶段”的入口统一了。

你不需要记住一堆 launch 文件,也不需要每次都手动 source、手动启动、手动改命令。对于比赛机器人或者实验室项目来说,这种脚本化入口非常重要。


3. 这套系统是怎么做到共用的?

核心思路可以概括成一句话:

只隔离硬件差异,不复制导航系统。

也就是说,仿真和实机确实有不同的地方,但不同的地方应该集中在最底层。

比如:

层级仿真实机是否共用
传感器来源 Gazebo 插件 Livox 驱动 不完全共用
URDF 仿真模型 实机模型 不完全共用
LIO FAST-LIO / Point-LIO FAST-LIO / Point-LIO 尽量共用
里程计桥接 lio_interface lio_interface 共用
点云输出 /registered_scan /registered_scan 共用
重定位 small_gicp / KISS-Matcher small_gicp / KISS-Matcher 共用
Nav2 DWB + Navfn DWB + Navfn 共用
TF 主结构 map→odom→base_footprint map→odom→base_footprint 共用

这才是重点。

仿真和实机不是完全一样,也不应该强行一样。 真正应该统一的是系统抽象之后的接口。

比如不管你底层是 Gazebo 雷达,还是 Livox 真雷达,进入 LIO 和导航系统之后,都应该尽量变成标准 ROS 2 数据流:

PointCloud2
Imu
Odometry
TF
LaserScan

这样后面的模块就不用关心“我现在是在仿真还是在实机”。

这就是 ROS 系统里非常重要的工程思想:

节点之间不要互相知道太多,靠标准话题和 TF 解耦。


4. 为什么我更关注 Gazebo 和实机的一致性?

因为仿真不是为了截图好看,而是为了降低实机调试成本。

真正做机器人实机的人都知道,实机调试是有代价的。

轻则机器人撞墙,重则雷达、相机、底盘一起开席。

所以一个比较靠谱的流程应该是:

先在 Gazebo 验证 TF
再在 Gazebo 验证 Nav2 参数
再在 Gazebo 验证建图流程
再在 Gazebo 验证重定位流程
最后再切到实机

如果仿真和实机是两套完全不同的系统,那仿真验证的价值会大幅下降。

因为你在 Gazebo 里调好的参数,上实机可能完全不适用; 你在 Gazebo 里跑通的 TF 树,上实机可能换了一套名字; 你在 Gazebo 里验证的重定位流程,上实机可能话题都对不上。

所以我做这个项目时,尽量让仿真和实机共用同一套系统:

同一套 LIO 接口
同一套 /registered_scan
同一套 map→odom→base_footprint
同一套 Nav2 参数结构
同一套重定位接口
同一套保存地图和加载地图流程

这样 Gazebo 不只是“玩具环境”,而是一个真正能服务实机部署的前置验证平台。


5. 仿真建图和实机建图

在仿真建图阶段,可以直接启动:

source install/setup.bash
./mapping_sim.sh

这个流程会启动 Gazebo、机器人模型、LIO、SLAM Toolbox、Nav2 相关节点和遥控工具。

在 Gazebo 里控制机器人运动,遍历环境后,可以保存两类地图:

./save_map.sh
./save_pcd.sh

其中:

2D map → 给 Nav2 使用
3D PCD → 给点云重定位使用

这点很关键。

因为 Nav2 主要跑在 2D costmap 上,但 3D LiDAR 重定位需要 PCD 先验地图。

所以系统里同时维护了:

2D 栅格地图
3D 点云地图

这也是 3D LiDAR + Nav2 系统里比较常见的组合方式:

3D LiDAR 负责定位和感知
2D costmap 负责导航规划

实机建图也类似:

source install/setup.bash
./mapping_real.sh

只不过这时 Gazebo 被替换为真实 Livox 驱动和实机 URDF。

从系统设计角度看,这种切换方式比较干净:

仿真建图:Gazebo + 仿真 URDF + 仿真 LiDAR
实机建图:Livox 驱动 + 实机 URDF + 真实 LiDAR

但建图之后输出的东西尽量一致:

map.yaml
map.pgm
prior.pcd

这就为后面的导航和重定位复用打下基础。


6. 仿真导航和实机导航

仿真导航入口:

source install/setup.bash
./nav2_sim.sh

实机导航入口:

source install/setup.bash
./nav2_real.sh

这两个脚本的意义不是“简单启动两个不同程序”,而是把系统分成了两部分:

底层环境层:仿真 / 实机不同
上层导航层:尽量一致

上层导航层主要包括:

LIO
/registered_scan
small_gicp_relocalization
pointcloud_to_laserscan
Nav2
RViz
TF

在 RViz 中发送 Nav2 Goal,系统会基于当前定位结果进行路径规划和局部避障。

其中定位链路大致是:

LIO 提供局部 odom
3D 重定位发布 map→odom
Nav2 使用 map 下的机器人位姿进行导航

这里最关键的是 map→odom。

LIO 本身通常只能提供局部连续里程计,也就是 odom→base_footprint。 但是机器人要在全局地图里导航,就必须知道 map→odom。

所以重定位节点的作用就是:

根据当前局部点云和先验 PCD 地图配准
估计机器人在 map 下的位置
持续修正 map→odom

这也是为什么系统里集成了 small_gicp 和 KISS-Matcher。


7. 重定位:让机器人不是只能“从原点开机”

传统实机导航经常有一个麻烦:

机器人最好从固定位置启动。

如果每次开机位置都不一样,而系统又不知道机器人当前在地图哪里,那 Nav2 再强也没用。

因为路径规划的前提是:

机器人知道自己在地图中的位置

这个项目里做了几种重定位方式。

第一种是纯 small_gicp,适合初始位姿大概已知的情况。 比如机器人就在地图原点附近,或者可以在 RViz 里用 2D Pose Estimate 给一个大概位置。

第二种是 KISS-Matcher + small_gicp,更适合无初值或初值很差的情况。

它的思路是:

先用 KISS-Matcher 做全局粗配准
得到一个大概位姿
再交给 small_gicp 做连续精配准
最后持续发布 map→odom

这套流程对实机更友好。

因为真实机器人不可能每次都老老实实放在同一个起点。 如果每次都要人工给初值,系统就不够自动化。 如果能通过当前点云和先验地图自动完成全局初始化,那么机器人就更接近“任意位置开机重定位”。

当然,全局重定位不是玄学。

它需要当前扫描和先验地图有足够重叠。 如果机器人刚启动时只看到一小块墙面,或者环境几何特征太少,初始化失败是正常的。

所以实机使用时,可以让机器人原地缓慢旋转,或者短距离移动,让 /registered_scan 累计到更完整的局部结构。

说白了就是:

你得让机器人多看几眼,它才知道自己在哪。


8. 这套设计最大的价值

我认为这个项目最大的价值,不是某一个算法有多花,而是系统结构比较工程化。

它把机器人导航系统拆成了几层:

传感器层
LIO 里程计层
TF / 话题桥接层
重定位层
Nav2 导航层
仿真 / 实机启动层

每一层只干自己的事情。

比如:

Livox 驱动只负责发点云和 IMU
FAST-LIO 只负责估计局部里程计
lio_interface 只负责坐标和话题转换
sensor_scan_generation 只负责发布标准 odom 和 registered_scan
重定位节点只负责发布 map→odom
Nav2 只负责规划和控制

这种设计的好处是,后面想换东西会更容易。

比如:

FAST-LIO 换成 Point-LIO
small_gicp 换成 ICP / NDT / 其他配准方法
Gazebo 仿真换成真实机器人
仿真 URDF 换成实机 URDF

只要输入输出接口保持一致,系统就不用推倒重来。

这就是我想做这个项目的原因:

不是为了把所有东西硬塞进一个 launch 文件,而是让不同模块可以自由组合、替换和复用。


9. 我对这套系统的理解

如果用一句话总结这个项目:

它是一套面向实机部署的 ROS 2 3D LiDAR 导航工作空间,而不是一个只能在 RViz 里演示的 demo。

Gazebo 在这里不是最终目的,而是实机部署前的验证平台。

仿真负责提前暴露问题:

TF 对不对
话题通不通
Nav2 参数合不合理
点云切片有没有数据
重定位能不能收敛
map→odom 有没有冲突

实机负责验证真实环境下的鲁棒性:

LiDAR 数据是否稳定
IMU 是否正常
外参是否正确
地图是否可复用
重定位是否可靠
导航是否能跑完整流程

最终目标是让两边尽量共用一套系统,而不是维护两套越来越分裂的工程。

对我来说,这才是机器人项目里最有价值的部分。

算法很重要,但系统工程同样重要。 一个算法 demo 跑起来不难,难的是让它能在仿真、实机、建图、导航、重定位这些场景里稳定复用。

Lidar_nav2_ws 目前就是朝这个方向做的:

用一套 ROS 2 系统,把 Gazebo 和真实机器人连接起来。

赞(0)
未经允许不得转载:171主机测评 » 我把 Gazebo 和实机打通了:一套 ROS 2 系统,仿真跑完直接上车
分享到: 更多 (0)

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址