洞穴建图是移动机器人进入矿洞、溶洞、人防工程或地下管廊时最先要解决的任务。洞穴环境 GPS 信号完全失效,照明不均匀,地面可能是碎石、坡道和积水,普通的走廊 SLAM 方案如果直接迁移过来,很容易出现里程计漂移、激光退化、地图出现重影。本文围绕“洞穴建图 | ROS1 | ROS2”这条主线,用一套可以放到 Gazebo 中运行的模拟洞穴环境,分别用 ROS1 Noetic 和 ROS2 Humble 跑通建图流程,并说明真实洞穴中传感器、驱动、TF 和日志回放要如何处理。读完后,你可以独立完成从环境安装、模拟洞穴搭建、SLAM 运行到地图保存的完整流程,也能在碰到激光数据中断、地图跳变和 ROS1/ROS2 环境串扰时找到排查方向。
1. 洞穴建图为什么不能照搬普通室内 SLAM
1.1 GPS 拒止、里程计退化和地图回环
室内 SLAM 最常用的前提是:机器人有稳定的轮式里程计,激光雷达能扫到规则墙面,环境中有足够多的几何特征用于闭环。洞穴环境把这三个前提同时削弱了。
第一是 GPS 拒止。洞穴内部没有卫星信号,无法用 GPS 修正全局位置,所有定位都依赖里程计、激光匹配和 IMU。这要求前端里程计的质量足够高,否则地图会一路歪下去。
第二是里程计退化。碎石、沙土、坡道和积水会让轮子打滑;即使轮子不打滑,松软地面也会让左右轮实际前进距离与编码器计算结果不一致。差速机器人在这种环境下,odom -> base_link的变换很快就会出现误差,Cartographer 等 SLAM 算法只能靠激光扫描匹配来拉回姿态。
第三是回环条件苛刻。洞穴通道通常窄、长、弯道多,激光的探测范围会被墙壁限制,回环往往要走到同一位置才能识别。如果途中累积漂移太大,回环检测会把本来的闭环判断成错误匹配,地图会出现错位或重影。
所以洞穴建图的第一步,不是急着调 SLAM 参数,而是先确认“里程计、激光、TF”这三个基础数据在实验环境里是干净且稳定的。
1.2 洞穴传感器选型约束
洞穴对传感器的约束比普通室内强得多。
激光雷达方面,2D 激光在平整矿洞或人工巷道中可用,但遇到坡度、碎石堆和凹凸墙面的效果会变差。3D 激光能提供更多几何约束,但成本和功耗更高。视觉方面,洞穴低光照会让普通相机失效,除非主动补光;热成像和结构光又会引入新的标定问题。
粉尘和湿度也不能忽略。很多洞穴环境中粉尘较大,激光雷达的窗口容易附着灰尘,导致测距出现不稳定值。雷达的扫描数据中会出现大量无穷远点或短距噪点,SLAM 前需要做裁剪和滤波。
因此,真实洞穴建图推荐按以下方式选型:
- 入门验证:单线激光雷达 + 轮式里程计。
- 工程落地:单线或 3D 激光 + IMU + 轮式里程计。
- 特殊场景:增加深度相机、补光灯或热成像辅助语义识别。
选型不必一步到位。先用低成本传感器跑通 SLAM 链路,再逐步增加 IMU 和 3D 点云,能减少很多不必要的调试成本。
1.3 洞穴建图在 ROS 中的通用数据链路
无论用 ROS1 还是 ROS2,洞穴建图最后都会落到同一条数据链路上:
传感器 -> 话题 /scan -> SLAM 节点 -> /map 话题 -> 地图文件 ↑ TF: map -> odom -> base_link -> laserSLAM 节点要做两件事:一是根据连续帧激光扫描估计机器人位姿,二是把位姿和扫描结果融合成占据栅格地图。/scan是输入,/map是输出,TF 是中间的骨架。
这条链路中,最容易出问题的是 TF。机器人底盘驱动会发布odom -> base_link,机器人描述文件会发布base_link -> base_scan,SLAM 节点会维护map -> odom。任何一级 TF 缺失或跳变,都会导致 Cartographer 无法工作,甚至直接崩溃。
所以后面的仿真验证,会先从话题和 TF 的检查开始,再进入建图环节。这个顺序也适用于真实洞穴机器人。
2. 环境准备:ROS1 Noetic 与 ROS2 Humble 并存
2.1 系统版本与发行版选型
ROS1 和 ROS2 的系统绑定关系很明确:Ubuntu 20.04 对应 ROS1 Noetic,Ubuntu 22.04 对应 ROS2 Humble。要在一台机器上同时做实验,有两个常见方案。
第一个方案是只装一个系统,选一个主发行版。如果团队遗留代码是 ROS1,就先装 Noetic;如果新项目是从零开始,优先选 ROS2 Humble。第二个方案是一台 Ubuntu 20.04 上同时安装 Noetic 和 ROS2(比如 Foxy 或通过容器方式),但要注意环境变量串扰。
本文以 Ubuntu 20.04 + ROS1 Noetic、Ubuntu 22.04 + ROS2 Humble 为例。实际环境版本不完全一致时,命令中的noetic和humble需要对应替换。
注意:不要在同一终端里同时 source
/opt/ros/noetic/setup.bash和/opt/ros/humble/setup.bash,否则ROS_DISTRO、CMAKE_PREFIX_PATH等变量会互相覆盖,后续编译和运行会出现非常难查的问题。
2.2 用国内源安装 ROS1 Noetic
ROS1 Noetic 可以直接从 ROS 官方源安装,但国内网络环境下经常很慢。推荐把 ROS 源切换为国内镜像源。
Ubuntu 20.04 上可以这样配置清华 ROS 镜像源:
sudo sh -c 'echo "deb https://mirrors.tuna.tsinghua.edu.cn/ros/ubuntu focal main" > /etc/apt/sources.list.d/ros-noetic.list' sudo apt update然后安装完整桌面版:
sudo apt install -y ros-noetic-desktop-fullros-noetic-desktop-full已经包含 Gazebo、RViz、机器人 TF 等常用工具,适合仿真教学。如果空间不足,可以改成ros-noetic-desktop,但后面部分依赖需要单独安装。
安装完成后配置环境:
echo "source /opt/ros/noetic/setup.bash" >> ~/.bashrc source ~/.bashrc验证是否生效:
rosversion -d输出应为noetic。
如果 apt 源本身就很快,也可以直接使用http://packages.ros.org/ros/ubuntu官方源,不需要改镜像。关键是确保 apt 源可用,不要因为换源导致 GPG key 或源地址格式错误。
2.3 用脚本或二进制方式安装 ROS2 Humble
ROS2 Humble 的常规安装方式也是先添加源,再安装ros-humble-desktop。
Ubuntu 22.04 上使用清华镜像源:
sudo sh -c 'echo "deb https://mirrors.tuna.tsinghua.edu.cn/ros2/ubuntu jammy main" > /etc/apt/sources.list.d/ros2.list' sudo apt update sudo apt install -y ros-humble-desktop python3-colcon-common-extensions安装完成后:
echo "source /opt/ros/humble/setup.bash" >> ~/.bashrc source ~/.bashrc如果不想手动配置源和依赖,也可以使用社区维护的一键安装脚本。例如“鱼香ROS”脚本会提供 ROS1/ROS2 安装、镜像源选择和常见工具安装,适合快速搭实验环境。使用这类脚本前,要确认脚本来源和当前系统版本,不要盲目执行来自不可信页面的命令。
验证 ROS2 环境:
ros2 --help能看到ros2命令说明安装成功。
2.4 验证 ROS1/ROS2 环境切换
如果只装一个发行版,环境变量比较简单。如果两个发行版都存在,建议不要在.bashrc中同时写入两条 source,而是用别名手动切换。
例如在~/.bashrc中写:
alias rb='source /opt/ros/noetic/setup.bash' alias rh='source /opt/ros/humble/setup.bash'新终端默认可以什么都不 source,需要 ROS1 时输入rb,需要 ROS2 时输入rh。这样能避免 ROS1 节点跑到 ROS2 环境变量下的问题。
也可以用两条独立命令检查当前环境:
echo $ROS_DISTRO which roscore which ros2ROS1 环境应能看到/opt/ros/noetic/bin/roscore,ROS2 环境应能看到/opt/ros/humble/bin/ros2。如果roscore和ros2同时存在,说明两个环境变量发生了串扰,要重新检查.bashrc。
3. 搭建模拟洞穴环境:用 Gazebo 替代真实洞穴
3.1 为什么先在仿真里做
真实洞穴测试成本高,机器人可能被困、跌落或损坏传感器。仿真环境下可以先用墙壁、石块和弯道构造一个近似洞穴的封闭环境,验证 SLAM 链路是否能跑通。
本文的仿真目标不是还原真实洞穴,而是制造三个关键条件:
- 环境没有 GPS,只有激光和里程计。
- 通道宽度窄,机器人转弯空间有限。
- 环境中存在不规则障碍,激光数据不是简单的一圈墙。
用 Gazebo 可以满足这些条件,而且 Gazebo 自带传感器噪声模型,能帮助发现代码中的话题和 TF 问题。
3.2 创建洞穴世界文件
在 ROS1 的 catkin 工作空间中,先创建一个功能包用于存放自定义世界文件和 launch 文件。
工作空间结构建议如下:
my_cave_bringup/ ├── CMakeLists.txt ├── package.xml ├── launch/ │ ├── cave_gazebo.launch │ └── cartographer.launch ├── worlds/ │ └── cave.world └── config/ └── cave_cartographer.luacave.world是一个简化版 SDF 世界文件,用几个长条石块模拟洞穴中的墙壁和障碍:
<?xml version="1.0"?> <sdf version="1.6"> <world name="cave_demo"> <include> <uri>model://sun</uri> </include> <include> <uri>model://ground_plane</uri> </include> <model name="wall_1"> <static>true</static> <pose>0 1.0 0.5 0</pose> <link name="link"> <collision name="collision"> <geometry><box><size>6 0.3 1.0</size></box></geometry> </collision> <visual name="visual"> <geometry><box><size>6 0.3 1.0</size></box></geometry> </visual> </link> </model> <model name="rock_1"> <static>true</static> <pose>2.5 -0.6 0.4 0.3</pose> <link name="link"> <collision name="collision"> <geometry><box><size>1.2 1.0 0.8</size></box></geometry> </collision> <visual name="visual"> <geometry><box><size>1.2 1.0 0.8</size></box></geometry> </visual> </link> </model> </world> </sdf>这个文件只是示例。真实洞穴环境的创建可以用更精细的 mesh 或 heightmap,但先用简单几何体验证 SLAM 链路足够了。
要注意 SDF 中的model://sun和model://ground_plane是 Gazebo 内置模型。如果启动时提示找不到模型,需要先确认 Gazebo 模型数据库路径是否正确。
3.3 加载 TurtleBot3 机器人并发布话题
直接写一套完整差速机器人 URDF 比较长,这里使用 TurtleBot3 作为载具。它自带 Gazebo 差速控制插件和激光雷达插件,可以稳定发布/scan、/odom和 TF。
写一个cave_gazebo.launch,用自定义世界替换 TurtleBot3 默认世界:
<launch> <arg name="world_name" default="$(find my_cave_bringup)/worlds/cave.world"/> <arg name="model" default="burger"/> <arg name="x" default="0.0"/> <arg name="y" default="0.0"/> <arg name="z" default="0.02"/> <include file="$(find gazebo_ros)/launch/empty_world.launch"> <arg name="world_name" value="$(arg world_name)"/> <arg name="paused" value="false