☰
Cartographer从零实战:2D/3D建图与定位调参全攻略
2026/10/3 3:42:47 网站建设 项目流程

做机器人SLAM这一行,绕不开的名字不多,Cartographer绝对算一个。这货是Google开源的那套基于图优化的SLAM框架,不只能跑2D建图,3D照样能上,而且自带回环检测和全局优化,比gmapping那种纯粒子滤波的玩法稳定不少。我最早被它吸引,是因为同一套代码里既能处理单线雷达,也能接三维激光,建图定位一把梭,省了在多个框架之间来回折腾的破事。这篇文章我就用实际跑通的经历,从零开始把Cartographer怎么装、怎么跑2D和3D建图、怎么利用已有地图做定位,一条线捋清楚,中间穿插我踩过的坑和调参思路,希望能帮后来的人少走点弯路。

这篇文章适合谁?如果你已经在ROS里跑过几个demo,手头有雷达(或者一个能发布激光/点云数据的bag包),但一直没把Cartographer真正用起来,那这篇正合适。纯小白也能跟下来,安装部分我会把能省的坑都提前踩平,但至少要明白ROS节点、Topic、TF这些基本概念。

1. 建图之前,先把Cartographer的原理吃透

1.1 为什么偏偏是Cartographer

先说结论:Cartographer在激光SLAM圈子里,属于“上限高、下限也稳”的那类框架。它的核心并不是什么黑科技,而是把传感器数据切割成一小段一小段的局部地图(Submap),然后用图优化的方式把这些局部地图拼起来,边拼边回环检测,发现闭环就整体修正一次位姿轨迹。

对比一下常见的方案就清楚了。

gmapping是贝叶斯滤波的老路子,粒子越多越准,但地图一大、走廊一走一长条,粒子群容易退化,又没有闭环修正,跑久了轨迹就飘。ORB-SLAM那套是基于视觉特征点的,激光场景下用不上。Cartographer走的是图优化路线:局部靠Submap累积里程计信息,全局靠回环检测把误差压下去,多传感器(IMU、里程计、雷达)融合进来也不会爆炸。而且官方直接给ROS接口,2D、3D都能跑,这一点在开源库里相当难得。

1.2 理解Submap和回环检测,你就能调好参数

用大白话讲,Submap就是你把当前一小段时间里的激光帧叠到一起,形成的一个局部小地图块。机器人往前走,不断地生成新的Submap。但机器人本身有累计误差,Submap之间不可能严丝合缝——这时候就需要“回环检测”了。机器人绕一圈回到曾经经过的位置,算法通过匹配识别出“这里我来过”,于是把这两个位置的约束条件扔进图优化器里,让整条轨迹的累计误差被重新分配、削弱。

理解了这个机制,你就能明白为什么Cartographer的几个关键参数这么重要:

  • 生成Submap的频率(num_range_data)影响局部地图的稠密程度
  • 回环检测的阈值(min_score)决定多“像”才算闭环,设太高容易漏检,设太低容易误检
  • 每优化多少帧做一次全局优化(optimize_every_n_nodes)直接影响CPU占用和地图平滑度

所以建图时不是参数越多越好,而是要先搞清楚每个参数挂在哪个环节上。

提示:在调参前先把原理弄明白,不然就像蒙着眼睛扭收音机,这边拧一下那边拧一下,最后还是听不清。

1.3 2D和3D本质上是两套数据流程

2D建图,传感器给的是平面的一圈距离数据,Cartographer把这些数据直接投到平面格子里做匹配。3D建图就不一样了,三维激光给的是空间点云,Cartographer要先做点云的分段和特征提取,然后在三维栅格里做匹配,计算量和内存消耗直接翻好几倍。更关键的是,3D模式下如果没有IMU提供重力方向,点云配准很容易因为俯仰、横滚角度的漂移而发散。

所以我通常的建议是:2D优先跑通,3D再逐级加难度。2D模式的调试成本低很多,帮你把TF树、数据质量、参数手感都打磨好,再上手3D就不至于被一堆莫名其妙的问题淹没。

2. 环境准备与安装细节

2.1 版本匹配别乱来,Ubuntu和ROS先配好

