
洞穴建图是移动机器人进入矿洞、溶洞、人防工程或地下管廊时最先要解决的任务。洞穴环境 GPS 信号完全失效照明不均匀地面可能是碎石、坡道和积水普通的走廊 SLAM 方案如果直接迁移过来很容易出现里程计漂移、激光退化、地图出现重影。本文围绕“洞穴建图 | ROS1 | ROS2”这条主线用一套可以放到 Gazebo 中运行的模拟洞穴环境分别用 ROS1 Noetic 和 ROS2 Humble 跑通建图流程并说明真实洞穴中传感器、驱动、TF 和日志回放要如何处理。读完后你可以独立完成从环境安装、模拟洞穴搭建、SLAM 运行到地图保存的完整流程也能在碰到激光数据中断、地图跳变和 ROS1/ROS2 环境串扰时找到排查方向。1. 洞穴建图为什么不能照搬普通室内 SLAM1.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_scanSLAM 节点会维护map - odom。任何一级 TF 缺失或跳变都会导致 Cartographer 无法工作甚至直接崩溃。所以后面的仿真验证会先从话题和 TF 的检查开始再进入建图环节。这个顺序也适用于真实洞穴机器人。2. 环境准备ROS1 Noetic 与 ROS2 Humble 并存2.1 系统版本与发行版选型ROS1 和 ROS2 的系统绑定关系很明确Ubuntu 20.04 对应 ROS1 NoeticUbuntu 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 NoeticROS1 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 HumbleROS2 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 rbsource /opt/ros/noetic/setup.bash alias rhsource /opt/ros/humble/setup.bash新终端默认可以什么都不 source需要 ROS1 时输入rb需要 ROS2 时输入rh。这样能避免 ROS1 节点跑到 ROS2 环境变量下的问题。也可以用两条独立命令检查当前环境echo $ROS_DISTRO which roscore which ros2ROS1 环境应能看到/opt/ros/noetic/bin/roscoreROS2 环境应能看到/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 version1.0? sdf version1.6 world namecave_demo include urimodel://sun/uri /include include urimodel://ground_plane/uri /include model namewall_1 statictrue/static pose0 1.0 0.5 0/pose link namelink collision namecollision geometryboxsize6 0.3 1.0/size/box/geometry /collision visual namevisual geometryboxsize6 0.3 1.0/size/box/geometry /visual /link /model model namerock_1 statictrue/static pose2.5 -0.6 0.4 0.3/pose link namelink collision namecollision geometryboxsize1.2 1.0 0.8/size/box/geometry /collision visual namevisual geometryboxsize1.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 nameworld_name default$(find my_cave_bringup)/worlds/cave.world/ arg namemodel defaultburger/ arg namex default0.0/ arg namey default0.0/ arg namez default0.02/ include file$(find gazebo_ros)/launch/empty_world.launch arg nameworld_name value$(arg world_name)/ arg namepaused valuefalse