尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

Cartographer从零实战:2D/3D建图与定位调参全攻略

Cartographer从零实战:2D/3D建图与定位调参全攻略 做机器人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 --rosdistronoetic -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或者类型不匹配。解决方法是源码安装最新版Ceressudo 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包直接播放bagTF通常也在包里。检查一下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 namecartographer_node pkgcartographer_ros typecartographer_node args-configuration_directory $(find cartographer_ros)/configuration_files -configuration_basename backpack_2d.lua outputscreen remap fromscan toscan / /node node namerviz pkgrviz typerviz args-d $(find cartographer_ros)/configuration_files/demo_2d.rviz / /launch这里面remap fromscan toscan看起来有点傻但如果你实际雷达话题是/rplidar/scan这里就要改成remap fromscan torplidar/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 90optimize_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和yamlrosrun 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 namecartographer_node pkgcartographer_ros typecartographer_node args-configuration_directory $(find cartographer_ros)/configuration_files -configuration_basename backpack_3d.lua outputscreen remap frompoints2 tovelodyne_points / /node4.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 namestart_trajectory_with_localization valuetrue / param nameinitial_pose value0.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 我遇到的几个典型问题这些问题全是实操里真实遇到过的我整理成一个速查表按影响程度排个序。问题现象可能原因解决办法建图启动后地图一片空白雷达话题没remapCartographer没拿到数据检查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_data6.2 我常用的几个调试心得价值不低第一个心得colored map帮大忙。Cartographer的Rviz插件可以显示“子图视图submap list”在Rviz左侧的Map面板里勾选Map的Topic为/submap_list然后把视角切到俯视图你能很直观地看到回环检测触发时子图之间的连接。调参的时候这个视图比单纯看栅格图直观得多。第二个心得多看/tf的延迟。运行期间用rosrun tf tf_monitor查看各link之间变换的发布延迟。如果odom-base_link的延迟经常超过100msCartographer在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标注日期和改动点后来换场景、复现问题都方便得很。
返回列表