Cartographer对ROS版本有要求,别指望拿Kinetic去跑Ubuntu 20.04这种组合,不存在的。我的建议组合就两个:

Ubuntu版本ROS版本说明
18.04Melodic老组合,资料多,稳定
20.04Noetic新一些,Python3友好,长期支持到2025

我自己用的是Ubuntu 20.04 + Noetic。如果你还没有ROS环境,建议先装desktop-full,省得后面缺一堆基础包。装的时候别一个个手敲,直接:

sudo apt update sudo apt install ros-noetic-desktop-full

这步会装挺久,保持网络稳定,别断电。装完记得配rosdep和环境变量,不会的搜一下“rosdep init”和“source /opt/ros/noetic/setup.bash”,这些都是基本功,我这里不展开了。

2.2 apt直接装还是源码编译,两条路我分别说清楚

安装Cartographer有两条路线:一条是直接apt安装,一条是源码编译。

apt安装这段很简单,基本就是几条命令的事:

sudo apt install ros-noetic-cartographer ros-noetic-cartographer-ros ros-noetic-cartographer-rviz

优点是省事,缺点是版本不一定最新,而且你想改源码调试的时候没有本地代码。如果你是新手,只想先跑通,那apt装完就直接跳到第3节吧,别跟自己过不去。

源码编译的好处是能读到全部源码、可以随意改lua配置路径,也能在出问题时跟代码排查。坏处是会踩不少编译坑。我的建议是,如果之后打算深度使用,那迟早要源码编译,建个工作空间,把cartographer和cartographer_ros都clone下来,加上rosdep装上依赖,再catkin_make。下面这段是完整流程:

mkdir -p ~/catkin_ws/src cd ~/catkin_ws/src git clone https://github.com/cartographer-project/cartographer.git git clone https://github.com/cartographer-project/cartographer_ros.git cd ~/catkin_ws rosdep install --from-paths src --ignore-src --rosdistro=noetic -y catkin_make_isolated --install --use-ninja

这里面我踩过比较深的一个坑,是cartographer这个包编译时会依赖abseil,这个库一般不在apt源里,具体错误信息千奇百怪,有的报abseil not found,有的报google::absl相关的头文件缺失。解决方法是在编译cartographer本体之前,先把abseil源码装好,或者干脆添加cartographer官方推荐的abseil编译安装步骤。网上有个叫“鱼香ROS”的一键安装脚本可以辅助装ROS,但Cartographer本身的依赖还是建议手动处理。

2.3 源码编译的几个经典报错,一次说个透

编译踩坑是必然的,我把遇到过频率最高的几个写出来,至少能帮你省下大半天排查时间。

第一,缺少vcs工具。cartographer_ros仓库里有一些.repos文件需要用vcs命令来拉取子模块,如果你执行rosdep install之前发现找不到vcs,先装一下:

sudo apt install python3-vcstool python3-rosdep ninja-build stow

第二,Ceres Solver版本过旧。Cartographer依赖Ceres的某些新接口,Ubuntu apt源里自带的Ceres版本可能不够,编译报failed to find Ceres或者类型不匹配。解决方法是源码安装最新版Ceres:

sudo apt install libgoogle-glog-dev libgflags-dev libatlas-base-dev libeigen3-dev libsuitesparse-dev git clone https://github.com/ceres-solver/ceres-solver.git cd ceres-solver mkdir build && cd build cmake .. make -j$(nproc) sudo make install

第三,protobuf版本冲突。这个最阴间。Cartographer依赖的protobuf版本如果和ROS自带的冲突,编译时会出现一堆google::protobuf相关的诡异报错。我自己的解决办法是:在编译cartographer_ros之前,先确认一下系统里的protobuf版本,必要时通过conda环境或者在单独的工作空间里管理版本,避免和ROS系统包打架。如果你对protobuf不熟,建议直接apt安装Cartographer而不是源码编译,少受内伤。

3. 2D建图实战:从激光数据到一张完整地图

3.1 数据从哪里来:雷达、bag包和TF

