拓冰建站拓冰建站
首页 / 资讯中心 / 正文

洞穴建图实战:ROS1与ROS2下的传感器配置与Cartographer建图

如果你在露天园区用 2D 激光雷达建一张地图一般十几分钟就能出一张能用的栅格图可一旦把同样一套 ROS 建图流程搬到洞穴、地下矿道或隧道里情况会很快失控——地图漂移、回环闭合失败、点云撕裂甚至跑着跑着系统直接报“找不到 TF”。洞穴建图的难点从来不在 SLAM 算法本身而在传感器数据是否可信、时间戳是否对齐、坐标系是否完整。我一直在整理“洞穴建图”这个课题也反复对比过 ROS1 和 ROS2 两套环境。我的判断很明确在洞穴这种 GPS 失效、纹理重复、粉尘多、光线差的环境中传感器工程比代码调试更关键。算法能解决 90% 的问题剩下 10% 是让传感器在任何环境下都不“说谎”。这也决定了 ROS1 和 ROS2 在洞穴建图项目中真正的取舍点不是“哪套更新”而是“哪套能更快把传感器数据变成可靠的地图约束”。这篇文章以“洞穴建图 | ROS1 | ROS2”为主题会从环境准备讲起覆盖传感器配置、TF 坐标系、时间同步、Cartographer 建图流程、3D 激光惯性方案以及洞穴场景下最常见的漂移和排查思路。读完之后你可以直接在 ROS1 或 ROS2 环境中跑通一套最小洞穴建图流程并知道下一步该怎么扩展。1. 洞穴建图为什么容易失败问题本质不是算法1.1 洞穴环境对建图系统提出了哪些苛刻要求先看一张典型的洞穴建图数据流激光雷达或深度相机采集帧IMU 提供角速度和加速度轮式或履带里程计给出局部位移SLAM 节点把这些数据统一优化成一张地图。听起来和室内建图没有区别但洞穴环境把每个环节都推到了极限。第一个问题是 GPS 不可用。Robot Operating System 里很多里程计和定位框架都假设存在一个“绝对参考”洞穴里没有。没有 GPS系统只能依赖局部坐标系也就是odom和map之间的位姿由 SLAM 自己维护。一旦漂移积累odom到map之间就会出现明显偏差。第二个问题是隧道走廊特征稀疏。洞穴通常是一段段狭长通道激光扫过去大量点云集中在前进方向横向信息很少。这样的环境里回环检测非常容易失效因为不同位置的扫描看起来几乎一样。没有回环约束前方的位姿就会沿着隧道方向慢慢漂移等走出洞口再回头地图已经歪了。第三个问题是传感器物理条件恶劣。洞穴里光照不足视觉相机很容易拍出过曝或全黑图像粉尘和潮湿会让激光雷达点云产生噪点地面不平会导致履带或轮式里程计打滑。这些因素叠加起来建图系统会频繁出现“数据不可用”的瞬间。1.2 什么样的开发者适合读这篇文章如果你正在做以下事情这篇文章会很有参考价值课程项目或毕业设计选择了“洞穴建图”“地下空间建图”“矿道仿真”等方向想了解 ROS1 和 ROS2 在真实 SLAM 任务中的差异尤其是 Cartographer 的配置和运行方式需要在一个没有 GPS 的环境中做移动机器人自主导航先要把环境地图建出来已经会用 ROS 建室内图但换到地下或隧道场景后地图一直漂移想找到排查方法。这篇文章不会停留在“启动一个 SLAM 节点”的层面而是会重点讲清楚在洞穴这种退化环境下怎样的传感器组合和参数调整才能让地图保持稳定。2. 洞穴建图的基础概念与框架选型2.1 SLAM、里程计、建图三个概念要拆开很多初学者把 SLAM 当成一个“黑盒”模块但洞穴建图项目里必须把它拆开理解。里程计只关心短时间内的相对位移。车轮、IMU、激光扫描匹配都能提供里程计信息但它只解决“我相对上一时刻移动了多少”不解决“我在哪里”。建图把每一帧传感器数据放到同一个全局坐标系下拼成一张可用的地图。SLAM在不确定的位姿估计下同时完成建图和定位核心是维护历史位姿之间的约束尤其是回环约束。洞穴建图对三个环节的要求分别是里程计要稳定建图要能处理稀疏点云SLAM 要能在缺少回环时尽可能抑制漂移。2.2 为什么激光雷达 IMU 是洞穴建图的主流组合视觉方案在洞穴里非常吃亏。低光照、粉尘、纹理重复都会让视觉特征匹配失效。相比之下激光雷达不依赖光照能直接提供几何信息IMU 不受打滑和粉尘影响可以提供高频的姿态和加速度。两者组合能形成很好的互补激光负责绝对几何约束IMU 负责短时间剧烈运动补偿。在 ROS1 / ROS2 中最常见的洞穴建图框架包括框架ROS1 支持ROS2 支持适用场景Cartographer官方支持官方支持2D 隧道、小规模洞穴、走廊环境LIO-SAM官方仓库主要面向 ROS1以社区移植为主3D 大场景、需要紧耦合激光惯性FAST-LIOROS1 生态更完整有社区分支3D 大场景、强调计算实时性BLAMROS1较少轻量级 3D 激光建图实验这里需要说明以上框架的版本和 ROS 分支会随社区更新变化安装前一定要查看对应仓库的 README 和分支说明。从整体维护情况看ROS1 生态对这些开源 SLAM 的支持更完整ROS2 主要依赖 Cartographer 和部分社区移植版本。这也是“洞穴建图”这类偏研究型项目仍然大量使用 ROS1 的原因之一。2.3 ROS1 与 ROS2 在洞穴建图中的取舍ROS1 的优势是资料多、老驱动多、第三方 SLAM 包多。很多工业激光雷达、旧的底盘驱动、科研用的建图算法都只提供了 ROS1 接口。ROS2 的优势是安全性、实时性、多机通信和生命周期管理更好适合产品化。洞穴建图项目如果没有历史代码包袱推荐从 ROS2 Humble 开始如果一定要用 LIO-SAM 这类激光惯性方案可以先用 ROS1 Noetic 跑通数据流再考虑移植到 ROS2。不要一上来就双线并行否则会在环境问题上消耗大量时间。3. 环境准备ROS1 与 ROS2 的安装与配置3.1 版本建议与操作系统匹配洞穴建图项目和普通 ROS 项目一样首先要确定操作系统和 ROS 发行版。最常用的组合是操作系统ROS 发行版说明Ubuntu 20.04ROS NoeticROS1老项目多、LIO-SAM 等算法更好跑Ubuntu 22.04ROS2 HumbleROS2新项目推荐、支持 ARM 设备Ubuntu 24.04ROS2 Jazzy新版本迭代中需确认第三方包兼容性树莓派等 ARMUbuntu Server ROS2 Humble适合小型洞穴建图平台版本细节请以实际项目为准本文重点演示通用思路。开发环境推荐 Ubuntu 22.04 ROS2 Humble同时用 Docker 或双系统保留一个 Ubuntu 20.04 ROS1 Noetic 环境做兼容测试。3.2 配置国内 ROS 镜像源国内用户安装 ROS 时最常遇到的问题是从官方源下载包特别慢甚至中途超时。解决方式是先把 ROS 源换成国内镜像。这里以清华镜像为例# ROS1 Noetic 源Ubuntu 20.04 / focal sudo sh -c echo deb http://mirrors.tuna.tsinghua.edu.cn/ros/ubuntu focal main /etc/apt/sources.list.d/ros1-mirror.list # ROS2 Humble 源Ubuntu 22.04 / jammy echo deb [arch$(dpkg --print-architecture)] http://mirrors.tuna.tsinghua.edu.cn/ros2/ubuntu jammy main | sudo tee /etc/apt/sources.list.d/ros2-mirror.list换源后需要重新导入 ROS 的 GPG key不同发行版导入方式有差异。还有一种更省事的方式是用社区维护的一键安装脚本。这类脚本本质上就是在替你执行“换源、安装、初始化 rosdep”这几步但在安装前至少要把脚本里涉及的源地址和分支读一遍避免装到不合适的版本。3.3 安装 ROS1 Noetic在 Ubuntu 20.04 上安装 ROS1 Noetic核心命令如下# 添加 ROS1 源 sudo sh -c echo deb http://packages.ros.org/ros/ubuntu $(lsb_release -sc) main /etc/apt/sources.list.d/ros1-latest.list # 导入 key sudo apt install curl curl -s https://raw.githubusercontent.com/ros/rosdistro/master/ros.asc | sudo apt-key add - # 更新并安装 sudo apt update sudo apt install ros-noetic-ros-base ros-noetic-desktop-full # 初始化 rosdep sudo rosdep init rosdep update # 配置环境变量 echo source /opt/ros/noetic/setup.bash ~/.bashrc source ~/.bashrcros-noetic-desktop-full包含 rviz、tf、nav 等常用工具安装体积比较大建议保持网络稳定。3.4 安装 ROS2 Humble在 Ubuntu 22.04 上安装 ROS2 Humble官方推荐命令如下sudo apt update sudo apt install software-properties-common sudo add-apt-repository universe sudo apt update sudo apt install curl -y sudo curl -sSL https://raw.githubusercontent.com/ros/rosdistro/master/ros.key -o /usr/share/keyrings/ros-archive-keyring.gpg echo deb [arch$(dpkg --print-architecture) signed-by/usr/share/keyrings/ros-archive-keyring.gpg] http://packages.ros.org/ros2/ubuntu $(. /etc/os-release echo $UBUNTU_CODENAME) main | sudo tee /etc/apt/sources.list.d/ros2.list /dev/null sudo apt update sudo apt install ros-humble-ros-base ros-humble-desktop echo source /opt/ros/humble/setup.bash ~/.bashrc source ~/.bashrcROS2 不依赖apt-key这种全局 key 管理方式而是使用 keyring 文件所以安装命令会略有不同。如果你已经配置了国内镜像源这里的packages.ros.org可以替换成对应的镜像地址。3.5 创建工作空间与编译工具ROS1 通常用catkinROS2 通常用colcon。这里建议统一创建cave_ws工作空间# ROS1 mkdir -p ~/cave_ws/src cd ~/cave_ws catkin_make source devel/setup.bash # ROS2 mkdir -p ~/cave_ws/src cd ~/cave_ws colcon build source install/setup.bash无论使用哪个版本后续自定义节点、启动文件、配置文件都要放到src目录下对应功能包中。4. 传感器配置与坐标系洞穴建图的前提4.1 洞穴建图中的数据流一套典型的洞穴建图系统数据流大致如下激光雷达 - /scan 或 /velodyne_points - SLAM IMU - /imu/data - SLAM 底盘编码器 - /odom - SLAM可选 SLAM - /map、/tf - rviz 可视化在 ROS 中tf坐标树的完整程度直接决定 SLAM 能否启动。如果缺少base_link到激光雷达的静态变换Cartographer 会直接报 “Could not find transform”。因此在运行建图之前必须先检查 tf 树。4.2 发布传感器静态变换假设机器人底盘坐标系是base_link激光雷达在底盘正上方 0.2 米处IMU 在底盘后方 0.05 米处可以这样发布静态变换# ROS1 rosrun tf2_ros static_transform_publisher 0 0 0.2 0 0 0 base_link laser_frame # ROS2注意参数顺序部分版本支持四元数形式 ros2 run tf2_ros static_transform_publisher 0 0 0.2 0 0 0 base_link laser_frame ros2 run tf2_ros static_transform_publisher 0 0 0 0 0 0 0 1 base_link imu_link注意ROS2 的static_transform_publisher在不同版本中的参数格式有差异有些版本使用x y z yaw pitch roll有些使用x y z qx qy qz qw。运行前可以用ros2 run tf2_ros static_transform_publisher --help查看。4.3 检查话题、TF 和发布频率启动传感器驱动后建议按以下顺序检查# ROS1 rostopic list rostopic hz /scan rostopic echo /scan -n 1 rosrun tf2_tools view_frames # ROS2 ros2 topic list ros2 topic hz /scan ros2 topic echo /scan --once ros2 run tf2_tools tf2_monitor如果激光雷达话题频率低于预期或者 TF 树里缺少关键坐标变换建图质量会大打折扣。洞穴环境里点云质量本来就不高必须在数据来源处确认发布频率和范围。4.4 时间同步为什么不能直接拼接不同时间戳的消息在洞穴环境中机器人经常处于颠簸状态激光雷达每一帧和 IMU 每一帧的真实时刻可能相差几十毫秒。如果直接用不同时间戳的数据做融合点云会出现“重影”地图也会出现重影。ROS1 中建议使用message_filters做近似时间同步#!/usr/bin/env python3 # 文件路径cave_ws/src/cave_sync/scripts/sync_node.pyROS1 import rospy import message_filters from sensor_msgs.msg import PointCloud2, Imu def callback(pointcloud, imu): rospy.loginfo(同步收到点云和IMU消息) def main(): rospy.init_node(cave_sync) sub_pc message_filters.Subscriber(/velodyne_points, PointCloud2) sub_imu message_filters.Subscriber(/imu/data, Imu) ts message_filters.ApproximateTimeSynchronizer([sub_pc, sub_imu], 10, 0.05) ts.registerCallback(callback) rospy.spin() if __name__ __main__: main()这里的ApproximateTimeSynchronizer接受slop参数也就是 0.05 秒。洞穴环境中如果机器人颠簸大建议适当放宽到 0.1 秒但不要太大否则同步后的数据已经失真。ROS2 中的写法类似# 文件路径cave_ws/src/cave_sync/cave_sync/sync_node.pyROS2 import rclpy from rclpy.node import Node from sensor_msgs.msg import PointCloud2, Imu from message_filters import ApproximateTimeSynchronizer, Subscriber class CaveSync(Node): def __init__(self): super().__init__(cave_sync) self.sub_pc Subscriber(self, PointCloud2, /velodyne_points) self.sub_imu Subscriber(self, Imu, /imu/data) self.sync ApproximateTimeSynchronizer([self.sub_pc, self.sub_imu], 10, 0.05) self.sync.registerCallback(self.callback) def callback(self, pointcloud, imu): self.get_logger().info(同步收到点云和IMU消息) def main(): rclpy.init() node CaveSync() rclpy.spin(node) if __name__ __main__: main()在 CMakeLists.txt 或 setup.py 中正确配置入口后就能在对应环境中启动该节点。5. 完整示例使用 Cartographer 做 2D 洞穴建图5.1 为什么先选 2D Cartographer洞穴建图项目如果还处于验证阶段建议先用 2D 激光雷达跑通 Cartographer。原因是2D 雷达便宜、驱动成熟、算法对点云依赖更容易调参通过 2D 流程可以把“TF、时间同步、地图保存”这些关键环节全部理顺再升级到 3D 方案会轻松很多。5.2 Cartographer 配置文件Cartographer 的 2D 配置以 Lua 文件形式存在。这里给出一份适合洞穴走廊环境的配置示例-- 文件路径cave_ws/src/cartographer_ros/cartographer_ros/configuration_files/my_cave_2d.lua include map_builder.lua include trajectory_builder.lua options { map_builder MAP_BUILDER, trajectory_builder TRAJECTORY_BUILDER, map_frame map, tracking_frame base_link, published_frame odom, odom_frame odom, provide_odom_frame true, publish_frame_projected_to_2d false, use_odometry false, use_nav_sat false, use_landmarks false, num_laser_scans 1, num_multi_echo_laser_scans 0, num_subdivisions_per_laser_scan 1, num_point_clouds 0, lookup_transform_timeout_sec 0.2, submap_publish_period_sec 0.3, pose_publish_period_sec 5e-3, trajectory_publish_period_sec 30e-3, } MAP_BUILDER.use_trajectory_builder_2d true TRAJECTORY_BUILDER_2D.submaps.num_range_data 35 TRAJECTORY_BUILDER_2D.min_range 0.3 TRAJECTORY_BUILDER_2D.max_range 30.0 TRAJECTORY_BUILDER_2D.missing_data_ray_length 5.0 TRAJECTORY_BUILDER_2D.use_imu_data false TRAJECTORY_BUILDER_2D.use_online_correlative_scan_matching true TRAJECTORY_BUILDER_2D.motion_filter.max_angle_rad 0.002 TRAJECTORY_BUILDER_2D.motion_filter.max_distance_m 0.1 return options洞穴环境中容易遇到“扫描重叠少、回环少”的情况所以把num_range_data调大一些让子图积累更多扫描帧use_online_correlative_scan_matching打开后可以增强扫描匹配的鲁棒性。5.3 ROS1 启动文件launch !-- 文件路径cave_ws/src/cave_mapping/launch/cartographer_2d_cave.launch -- node namecartographer_node pkgcartographer_ros typecartographer_node args-configuration_directory $(find cartographer_ros)/configuration_files -configuration_basename my_cave_2d.lua outputscreen / node namecartographer_occupancy_grid_node pkgcartographer_ros typecartographer_occupancy_grid_node args-resolution 0.05 outputscreen / /launch5.4 ROS2 启动文件# 文件路径cave_ws/src/cave_mapping/launch/cartographer_2d_cave.pyROS2 import os from launch import LaunchDescription from launch_ros.actions import Node def generate_launch_description(): config_dir os.path.join( os.getenv(AMENT_PREFIX_PATH, ), cartographer_ros, share, cartographer_ros, configuration_files ) return LaunchDescription([ Node( packagecartographer_ros, executablecartographer_node, namecartographer_node, outputscreen, parameters[{ configuration_directory: config_dir, configuration_basename: my_cave_2d.lua, }], ), Node( packagecartographer_ros, executablecartographer_occupancy_grid_node, namecartographer_occupancy_grid_node, outputscreen, parameters[{ resolution: 0.05, publish_period_sec: 1.0, }], ), ])脚本中config_dir的写法在实际工程里可能因为 ROS2 包安装位置不同而需要调整更稳妥的做法是使用ament_index_python查找包路径或者直接写绝对路径。5.5 运行建图确保激光雷达话题是/scan坐标变换已发布然后分别执行# ROS1 roslaunch cave_mapping cartographer_2d_cave.launch # ROS2 ros2 launch cave_mapping cartographer_2d_cave.py打开 rvizROS1 用rvizROS2 用rviz2添加Map显示话题选择/map再添加TF显示就能看到机器人移动时地图逐渐生成。5.6 保存地图Cartographer 保存地图和普通 SLAM 不太一样它先保存.pbstream再转换成 ROS 常用的.pgm和.yaml# ROS1 rosservice call /finish_trajectory 0 rosservice call /write_state {filename: /home/user/cave_ws/cave.pbstream} rosrun cartographer_ros cartographer_pbstream_to_ros_map \ -pbstream_filename /home/user/cave_ws/cave.pbstream \ -resolution 0.05 # ROS2 ros2 service call /finish_trajectory cartographer_ros/srv/FinishTrajectory {trajectory_id: 0}ROS2 下的/write_state服务名称可能因版本不同建议先执行ros2 service list和ros2 service find cartographer_ros/srv/WriteState确认。5.7 保存原始数据建图数据采集过程中最好同时录制 rosbag方便后续回放调参# ROS1 rosbag record -O cave_mapping.bag /scan /imu/data /tf /tf_static # ROS2 ros2 bag record /scan /imu/data /tf /tf_static洞穴环境复杂现场不一定有机会反复跑rosbag 是唯一可靠的“数据备份”。6. 扩展到 3D 洞穴建图点云、IMU 与 LIO 方案6.1 为什么洞穴大场景需要 3D 激光雷达2D 激光雷达只能扫描一个平面洞穴里地面和顶部往往不在同一水平面2D 建图会丢失很多几何约束。3D 激光雷达能同时感知地面、墙壁、顶部对洞穴这种三维结构来说更可靠。3D 建图的问题也很明显点云量巨大普通 ROS 节点处理不过来运动畸变更严重点云中的噪声更难滤除。因此3D 方案通常采用“激光雷达 IMU”紧耦合的方式由 IMU 负责高频运动预测激光雷达负责低频位姿修正。6.2 点云预处理先做范围过滤在把点云送给 SLAM 前先做一个 pass-through 滤波把过远的点、地面噪声点、头顶上的杂散点都过滤掉。这里以pcl_ros为例launch !-- 文件路径cave_ws/src/cave_mapping/launch/pass_through_cave.launch -- node pkgpcl_ros typepassthrough namepass_through_z outputscreen remap from~input to/velodyne_points/ remap from~output to/points_filtered/ param namefilter_field_name valuez/ param namefilter_limit_min value-1.0/ param namefilter_limit_max value5.0/ param namefilter_limit_negative valuefalse/ /node /launch过滤参数并不是越小越好。洞穴地面起伏较大如果z的下限设置过高会丢失坑洼区域的点云导致地面不平整上限过低又会丢掉顶部约束。建议先录制一段 rosbag反复调整过滤参数再决定最终阈值。6.3 LIO-SAM 与 FAST-LIO 的思路LIO-SAM 和 FAST-LIO 共同点是“紧耦合激光惯性里程计”把 IMU 预积分结果和激光点云配准结果放到同一个因子图或卡尔曼滤波框架中优化。相对于 Cartographer它们在大范围 3D 场景中更能抑制漂移但对传感器标定、IMU 噪声模型、点云去畸变要求很高。如果你在 ROS1 环境中使用 LIO-SAM需要注意几个前置条件IMU 必须完成标定激光雷达和 IMU 的外参要写入配置imu_link和base_link之间的静态变换必须准确启动时通常需要保持机器人静止几秒让
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门