ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

Cartographer激光SLAM入门:原理、架构与工程实践

Cartographer激光SLAM入门:原理、架构与工程实践 如果你最近在折腾移动机器人、扫地机、园区配送车这类带实体移动的东西大概率绕不开 Cartographer 这个名字。它是 Google 在 2016 年开源的激光 SLAM 库主打 2D/3D 实时建图与定位这几年几乎成了工业级激光 SLAM 的默认选项之一。这个系列我打算从头到尾拆一遍第一篇先把 Cartographer 是什么、为什么选它、整体架构长什么样讲清楚让还没上手的人有一个完整的地图认知后面几篇再钻代码、调参数、记录实战踩坑。如果你是有 SLAM 基础但一直没系统读过 Cartographer 源码的朋友或者刚接触机器人导航、想给自己小车加一个靠谱建图方案的新手这篇都值得好好看看。我不打算给你堆术语尽量把每个概念还原到“为什么这么设计”的层面上。1. Cartographer到底是什么1.1 起源与定位Cartographer 是 Google 在 2016 年 10 月开源的实时 SLAM 库对应的论文是《Real-Time Loop Closure in 2D LIDAR SLAM》当年发在 ICRA 上。开源之后很快在 ROS 社区火起来因为它解决了当时很多开源 SLAM 方案的真实痛点长时间建图累积漂移、回环检测不稳定、对传感器要求苛刻。严格说Cartographer 不是单纯“画地图”的工具而是一整套轨迹估计与建图系统。它把激光数据、IMU、里程计、甚至顶帽标记点Landmark都融合进来一边估计机器人轨迹一边构建栅格地图并且在检测到机器人回到曾经去过的地方时会主动“回头修正”历史和轨迹。这种闭环能力是它区别于 Gmapping、Hector 这类轻量方案的核心标志。很多人第一次听到“SLAM”会以为是个黑盒子激光进去地图出来。Cartographer 给我的感觉更像是一条生产流水线前端负责把每一帧激光精确“贴”到当前局部地图上后端负责定期检查“整条轨迹有没有闭环错误”发现问题就全局优化一遍。两个环节互相配合一个管局部准一个管全局稳。1.2 它和“建图工具”的区别在 ROS 生态里常见的建图工具不少但 Cartographer 的设计定位有两个明显不同。第一个不同它对传感器融合的态度是“最好全都要”。Cartographer 不会因为你是单线激光雷达就拒绝工作但如果你想让它长时间稳定跑它更希望拿到 IMU 的角速度和线加速度拿到轮式里程计或视觉里程计甚至拿到路标点数据。多一份约束轨迹的漂移就少一点。你可以把它理解为“一个非常愿意吸收各种信息来降低不确定性的系统”。第二个不同地图不是一次性输出的结果而是伴随轨迹持续维护的状态。Cartographer 内部有很多被称为 Submap子图的局部地图轨迹则由一连串位姿构成。回环检测发生时它修正的不是当前帧而是把整串位姿和相关子图做一次大调整。这也是为什么它在回环场景下表现明显强于普通 scan-to-scan 匹配方案。如果一个机器人只走直线、从不回头任何 SLAM 都会表现不错一旦绕圈、回到原点附近才是真正见功底的地方。2. 为什么选择Cartographer2.1 与主流开源方案横向对比每次聊 SLAM免不了拿经典方案做对比。我直接给一张表把几个常见 2D 激光 SLAM 方案放在一起方便你按场景挑。方案核心思想优点主要痛点GmappingRBPF粒子滤波轻量、适合小场景、对激光频率要求不算高依赖里程计大场景粒子数爆炸回环能力弱HectorSLAMscan-to-map梯度匹配不需要里程计实现简洁对激光帧率和噪声敏感无回环打滑场景容易废Karto图优化SLAM有后端能做闭环早期工程整合成本高社区维护不够活跃Cartographer子图图优化分支定界回环回环强、支持多传感器融合、2D/3D统一配置复杂CPU占用偏高纯激光场景也需细心调参注意这张表不是说其他方案一无是处。如果你的机器人只在几十平房间里低速跑、环境又比较空旷Gmapping 完全可以胜任如果传感器很差、没有轮式里程计可能 Hector 反而更容易跑起来。但如果你需要长时间、大范围、反复往返的工作场景比如商场清洁机器人、仓储盘点机器人、园区巡检车Cartographer 的抗漂移能力就是实打实的优势。2.2 从工程角度谈选型除了算法能力工程因素也很重要。Cartographer 背后是 Google 维护的开源项目代码风格统一文档、issue 讨论活跃Ceres Solver、Abseil、glog 等依赖也都是成熟库。相比某些只有论文、没有可用代码的方案它更接近“可以拿去落地”的工业级项目。选型时你还要想清楚一个问题团队里后续有没有人能读懂、改得动代码。Cartographer 的代码起初对新手不太友好抽象层级多模板多但要改回环搜索策略、匹配核函数、栅格更新方式它的扩展点其实非常清晰。这一点我在后面系列文章里会重点拆。假如你们只是临时要一张地图那确实没必要啃 Cartographer 源码但如果是做产品长期要维护地图质量、定位稳定性那就值得在它上面投入时间去学透。纯激光小车的场景要特别提醒Cartographer 虽然融合能力很强但对纯 2D 激光、没有 IMU、没有里程计的小车并不是零成本跑起来。你需要给它补充运动预测信息或者通过参数调整放宽匹配搜索范围否则很容易出现“地图莫名扭曲”的状况。这个我放在第四节详细说。3. 核心原理初探不懂代码也能理解的设计3.1 两段式架构局部SLAM与全局SLAMCartographer 整体可以分为两个大的模块局部 SLAMLocal SLAM和全局 SLAMGlobal SLAM。你可以把它们想象成两个配合工作的员工一个人手里拿着草稿纸负责把最新看到的激光点以最快速度画到当前这页草图上要求手稳、速度快另一个人拿着整本笔记定期检查前面的草稿页之间是不是拼错了一旦发现某页应该和另一页相邻就回头把所有相关页重新排列。局部 SLAM 的核心任务是维护“当前子图”。每来一帧激光它先用当前估计位姿把激光点投影到子图坐标系下再通过 scan-to-map 匹配对位姿做一次更精细的修正最后把修正后的激光点插入子图更新栅格概率。这个过程以帧为单位实时执行频率高、延迟低但只考虑局部一致性时间久了必然有累积误差。全局 SLAM 的核心任务是维护位姿图Pose Graph。节点就是不同时刻的机器人在某个坐标系下的位姿边代表这些位姿之间的约束。约束来自两类一类是相邻帧之间“短时间内的位姿关系”另一类是回环检测发现“并不相邻、但从数据结构上看应当重合”的长程关系。全局 SLAM 在收到新的约束后会运行一次基于 Ceres Solver 的图优化把所有节点和边的误差最小化得到一版全局更一致的轨迹。3.2 Submap和“打草稿再定稿”的巧妙之处Submap子图是理解 Cartographer 的一把钥匙。它不是一整张大地图而是一段局部地图。默认情况下Cartographer 会攒一定数量的激光帧就生成一个新的子图比如 2D 模式里常见的是几十帧一个。子图可以理解成“被打草稿的半成品地图”新来的激光帧会先贴到最新子图上子图累积到足够帧数后就冻结变成不可变的地图块。为什么不能直接把所有帧都往一整张大图上贴因为存在累积误差。如果把所有帧都强制放到同一个全局栅格上前面几帧效果还好后面误差会越来越大地图越来越糊。分成子图以后局部 SLAM 只需要保证“当前帧贴到当前子图”是准确的至于子图和子图之间是否“拼得严丝合缝”交给全局 SLAM 去检查。这样一来局部匹配的压力小全局修正的范围又可控整个系统实时性和准确性都能兼顾。用拼拼图来类比先按时间顺序拼出很多小块子图每块内部的拼图是紧贴的然后退后一步看哪几块其实挨着、哪几块拼反了再统一调整所有小块的相对位置。Cartographer 的回环检测就是那个“退后一步看全貌”的动作。3.3 回环检测怎么知道机器人回到了原来的地方Gmapping 这类方案很少做回环因为它的地图表示和粒子滤波结构很难高效回溯修正。Cartographer 则把回环检测当成头等大事。回环检测的核心问题机器人走到某个位置它怎么知道“这个场景我以前来过”Cartographer 的做法是把当前激光扫描与附近所有已经冻结的子图做一一匹配寻找一个分数最高的匹配结果。如果分数超过设定阈值并且这个匹配对应的轨迹位置与当前位姿足够远说明确实走出了一个大圈就认为这是一个回环约束把它加入位姿图。匹配过程不是暴力搜索所有位置因为那样太慢。Cartographer 论文里的一个亮点是使用分支定界Branch and Bound搜索策略。简单理解先在一个很粗的搜索网格上做低精度匹配快速剔除大量明显不行的候选位置再在剩余的高分区域逐步细化找到全局最优或接近全局最优的匹配。这样既保证搜索范围足够大又控制计算量。这条设计直接影响建图质量。实际测试中一个 1000 平米以上的区域纯靠前端的局部匹配走下来轨迹误差经常会到几十厘米甚至几米但触发几个靠谱的回环约束再做全局优化误差能压回厘米级别。这就是 Cartographer 的“杀招”。4. 传感器输入、TF树与数据流水线4.1 前端需要哪些传感器Cartographer 在纯 2D 模式下最少只需要 2D 激光扫描但这不代表它喜欢这么干。从稳定性角度看下面几种传感器各有分工激光雷达核心传感器提供点到环境的距离观测。Cartographer 对激光数据会做体素滤波把同一区域重复的点压掉控制计算量。IMU提供高频姿态与加速度约束。特别在机器人快速旋转的时候IMU 能告诉前端“这半秒内机器人转了多少度”避免 scan-to-submap 匹配因为初值太差不收敛。里程计Odometry提供短时间内的位移估计给 scan matcher 一个靠谱的初始位姿。轮式里程计、全向底盘编码器、视觉里程计都行。Landmark路标点可选输入比如顶帽、二维码、UWB 锚点。它们能提供绝对位置约束对长时间建图尤其有用。这段信息很重要Cartographer 不是“越多传感器越好”而是“约束越稳越好”。如果你给它的里程计本身打滑严重就得适当降低里程计在初始估计里的权重如果 IMU 安装角度没配好反而会把姿态带偏。4.2 TF树map、odom、base_link三个世界接触 Cartographer 之后你一定会遇到一张 TF 树核心是 map、odom、base_link 三个坐标系。map地图坐标系全局回环修正之后的地图锚定在这里是“真实世界”的参考系。odom里程计坐标系由轮式里程计或惯导积分出来的轨迹位于这里短时间局部可用但长时间会漂移。base_link机器人本体坐标系激光、IMU 都挂在它下面。Cartographer 的工作可以描述成前端主要在 odom 坐标系附近干活用它和激光匹配保证局部轨迹平滑后端一旦做出回环修正就调整 map 到 odom 的变换让整条轨迹在地图坐标系下重新对齐。所以你在 RViz 里如果看到地图偶尔“跳一下”完全不用慌大多数时候是后端收到回环约束后在做全局修正。很多新手在这块犯迷糊明明是激光雷达数据为什么 TF 树不对就跑不出地图因为 Cartographer 需要知道你每个传感器的空间位置和外参坐标变换错了数据融合全歪。我遇到过因为激光安装高度填错地图建出来像被“压扁”的案例查了半天最后发现是 TF 的问题。4.3 激光数据如何一步步变成地图拿 2D 场景举例一次完整的数据流水线大概是这样的激光雷达发布原始点云或 LaserScan经过自适应体素滤波减少密集区域点数。同一时刻收到 IMU 数据和里程计数据通过外参把它们转换到 base_link 坐标系。前端用上一个时刻的位姿加上 IMU/里程计预测得到当前帧的初始估计。把当前激光扫描与最新子图做 scan-to-map 匹配优化位姿。优化后的位姿作为轨迹节点同时把激光点插入子图更新栅格概率。当子图达到指定帧数冻结子图开启下一个子图。后端定期做回环搜索发现闭环则加入约束并触发全局优化。最终通过 cartographer_ros 中的占据栅格转换逻辑把子图转为 ROS 的 OccupancyGrid 发布出去。可以看到每一步都有明确目标。前端两步保证“准”后端一步保证“稳”栅格输出保证“能用”。理解这条流水线之后你再看 Cartographer 的源码就能对着文件找自己的位置了。5. 环境搭建与快速跑通第一个Demo5.1 安装方式建议源码编译Cartographer 最简单的安装方式是 apt 直接装ros-noetic-cartographer和ros-noetic-cartographer-ros对于只想调包的用户确实省事。但我个人推荐源码编译原因很简单后面几篇要解析代码你总得能随时改源码、加日志、打断点。而且源码编译能让你顺便摸清 Ceres、Abseil 这些依赖在系统里是怎么组织起来的。源码编译基本步骤如下# 创建工作空间 mkdir -p ~/carto_ws/src cd ~/carto_ws/src git clone https://github.com/cartographer-project/cartographer_ros.git git clone https://github.com/cartographer-project/cartographer.git # 使用wstool合并rosdep依赖 sudo apt update rosdep update rosdep install --from-paths src --ignore-src -r -y # 安装protobuf、suitesparse等系统依赖 sudo apt install -y python3-wstool python3-rosdep ninja-build libceres-dev libsuitesparse-dev # 编译 cd ~/carto_ws catkin_make_isolated --use-ninja source devel_isolated/setup.bash编译过程最常遇到的问题有两个protobuf版本冲突以及Ceres Solver版本过旧。建议你把系统里的 protobuf 版本与 Cartographer 要求的版本对一下必要时单独编译一套libprotobuf和protoc放到独立前缀下避免污染系统环境。5.2 跑通离线建图Demo安装完成后最快的体验方式是跑官方提供的离线 bag 包。Cartographer 官方仓库里放了几个示例数据包比如b2-2016-04-05-14-44-52.bag是背包式设备在室内环境中采集的真实数据。下载完 bag 后执行roslaunch cartographer_ros demo_backpack_2d.launch \ bag_filename:/your/path/to/b2-2016-04-05-14-44-52.bag你会看到 RViz 窗口打开机器人的轨迹和地图慢慢生长出来。值得注意的几个点官方 demo 默认开了 3D 可视化地图用的是/map话题建图过程中轨迹会保持稳定当 robot 在第几分钟内绕回起点附近时你能明显观察到地图被“拉正”的过程这就是回环闭合带来的全局修正效果。第一次跑建议多做一件事把 bag 播放速度调慢一点例如rosbag play -r 0.5观察后端优化发生时轨迹和地图的变化。这个体感比任何文档描述都直观。5.3 认识第一份配置文件跑通 demo 之后你肯定会想能不能调参数让我自己的机器人也跑起来Cartographer 的参数配置集中在.lua文件里2D 场景最常改的是这两个map_builder.lua负责地图构建器的全局设置比如是否启用 2D、3D以及回环优化线程数。trajectory_builder_2d.lua负责 2D 轨迹构建器的详细参数包括激光匹配、子图插入、传感器权重。我挑几个新手绕不开的关键参数TRAJECTORY_BUILDER_2D.min_range 0.3 TRAJECTORY_BUILDER_2D.max_range 30. TRAJECTORY_BUILDER_2D.num_accumulated_range_data 1 TRAJECTORY_BUILDER_2D.use_imu_data truemin_range/max_range过滤激光数据。雷达近处容易有飞点、遮挡远处灰尘杂讯多把它们设为合理值能显著提升匹配稳定性。比如室内你一般不需要看 30 米外可以降下来省算力。num_accumulated_range_data把几帧激光累积起来再插入子图。值越大单次插入的信息越密集但实时性会变差内存开销也高。use_imu_data如果你的设备真的没有 IMU必须设为false否则系统会因为等待 IMU 数据而不工作或崩溃。但一旦关了建议给足里程计源否则前端缺少预积分运动估计纯靠 scan match 撑不久。提示改参数不是玄学每次只改一个变量对比前后地图差异避免多个参数一起调导致无法定位问题来源。6. 常见问题排查与实战心得6.1 地图乱飘、轨迹发散怎么办表现地图越建越歪或者机器人明明走直线轨迹却画成圆弧。排查优先级我一般这样排第一检查TF树是否完整map、odom、base_link、laser、imu 都在发布吗外参对吗第二确认use_imu_data与真实传感器是否一致没 IMU 却填 true可能直接崩有 IMU 但安装方向反了轨迹会离谱。第三看子图冻结频率如果num_accumulated_range_data设得过大系统响应慢容易掉帧导致匹配错位。还有一个容易忽略的点激光雷达本身的频率和角分辨率。Cartographer 对低帧率雷达比如 5Hz 以下会力不从心因为两帧之间机器人可能已经转动很大角度匹配初值很难猜准。如果雷达只能提供 10Hz尽量配合较高频率的 IMU否则就调大loop_closure相关搜索范围。6.2 CPU占用偏高怎么优化Cartographer 计算开销主要集中在 scan-to-submap 匹配、栅格概率更新和回环搜索上。项目里如果 CPU 不够优先做三件事打开自适应体素滤波器把每一帧激光点数限制在合理范围。2D 场景几千个点足够不需要几万点全上。调大num_accumulated_range_data减少激光插入子图的频率。降低回环检测频率或者限制搜索半径例如限制max_constraint_distance不让它搜索离当前轨迹太远的位置省掉大量无效匹配。调试 CPU 占用时不要纠结单个匹配有多快先看整体帧率能不能保持实时。SLAM 是流式系统局部 SLAM 必须跟上传感器输入频率否则后台积压数据实时性直接崩。6.3 回环出现明显瞬移或者错误闭合正常回环发生时后端优化会让轨迹“跳”到正确位置地图也会有可见变化这不算 bug。但如果你发现地图从某个位置开始大面积错位、或者明明两个不重叠的位置被强制拼到了一起通常是回环误匹配导致的。遇到这种情况优先调高匹配阈值min_score降低误匹配概率再检查分支定界搜索的栅格分辨率submaps.grid_2d.resolution分辨率太低容易匹配粗粝导致误闭环。此外landmark和odometry权重过高也可能让位姿图里长程约束失真需要综合调整。这里给一条调试习惯每次跑完建图保存一份pbstream然后用cartographer_pbstream做离线分析。官方提供的工具链能帮你查看轨迹和约束的分布你会发现很多只靠 RViz 看不出细节的问题。7. 从“会用”到“读懂”的学习路线7.1 四个阶段建议以大家熟悉的 ROS 生态来划分我把学习 Cartographer 分成四个阶段能用能编译、能跑官方 demo、能换自己的 bag 包建出像样的地图。会调掌握.lua参数与真实传感器的对应关系遇到地图问题知道往哪个方向试。能读能从节点入口找到数据流理清局部 SLAM、全局 SLAM 的边界和交互。能改能自定义回环搜索策略、换匹配核、接入新的传感器类型或者移植到非 ROS 系统。大多数网上找资料的人会卡在“会调”到“能读”之间因为不读源码就只能猜参数而读源码又不知道从哪儿下手。后面几篇我会直接带着大家过核心源码订正这条跳板。7.2 源码结构地图先给个整体地图方便你有空自己翻。cartographer/common时间、配置、数学工具、线程池等底层组件。cartographer/sensor激光点云、IMU、里程计、Landmark 等数据结构的定义与采集逻辑。cartographer/mapping栅格图、子图、位姿图、轨迹构建器的核心实现。cartographer_rosROS 适配层包括节点入口、话题订阅、TF 发布、栅格地图输出。个人建议先读cartographer_ros/node.cc它是整个系统的入口话题回调都在这里注册接着读cartographer/mapping/internal/2d/pose_graph_2d.cc这里会看到位姿图维护的精髓。读完这两个文件你对 Cartographer 的理解会直接从“用了半年”跳升到“真正懂了一点”。第一篇先到这儿。当年我第一次编译 Cartographer 的时候也被各种依赖折腾到怀疑人生但跑通 demo 并亲眼看到回环把偏掉的地图拉回来的瞬间确实觉得前面所有折腾都值了。这套系列后续会慢慢展开局部匹配、回环搜索、参数调优和代码解析建议你先把环境搭好、demo 跑通后面几篇就能边看边练。
返回列表