开始2D建图前,你得有一个能发布/scan话题的传感器源。不外乎两种情况:物理雷达直连,比如rplidar、思岚、镭神这些,装上驱动就能出数据;或者手头有一个公开的bag包,里面已经录好了雷达扫描和里程计信息,不用接硬件就能直接跑,新手学习强烈推荐这种方式。

除了雷达数据,Cartographer还需要TF树。最基础的需求是base_link到laser的坐标变换,以及odom到base_link的变换。你可以用一个固定TF把雷达装在机器人坐标系下,再用robot_pose_ekf或者车轮里程计或者IMU积分出odom到base_link。如果只是跑bag包,直接播放bag,TF通常也在包里。

检查一下topic和TF是不是完整,用这几条命令:

rostopic list rostopic echo /scan | head rosrun tf tf_echo base_link laser

如果TF里缺了odom->base_link,部分bag包里用的是map->odom->base_link结构播放的,那Cartographer也能用,但不建议新手一开始在这种结构里折腾。尽量保证在跑之前,odom、base_link、laser这三个frame是齐的。

3.2 2D建图的第一条启动命令

假设你已经准备好了bag包,并且已经启动了一个ROS master,然后进入cartographer_ros官方提供的demo目录,里面有几个launch文件可以直接复现经典场景。最省事的方式是直接跑官方bag的例子,比如backpack_2d那个数据集:

roslaunch cartographer_ros demo_backpack_2d.launch bag_filename:=/路径/到/your.bag

这个launch会同时启动Cartographer节点、Rviz可视化,以及自动播放bag。如果一切正常,你能在Rviz里看到地图在激光数据中逐渐展开、点云栅格慢慢成型,还能看到一条绿色的轨迹线。第一次看到这张图成形,那种成就感跟拼好一台装了好久的模型差不多。

如果你接的是真实雷达,需要自己写launch,核心部分其实就两段:一段是启动雷达驱动(把原始数据转成/scan消息),另一段是启动cartographer节点(加载2D的lua配置)。比如你雷达已经在发/scan了,那节点启动大致是:

<launch> <node name="cartographer_node" pkg="cartographer_ros" type="cartographer_node" args="-configuration_directory $(find cartographer_ros)/configuration_files -configuration_basename backpack_2d.lua" output="screen"> <remap from="scan" to="scan" /> </node> <node name="rviz" pkg="rviz" type="rviz" args="-d $(find cartographer_ros)/configuration_files/demo_2d.rviz" /> </launch>

这里面remap from="scan" to="scan"看起来有点傻,但如果你实际雷达话题是/rplidar/scan,这里就要改成:

<remap from="scan" to="rplidar/scan" />

3.3 建图过程中的调参手感,我这么调

2D建图一旦能跑起来,下一步就是调参。核心配置文件是backpack_2d.lua,里面又引用了一个通用的map_builder.lua和一个trajectory_builder_2d.lua。改动之后不需要重新编译,重启节点即可生效。

我调参数的习惯是先调这三个:

-- trajectory_builder_2d.lua TRAJECTORY_BUILDER_2D.num_accumulated_range_data = 10 TRAJECTORY_BUILDER_2D.min_range = 0.3 TRAJECTORY_BUILDER_2D.max_range = 30.

num_accumulated_range_data控制多少帧激光数据叠加成一个点云进行scan matching。数值越大,局部地图越稠密,但运动太快时会拖影、造成匹配失败。我一般室内场景设5到10,走廊长、雷达帧率高的场景可以设大一点,但也要注意CPU占用。

min_range和max_range直接过滤雷达的无效值和不想要的近距离噪点。很多雷达在0.2米以内会有一圈杂波,把min_range设到0.3甚至0.5能有效规避。

然后是回环检测的阈值,在map_builder.lua或者pose_graph部分:

POSE_GRAPH.constraint_builder.min_score = 0.55 POSE_GRAPH.optimize_every_n_nodes = 90

optimize_every_n_nodes设得越小,全局优化越频繁,地图一致性越好,但CPU负担也越大。如果机器性能一般,我一般设90~120之间。min_score设得太高比如0.8以上,闭环检测率会明显下降,地图会容易“叠层”,也就是走廊拐弯处出现重影。我自己遇到过最典型的场景:一个转弯处来回走了三四遍,地图上出现双层墙,就是score阈值太高导致回环没触发,调低到0.55之后很快就拼上了。

还有个参数容易被忽略,但很关键——map_frame和tracking_frame的名字要和你的TF树一致。默认是map、base_link,如果你机器人写了base_footprint之类的frame名,不去改配置就会疯狂报错。

注意:调参时每次只改一个变量,跑一小段再对比地图。不要一次改三个参数,不然出了问题根本不知道是谁的锅。

3.4 地图怎么保存和转换

建图结束以后,Cartographer本身不直接存.pgm/.yaml这种传统2D栅格地图。你需要用官方提供的脚本把pbstream转成栅格地图。先保存当前地图为pbstream格式:

rosservice call /write_state "{filename: '/home/user/map.pbstream', include_unfinished_submaps: true}"

再用cartographer_ros自带的cartographer_pbstream_to_ros_map工具转成pgm和yaml:

rosrun cartographer_ros cartographer_pbstream_to_ros_map -map_filestem=/home/user/map -pbstream_filename=/home/user/map.pbstream

这样就会生成map.pgm和map.yaml,这两个文件可以直接用于后续的定位模式,或者给move_base做导航用。

4. 3D建图实战:把点云变成空间

4.1 3D传感器的选择,贵有贵的道理

3D建图的传感器选择很关键。常见的有Velodyne的16线/32线/64线机械式激光雷达、Livox(镭神)的固态雷达、以及RGB-D相机如Kinect、RealSense等。机械式雷达点云量大、均匀度高,效果最好但也最贵;Livox这类固态雷达视野不规则,点云密度不均匀,Cartographer的3D模式需要把它的点云转换成标准的PointCloud2消息,还要注意点云的坐标定义和雷达自身的畸变校正。

RGB-D相机可以做3D建图,但视野窄、测距近(一般5米内),只适合小房间级的重建,跑大场景容易废。如果你只有RGB-D相机,建议先去看看ORB-SLAM3或者RTAB-Map,别硬拿它磨Cartographer。

4.2 跑通3D建图的标准动作

官方给了一个3D的demo数据集,也给了launch文件。如果你手头有Velodyne的数据包,可以直接用demo_backpack_3d.launch跑,用法和2D差不多:

roslaunch cartographer_ros demo_backpack_3d.launch bag_filename:=/路径/到/your.bag

但如果你是用自己的雷达节点,就需要修改launch文件,把点云话题映射到Cartographer节点。3D模式下,Cartographer订阅的话题一般是/points2,这里有个很容易踩坑的点:有些三维雷达驱动发的是/velodyne_points或者/lslidar_point_cloud,需要remap,同时要确认消息类型是sensor_msgs/PointCloud2,不是PCLPointCloud2或PointCloud、PointXYZ之类的老格式。

<node name="cartographer_node" pkg="cartographer_ros" type="cartographer_node" args="-configuration_directory $(find cartographer_ros)/configuration_files -configuration_basename backpack_3d.lua" output="screen"> <remap from="points2" to="velodyne_points" /> </node>

4.3 3D建图会逼疯你的几个细节

第一,IMU几乎是必需品。3D建图如果没有IMU提供重力对齐和角速度积分,点云配准很容易发散,尤其是雷达运动有俯仰或者横滚的时候。Cartographer的3D配置里默认会监听/imu话题,发布频率最好在100Hz以上,延迟要低。有些廉价的IMU数据噪声很大,建出来的地图点云会像糊了一层雾,这时候宁可不用纯陀螺数据,也要在驱动层先做低通滤波。

第二,内存和CPU压力巨大。3D建图过程中,Submap维护的是三维栅格模型,内存消耗比2D高一个数量级。我在一台16GB内存的笔记本上跑16线激光的3D建图,跑了十几分钟内存就逼近12GB。建议强烈注意内存占用,如果机器吃紧,可以适当降低TRAJECTORY_BUILDER_3D.num_accumulated_range_data的数值,或者在雷达驱动侧做点云降采样。

第三,点云畸变不可忽视。机械式雷达扫描一圈需要几十到上百毫秒,在这期间机器人如果运动过快,一个点云帧里的点就会有运动畸变。Cartographer支持配置基于IMU的畸变矫正,但这里面比较考验传感器同步水平,通常需要精确的雷达时间戳和IMU时间戳校准。时间戳不同步的典型表现是地图边缘出现“卷毛状”鬼影,这时候别急着调Cartographer参数,先回头检查雷达驱动和IMU的时钟。

5. 建图好之后,怎么用它来定位

5.1 定位与建图的模式切换逻辑

Cartographer的定位模式本质上还是SLAM,只不过它把地图锁定了,相当于是“只利用已有地图来修正当前位姿”。它和重定位、amcl这类蒙特卡洛方法的区别在于,Cartographer依然会维护一个轨迹、会把当前激光数据和已有地图做匹配,而不是预先在地图上撒一大把粒子来猜。

这样做的优势是精度更高、对动态环境更鲁棒;缺点是初始位姿必须给得八九不离十,你得告诉它“我大概在这一片”,它才能收敛到精确位置。如果初始位姿偏差太大,Cartographer直接没法匹配上地图,定位也就废了。

5.2 地图文件准备和launch文件改造

要想从建图切到定位,第一步是准备好之前保存的pbstream文件。pbstream是Cartographer自己的地图格式,里面包含子图和轨迹信息,定位模式必须用它,而不是pgm/yaml。所以如果你只想用pgm做amcl定位,那走的另一套逻辑;如果要跑Cartographer纯定位,必须回到第3节说的/write_state服务来保存pbstream。

第二步是写一个定位专用的launch,参考官方提供的demo_backpack_2d_localization.launch。核心是把lua配置里的定位模式开启,并且加载已有地图。

在lua里,你需要加上类似这样的配置:

include "map_builder.lua" include "trajectory_builder_2d.lua" options = { map_builder = MAP_BUILDER, trajectory_builder = TRAJECTORY_BUILDER_2D, map_frame = "map", tracking_frame = "base_link", published_frame = "base_link", odom_frame = "odom", provide_odom_frame = false, publish_frame_projected_to_2d = true, 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_builder_2d = TRAJECTORY_BUILDER_2D, } return options

这里provide_odom_frame = false的意思是,odom到base_link的变换由机器人自己的里程计源提供,Cartographer只发布map到odom的修正。这样设计的好处是,如果你有轮式里程计或者视觉里程计在维持局部连续运动,Cartographer全局负责把odom到map的漂移修正掉。

启动的时候,在launch的cartographer_node里加一个参数:

<param name="start_trajectory_with_localization" value="true" /> <param name="initial_pose" value="0.0, 0.0, 0.0" />

initial_pose是你对机器人初始位姿的猜测值,单位是米和弧度,格式是x、y、yaw。如果给得太离谱,后面一切白搭,所以实用上最好在定位程序里额外写一个服务来设置初始位姿,或者通过Rviz的“2D Pose Estimate”按钮来指定。

5.3 定位模式跑起来之后,怎么判断它到底收敛没

定位模式启动后,别急着让它跑。先在Rviz里看激光数据是否和地图轮廓重合,正常情况下一开始就能看到点云和已有地图基本对齐,如果相差很大,说明初始位姿给错了。直接按上面说的,用Rviz的2D Pose Estimate重新给一个初值。

收敛之后,注意观察Cartographer输出的位姿里,map到odom的变换是否在缓慢漂移。正常的定位模式下,map到odom的变换会随着机器人移动而变化,这是算法在持续修正全局位姿,不是故障。但如果这个变换在一段时间内漂移幅度特别大,比如几秒内跳了好几十厘米,那说明激光匹配有严重问题,要检查当前场景的地图特征是否足够——空旷的走廊和大片空地是激光匹配的噩梦,特征太少会有“隧道效应”,沿走廊方向定位基本靠猜。

定位调参主要关注这两个参数:

POSE_GRAPH.constraint_builder.min_score = 0.6 POSE_GRAPH.optimize_every_n_nodes = 30

定位模式下optimize_every_n_nodes可以比建图时设得更小一些,因为不需要保存新子图,全局优化的频率高一点,实时修正会更灵敏。min_score在定位时我习惯设高一点,0.6左右,防止在相似区域产生误匹配——但注意别高到0.9,否则机器人一进相似环境就断定位。

6. 常见问题与排查技巧实录

6.1 我遇到的几个典型问题

这些问题全是实操里真实遇到过的,我整理成一个速查表,按影响程度排个序。

问题现象可能原因解决办法
建图启动后地图一片空白雷达话题没remap,Cartographer没拿到数据检查rostopic list和cartographer节点输出的日志,确认scan/points2话题订阅正常
地图出现重影、墙体叠层回环检测阈值过高,或optimize_every_n_nodes太大调低min_score,调小optimize_every_n_nodes,重新跑一段
地图边缘有拖尾、鬼影雷达和IMU时间戳不同步,或发布频率不稳定检查CPU占用和话题频率,校准时间戳,必要时降速移动机器人
定位模式启动后激光和地图错位初始位姿给错用Rviz的2D Pose Estimate重新手动指定初始位姿
3D建图跑几分钟内存爆掉点云频率太高或num_accumulated_range_data太大降低点云计算频率、做体素降采样、调小num_accumulated_range_data
编译时找不到abseil缺依赖库先编译安装abseil,或直接apt装cartographer,不要硬刚源码编译
启动节点报No map frame foundTF树缺map->odom变换先手动发布map->odom的静态TF,或者在配置里设置provide_odom_frame
电脑CPU占用100%参数如optimize_every_n_nodes太小增大optimize_every_n_nodes,减小num_accumulated_range_data

6.2 我常用的几个调试心得,价值不低

第一个心得:colored map帮大忙。Cartographer的Rviz插件可以显示“子图视图(submap list)”,在Rviz左侧的Map面板里勾选Map的Topic为/submap_list,然后把视角切到俯视图,你能很直观地看到回环检测触发时子图之间的连接。调参的时候,这个视图比单纯看栅格图直观得多。

第二个心得:多看/tf的延迟。运行期间用rosrun tf tf_monitor查看各link之间变换的发布延迟。如果odom->base_link的延迟经常超过100ms,Cartographer在scan matching时拿到的位姿预测就会不准,尤其Z轴有旋转的3D场景更容易炸。这时候优先解决里程计源,而不是调雷达参数。

第三个心得:建立“永远先检查数据”的肌肉记忆。每次出问题,第一件事不是查Cartographer,而是用rostopic hz /scan看雷达数据发布频率稳不稳定,用rqt_tf_tree看TF树是否完整,用rosbag info确认bag包里到底有没有IMU数据。八成的问题在数据质量阶段就能暴露出来。

第四个心得:尽量用roslaunch自带--screen参数把日志全部打印出来,别让output=“log”把信息藏起来。里面会出现I0625 ...、W0625 ...、E0625 ...这些日志,Warning和Error往往直接告诉你哪一步的约束失败了,比如Constraint score lower than min_score这种,一眼就能定位到是匹配score不够的问题。

7. 实际操作中的体会

我前前后后用Cartographer跑过2D室内建图、3D走廊扫描,也在移动底盘上做过好多次基于已有地图的定位,感受最深的一点是:这套系统对数据质量的要求比gmapping高得多。数据脏一点、TF慢一点、时间戳飘一点,gmapping可能还勉强撑得住,Cartographer就直接给你报错或者建出鬼影地图。但反过来,一旦数据理顺了,它给你的地图质量和定位稳定性也是gmapping给不了的。

如果你现在刚开始接触,我给的最实在的建议就是:先找一份公开的bag包,按文中的命令把2D建图跑通,别急着接实物雷达;跑通之后再换成自己的数据,一步一步排查;等2D流程熟透了,再碰3D。跳过步骤、直接一把梭搞3D的人,我见多了,大部分都卡在点云畸变和IMU时间戳上,白白耗掉好几天。

最后再分享一个小技巧:调参的时候养成记录参数组合的习惯,每次跑完把改过什么写下来,不然第二天你可能就忘了是哪个参数让地图突然变好了。我自己的方案是给不同的bag包建个参数配置文件夹,每个场景单独存一份lua,标注日期和改动点,后来换场景、复现问题都方便得很。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询