1. 为什么要专门做“纯定位”:建图模式根本不想让你长期跑
1.1 建图模式下定位的隐性问题
很多朋友第一次接触 Cartographer 的纯定位模式,都是因为同一个困惑:明明我在建图模式下也能看到机器人位置在变化,雷达点云能对上,为什么还要单独搞一个“纯定位”出来?
这个问题的答案,藏在 Cartographer 内部的两条工作线上。建图模式下,前端(Local SLAM)负责把每一帧点云跟当前子图匹配,输出高频位姿;后端(Global SLAM)负责在后台做闭环检测和全局优化,不断修正子图之间的相对位姿。后端一跑全局优化,地图就会跟着动,轨迹也会被修正。这是建图模式的正常工作方式,但放在“长期部署”场景里,问题就来了。
首先是地图会持续膨胀。机器人每走一段没去过的地方,前端就会创建新子图,后端不断积累新的约束。跑一天仓储环境,内存占用轻松涨到几个GB。其次是定位结果会被“未来”影响。后端优化是一批约束一起算的,上一秒发布的位姿,下一秒可能被后端整体调整掉,这在导航系统看来就是“定位突然跳了一下”。对导航控制来说,位姿抖动比位姿有固定偏差更致命。
更麻烦的是,建图模式下没有“地图不可变”这个概念。机器人长时间运行,雷达对同一个位置反复扫描,如果遇到玻璃墙、反光地砖这种退化场景,前端匹配可能把新子图钉在错误位置上,而后端会把这个错误当成真实约束加入优化图,最后把整个地图拉变形。这个过程的可怕之处在于:它不是一次性失败,而是随着运行时长慢慢腐化,等发现时地图已经没法用了。
1.2 纯定位模式的本质:把“建图”和“定位”拆成两件事
Cartographer 的纯定位模式,做的事情可以用一句话概括:加载一份已经建好的、不再变化的 3D 点云地图(pbstream),新启动的轨迹只做“匹配定位”,不往全局地图里塞新内容。
具体到代码层面,当你用load_state_filename参数加载 pbstream 时,Cartographer 会把文件里的所有轨迹标记为 frozen(冻结)。冻结轨迹意味着:这些轨迹对应的子图是只读的,不参与任何地图更新,但它们在位姿图优化中仍然扮演锚点角色。新轨迹进入系统后,local SLAM 照常做 scan-to-submap 匹配,产生高频位姿;global SLAM 后台则会把新轨迹的子图和冻结轨迹的子图做闭环匹配,用这些约束把新轨迹“钉”在已有地图上。
这个过程依然触发了后端优化,但优化的核心不是修正地图本身,而是修正“新轨迹在旧地图里的相对位姿”。所以地图是死的,位姿是活的。这就是纯定位模式和数据回放做定位的本质区别——数据回放只是把保存的轨迹发出来,纯定位是实时匹配、实时输出。
这也是为什么纯定位模式必须同时准备两样东西:一份高质量的 pbstream 地图,以及一套跟建图时完全一致(至少高度一致)的传感器配置和 TF 树。地图质量决定定位精度上限,配置一致性决定你能否稳定复现这个精度。
1.3 判断你的项目是否真的需要纯定位
不是所有场景都需要纯定位。我的判断标准很简单:
- 机器人只在已建图区域内运行,地图不需要变化,就需要纯定位。
- 机器人会长期、反复探索新区域,且地图需要持续更新,就别用纯定位,老老实实做在线建图加定期保存地图。
- 如果只是做技术验证,跑一段 bag 看效果,也不必大动干戈,直接在建图模式下跑,关闭地图更新即可。
用 3D 雷达(机械式多线雷达或固态雷达)的场合,纯定位模式尤其有价值。因为 3D 点云数据量是 2D 的几十倍,后端优化压力非常大,在线长期建图对工控机的 CPU 和内存都是折磨。把建图和定位拆开,建图阶段可以在高性能机器上离线做,定位阶段用一台普通工控机就够了。
2. 纯定位模式的软件版本与地图数据基础
2.1 版本选择:为什么我推荐确认这几个 commit
Cartographer 的版本演进里,纯定位相关的改动比大多数人想象得多。2020 年之前的版本,加载 pbstream 后新轨迹的初始位姿经常跳变,要手动设置一个比较接近的初始值才能收敛。后来官方做了一次比较大的重构,把MapBuilderInterface的轨迹管理逻辑理顺了,加载地图后的重定位稳定性明显改善。
我的建议是:不要直接用最新 master,也不必用最早的老版本。选择你 ROS 发行版官方仓库里带的版本(比如 ROS Noetic 的 1.0.0),或者选择 2021 年之后、commit 相对稳定的某个 release 分支。因为 Cartographer 的代码质量很高,但上游更新频率不高,社区维护的 patch 往往更贴近实际部署场景。
纯定位模式相关的核心接口主要涉及这几块:
MapBuilder::AddTrajectoryBuilder:新建轨迹时根据参数决定是否冻结已有轨迹;MapBuilder::LoadState:加载 pbstream,并设置严格一致性检查;TrajectoryBuilderInterface:local SLAM 前端的接口定义。
如果你要二次开发(比如把纯定位模式嵌入自己的调度系统),这几个接口是绕不开的。建议在动手前先读一遍map_builder.cc里LoadState的实现,理解 frozen 轨迹在 PoseGraph 里是怎么被处理的,这对后续排查定位跳变问题非常有帮助。
2.2 pbstream 不只是点云:地图文件里到底装了什么
很多人误以为 pbstream 就是一张点云图,其实点云只是它的表象。pbstream 的完整内容可以拆成这样几层:
| 数据层 | 内容 | 在纯定位中的作用 |
|---|---|---|
| 子图集合 | 每个子图的点云、匹配体素栅格单元 | 提供 local 匹配的基准 |
| 轨迹数据 | 每条轨迹的节点位姿、子图位姿 | 提供全局坐标基准 |
| 约束关系 | 子图间、节点与子图间的匹配约束 | 保证地图内部刚性 |
| 传感器标定信息 | 激光与 IMU 外参、重力方向参考 | 保证新轨迹能对齐 |
这意味着,你加载一份 pbstream 时,不只是“加载了一张地图”,而是加载了一个完整的位姿图状态。新轨迹的每一个节点,都要跟这个位姿图里的子图做匹配,形成新的约束。这也是纯定位模式比“ICP 配准点云”类方案更稳定的原因:它同时利用了局部几何特征和全局拓扑约束,而不是单纯做帧到模型的匹配。
我在实际项目里有一个体会:pbstream 的质量,决定纯定位能用多久。一份由长时间建图、包含大量闭环约束的地图,即使局部点云稀疏一点,定位也能靠约束撑住。反之一份只跑了五分钟、没有闭环的“快餐地图”,在定位时稍微走远一点就开始飘。
2.3 录制 bag 和建图的预处理建议
既然地图质量决定定位上限,那建图阶段的预处理就得认真对待。这里分享我的标准操作流程:
- 使用 rosbag 录制原始数据,不做任何在线滤波。录制内容包括点云话题、IMU 话题、里程计话题(如果有)。录制时确保雷达转速稳定、IMU 采样率不低于 200Hz(典型 3D 雷达搭配的 IMU 都是这个量级)。
- 离线建图优先。把 bag 放到性能足够的机器上跑建图,开
use_online_correlative_scan_matching(3D 模式下通常设为 false,2D 才常用)。建图过程不要急,走完所有需要覆盖的区域,关键拐角处放慢速度,让前端有足够的时间积累视差。 - 检查轨迹和闭环质量。建图完成后,在 Rviz 里打开轨迹和子图,重点看拐角、走廊尽头这些位置,有没有明显错层。如果地图有分层,定位大概率会在对应区域跳变。这时回到第 2 步重新建图,或者裁剪掉出问题的轨迹段再保存。
- 保存地图:
# 结束编号为0的轨迹 rosservice call /finish_trajectory 0 # 等待几秒,让后端处理完剩余约束 sleep 5 # 保存地图 rosservice call /write_state "{filename: '/home/user/maps/industrial_3d_map.pbstream', include_unfinished_submaps: false}"注意include_unfinished_submaps这个参数。如果设为 true,会把还没完成匹配的子图也写进地图,地图面积更大,但边缘位置约束不完整。定位模式下我不建议这么做,设 false 更干净。
3. 3D 纯定位的 launch 与 lua 配置逐项拆解
3.1 配置主体框架:加载 pbstream 和关闭“在线建图”
纯定位模式的 launch 文件,核心就是一个cartographer_node,加上一个cartographer_occupancy_grid_node(如果你要在 Rviz 里看 2D 栅格)。先看一个典型的 3D 纯定位 launch 骨架:
<launch> <node name="cartographer_node" pkg="cartographer_ros" type="cartographer_node" output="screen"> <param name="configuration_directory" value="$(find my_robot_config)/config" /> <param name="configuration_basename" value="my_robot_3d_localization.lua" /> <!-- 加载已有地图,这是纯定位模式的入口 --> <param name="load_state_filename" value="$(find my_robot_config)/maps/industrial_3d_map.pbstream" /> <!-- 3D雷达点云话题 --> <remap from="points2" to="/rslidar_points" /> <remap from="imu" to="/imu/data" /> <remap from="odom" to="/odom" /> </node> <node name="cartographer_occupancy_grid_node" pkg="cartographer_ros" type="cartographer_occupancy_grid_node" output="screen"> <remap from="map" to="/map" /> </node> <!-- TF 静态变换,如果激光雷达和 base_link 之间有固定外参 --> <node pkg="tf2_ros" type="static_transform_publisher" name="base_to_laser" args="0 0 0.5 0 0 0 base_link rslidar" /> </launch>这份配置的关键一点都不在 launch 文件本身,而在于load_state_filename。只要这个参数被设置了,Cartographer 启动时就会先加载 pbstream,然后以“已有地图+新轨迹”的方式运行——这就是纯定位模式。如果没设置这个参数,同样的 lua 配置会跑成一个在线建图节点,所以千万别搞混。
从 Cartographer 1.0 之后,官方并不要求单独设置pure_localization = true这样的参数。模式切换完全由“是否加载地图”决定。很多新手在网上看到老教程里写了一个pure_localization参数,找了半天找不到,其实就是这个原因。
3.2 3D 雷达传感器参数:点云话题、体素滤波和坐标粘贴
3D 纯定位模式下,num_point_clouds通常设为 1,表示只接收一个点云话题。这里有一个细节:Cartographer 的 3D 前端对点云有一个要求——点云必须是“去畸变”的。但 Cartographer 自身不去畸变,它依赖雷达驱动去畸变,或者依赖你离线把点云处理好。
如果你用的是速腾、禾赛这类国产雷达,驱动里通常自带运动畸变去除选项。定位模式下建议开启。别小看这个参数,畸变未去除的点云在机器人转弯时会明显“拖尾巴”,纯定位的 scan-to-submap 匹配会对这种系统性变形非常敏感,表现为定位偶尔跳一下又回来。
lua 配置里跟点云相关的核心参数:
options = { -- 必须与 launch 里 remap 对应 num_point_clouds = 1, -- 雷达坐标系 tracking_frame = "base_link", -- 发布频率,定位模式下可以降低,节省CPU pose_publish_period_sec = 0.005, trajectory_publish_period_sec = 0.03, }tracking_frame这个名字经常让人疑惑,它并不是“跟踪坐标系”,而是“位姿追踪坐标系”。在 3D 模式下,Cartographer 内部做 scan-to-submap 匹配时,会自动把点云从雷达坐标系变换到 tracking_frame(通常是 base_link)。如果你的雷达安装在 base_link 的正上方,且不做大角度旋转,直接设tracking_frame = "base_link"没问题;如果雷达和 base_link 之间有较大外参,需要提供静态 TF。
3.3 local SLAM 和 global SLAM 在定位模式下的角色差异
我见过很多人对纯定位模式的理解是:关闭建图,只跑匹配。这个理解只说对了一半。实际上 Cartographer 纯定位模式下,local SLAM 依然会创建子图——只不过这些新子图不会影响已有地图,它们只作为临时局部参考,用于生成高频位姿。
这么说吧:
- local SLAM 前端:依然接收点云,构建新的 local submap,对当前扫描帧和 local submap 做匹配,输出高频帧位姿。
- global SLAM 后端:不用来做“地图大规模优化”,而是专门做“新轨迹与冻结轨迹的约束”。当机器人走到已有地图的某个区域时,后端会尝试把新轨迹的节点与冻结的子图做匹配(scan-to-map),形成全局修正。
理解这个机制的价值在于:当你在 lua 里调global_sampling_ratio这个参数时,你调的不是“定位的准确性”,而是“新轨迹和旧地图之间做闭环检测的频率”。调高它,定位更稳,但 CPU 占用量上升;调低它,CPU 更省,但长走廊等区域容易累积漂移。
我常用的经验值是:3D 纯定位模式下,global_sampling_ratio设在 0.03 左右,能平衡 CPU 和稳定性。你可以跑一段 bag 试调,看 Rviz 里连接新旧轨迹的约束线是否连续。
3.4 常用调参清单
下面这个表格是 3D 纯定位模式最值得调的参数,按优先级排列:
| 参数 | 建议值 | 作用 |
|---|---|---|
use_imu | true | 3D 定位必须开,IMU 提供重力方向 |
use_odometry | true | 有轮式/视觉里程计就开,能大幅提升抗退化能力 |
global_sampling_ratio | 0.03 | 控制全局匹配频率 |
pose_publish_period_sec | 0.005 | 控制位姿发布频率,200Hz 对导航足够 |
submap_publish_period_sec | 0.3 | 只在 Rviz 里看效果时影响不大,可适当增大 |
num_subdivisions_per_laser_scan | 1 | 3D 点云一般不需要细分 |
min_range/max_range | 根据雷达型号 | 过滤过近/过远的点,一定要跟建图时一致 |
use_online_correlative_scan_matching | false | 3D 模式下通常关闭,省资源 |
关键提醒:min_range和max_range在定位模式里必须和建图时完全一致。如果建图时用了 0.5~100m 范围,定位时改成 0.2~80m,点云的几何分布变了,匹配结果会系统性偏移。这个坑我踩过一次,查了很久才发现是范围参数不一致导致的定位偏差。
4. 从零到定位跑通:完整命令行与流程
4.1 启动节点,加载三维地图
假设你已经准备好了industrial_3d_map.pbstream,完整的启动流程大概是这样的:
# 终端1:启动雷达驱动 roslaunch rslidar_sdk start.launch # 终端2:启动IMU驱动(如果需要) roslaunch my_robot imu.launch # 终端3:启动cartographer纯定位节点 roslaunch my_robot_config localization_3d.launch # 终端4:启动Rviz查看效果 rviz -d $(rospack find cartographer_ros)/configuration_files/demo_3d.rviz启动之后,Rviz 里应该能看到三样东西:一个 2D 的map话题(来自cartographer_occupancy_grid_node)、3D 点云子图(来自submap_list)、以及机器人的 track 轨迹。
如果你发现map话题是空的,大概率是cartographer_occupancy_grid_node没有正确订阅到子图数据。检查一下它的submap_list话题是否跟cartographer_node的输出了话题名一致。这个问题经常出现在改了 node 命名空间之后。
4.2 TF 树的建立:map、odom、base_link 和雷达坐标系的关系
纯定位模式下 TF 树长这样:
map -> odom -> base_link -> rslidarmap到odom:由 Cartographer 内部维护,表示“全局位姿修正的漂移量”。这个变换会在线更新。odom到base_link:如果provide_odom_frame = true,由 Cartographer 发布;否则由你的里程计节点发布。base_link到rslidar:静态变换,由你负责提供,通常在 launch 里写死。
有一个常见误区:很多新手试图自己给map -> odom发一个静态变换,这会把定位功能完全废掉。map -> odom必须由 Cartographer 发布,并且是动态的。如果你在 rqt_tf_tree 里看到多个节点都在广播这个变换,TF 会频繁切换,定位会表现为“机器人模型在 Rviz 里抖动”。
纯定位模式对 TF 时间的同步要求也很严。lookup_transform_timeout_sec默认 0.2 秒,如果你的 TF 树里某个环节延迟过高(比如 IMU 驱动和雷达驱动跑在不同机器上),Cartographer 会频繁打印Transform timed out警告,点云被丢弃。遇到这种情况,优先检查网络延迟,而不是盲目调大超时。
4.3 初始位姿:不发布会怎样,怎么发布
启动纯定位节点后,Cartographer 对新轨迹的初始位姿默认是(0,0,0)。如果你的机器人实际不在这个位置附近,local SLAM 的匹配收敛不到正确解上,定位会一直“飘着”甚至直接报Unchanged 100 scans这种重复匹配失败的警告。
解决方式是通过/initial_pose话题发布一个大概的初始位姿:
rostopic pub /initial_pose geometry_msgs/PoseWithCovarianceStamped " header: frame_id: 'map' pose: pose: position: x: 5.0 y: 3.0 z: 0.0 orientation: w: 1.0 covariance: [0.25, 0, 0, 0, 0, 0, 0, 0.25, 0, 0, 0, 0, 0, 0, 0.25, 0, 0, 0, 0, 0, 0, 0.01, 0, 0, 0, 0, 0, 0, 0.01, 0, 0, 0, 0, 0, 0, 0.01] " -1这个位姿可以从 Rviz 的2D Pose Estimate工具直接获取,跟 AMCL 的初始定位类似。区别在于 Cartographer 的/initial_pose只在新轨迹启动后的窗口期内监听,如果你启动节点超过了十几秒才发布,可能不会生效。稳妥的做法是在 launch 文件里用roslaunch的<arg>传入初始坐标,让节点启动瞬间就设置好。
初始位姿的精度不需要太高,纯定位模式通常能在 1~2 秒内通过 scan-to-map 匹配收敛到位姿差半米以内的状态。但如果初始位姿偏离超过 3 米,收敛就非常困难,建议手动校准后再启动。
4.4 验证定位是否正常
判断纯定位是否真的“定位上了”,不要只看 Rviz 里点云对上没。我推荐按这四步验证:
- 看
/tracked_pose话题:如果位姿以 100Hz 以上稳定发布,且没有长时间中断,前端匹配是活的。 - 看退化检测:Cartographer 输出日志里的
Scan match residual,这个值突然变大说明当前帧和子图匹配不上,可能是走到了地图没覆盖的区域。 - 推一推机器人(或用手转一圈雷达):看 Rviz 里 base_link 和点云的相对关系是否跟着动。如果机器人动了但地图里的位姿不动,说明 TF 树有问题。
- 折返跑:让机器人沿原路折返,回起点时看位姿偏差。如果偏差超过 10cm,说明定位在后端约束上还有问题,需要回看全局匹配频率。
5. 3D 定位实测中的问题排查链路(踩坑实录)
5.1 点云坐标粘贴导致的重影和“假定位”
这是我做第一个 3D 纯定位项目时踩的坑。现象是:启动定位节点后,Rviz 里点云看起来和地图高度重合,但机器人实际前进时,位姿更新明显滞后,停下来后位姿还会倒退一段。
排查链路是这样的:
- 先看
/tracked_pose时间戳和点云时间戳,发现点云时间戳比位姿时间戳旧了大约 0.5 秒,疑点落在雷达驱动。 - 再看 TF 树,
base_link -> rslidar的静态变换没问题。 - 最后直接打印点云的
received time,发现雷达驱动把时间戳打成了“雷达内部时钟”,和主机上的 ROS 时钟差了老远。
问题根源就是:雷达驱动(或你的采集脚本)给点云打了错误的header.stamp。Cartographer 内部会用位姿时间戳索引点云帧,时间戳错位直接导致“地图坐标粘贴在错误的位姿上”,表现就是重影和假定位。
这个问题的修复方式很简单:在雷达驱动里校准时间戳,保证点云时间戳是主机时钟。但如果你的系统里有多台机器跨网络运行,记得用 PTP 或 NTP 做时钟同步。Cartographer 对时间同步的容忍度极低,这是 3D SLAM 系统的通病,不是 Cartographer 独有的要求。
5.2 运动畸变和去畸变配置差异
有过一个项目,定位模型在静态环境下精度极高,但机器人一跑起来就频繁“找不回去”,误差在 20~30 厘米之间来回跳。排查到后面发现:建图时用的 bag 是离线录制的,离线建图时雷达驱动开了去畸变;但定位时是实时跑,雷达驱动的去畸变选项被某次参数整理时不小心关掉了。
这个问题的本质是:Cartographer 的 3D 前端假设点云帧内没有运动畸变(或者说畸变已经被驱动去除),一旦假设被打破,匹配就会产生系统误差。机械式雷达每帧扫描需要 50~100ms,这期间机器人运动产生的畸变不可忽略。
排查方法很简单:把实时定位时收到的点云 dump 下来,和建图时同一场景的点云对比,看边缘有没有明显的“拖尾”。如果拖尾明显,检查雷达驱动的去畸变开关。
5.3 回环与退化环境:长走廊、玻璃墙、动态遮挡
3D 纯定位在开阔环境(停车场、厂房、货架区)表现很好,但在几类退化场景下会出问题:
- 长走廊:沿走廊方向没有足够几何约束,local SLAM 会沿走廊方向漂移,但垂直方向约束很强,所以定位表现为“方向正确但里程越走越偏”。
- 玻璃墙和镜面:雷达点打到玻璃上要么丢失要么产生多径反射,地图上会形成一大片“假点”。这些假点不参加匹配还好,一旦匹配到了,会造成局部错位。
- 动态人车:如果你的定位环境里经常有叉车、行人在雷达附近经过,它们会把局部点云“污染”。Cartographer 没有内置动态物体滤除,完全靠匹配的鲁棒性硬扛。
这些退化场景的解法不是一个参数能搞定的,我的建议是分层处理:
- 硬件层:在多线雷达之外,加一个短距 2D 雷达辅助定位。Cartographer 支持同时接收 3D 点云和 2D scan,两路数据互补。长走廊场景下,2D 雷达在底部提供的扫射约束往往比 3D 点云更稳定(前提是 2D 雷达的地图是可靠的)。
- 配置层:
max_range不要设太大,把玻璃墙外那些远距离噪声点挡在外面。同时适当提高global_sampling_ratio,让后端在退化区域更频繁地“拉一把”新轨迹。 - 软件层:在纯定位之外跑一个轻量的点云滤波节点(如通过半径离群点滤波剔除稀疏噪点),输出滤波后的点云给 Cartographer。
5.4 定位丢失后的自恢复与重定位触发
即使配置再好,定位也不是永远不丢。目前 Cartographer 纯定位模式没有内置的“丢失自恢复”机制——它没有一个像 AMCL 那样的主动粒子重定位阶段。定位一旦发散,基本只能重启新轨迹。
但你可以把“重启新轨迹”做成一个自动化流程。具体做法:在系统里监听 Cartographer 的匹配残差和轨迹节点的时间戳新鲜度,当连续 N 帧匹配残差超阈值,或者轨迹节点的更新时间超过某个阈值,就通过 service 调用新建一条轨迹来重新初始化:
rosservice call /start_trajectory "options: ..."这里有个经验:在调用start_trajectory前,先用你上一次有效位姿(或导航系统给出的大致位姿)做initial_pose,成功率会高很多。如果完全盲发,新轨迹很可能也匹配不上,因为 3D 点云的匹配窗口有限,不像 2D 那样容易碰运气。
在新轨迹成功匹配并稳定几秒后,再调用finish_trajectory结束旧轨迹。实际项目中,这个自动恢复逻辑挽救了无数次“深夜机器人卡死”事故。
6. 长期运行中的稳定性与性能调优
6.1 计算资源预算:local SLAM 实时性与多线程消耗
3D 纯定位对 CPU 的消耗比很多人想象的高。实测下来,一个 32 线机械雷达、10Hz 点云输入、开了 IMU 融合的定位任务,Cartographer 在工控机上大约消耗 2~3 个完整核。如果你的系统还要跑导航、感知、调度,CPU 预算要提前规划好。
Cartographer 的线程模型是:
map_builder的后端优化跑在一个独立线程里;- local SLAM 匹配跑在另一个线程;
- 数据分发和订阅占用一个线程;
- occupancy_grid_node 还要额外线程。
优化的思路是:定位模式下,降低不必要的发布频率。pose_publish_period_sec从默认 5ms 改到 10ms 对定位几乎没有影响,但能减少下游节点的压力。trajectory_publish_period_sec从 30ms 改到 100ms,Rviz 里的轨迹更新频率降低,但不影响定位精度。submap_publish_period_sec从 0.3 改到 1.0,能省不少可视化带宽。
6.2 内存和 pbstream 的裁剪策略
长期运行下,内存占用主要来自两部分:新轨迹的子图数据和位姿图的约束数据。如果你只在固定区域做来回运动,这些数据不会无限增长,因为新轨迹节点在同一个局部区域内会不断被合并进已有约束,不会无限膨胀。
但如果机器人会在整个园区不停穿梭,新轨迹覆盖的区域越来越大,内存会持续上涨。一个可行的策略是:定期(比如每 2 小时)调用finish_trajectory和start_trajectory,把当前轨迹截断,重新开一条新轨迹。这不会影响定位,因为新轨迹会自动跟冻结地图匹配。
另外,pbstream 文件本身也可以裁剪。如果建图时不小心把路线尽头的一条死路也建进地图,那些子图对定位没任何帮助,却会让LoadState时间变长。我建议有空的时候用 Cartographer 自带的cartographer_pbstream工具或写个小脚本,把 pbstream 里的轨迹按空间范围裁剪掉,减少加载时间。
6.3 多雷达融合和低退化风险的传感器配置思路
如果你的预算允许,我非常推荐在 3D 雷达之外加一个 2D 激光。Cartographer 的num_laser_scans和num_point_clouds可以同时大于 0,并且 2D 和 3D 的匹配结果会共同约束同一帧位姿。
实际做下来,2D + 3D 的双传感器融合,在长走廊场景的定位精度提升非常明显。原因是 2D 雷达在贴近地面、扫描范围较小,几何约束更“硬”,不容易被走廊方向拉松。当然代价是配置复杂度上升:你需要额外维护一套 2D 雷达的外参标定,并且建图时也要同时录制 2D 雷达的数据。
对于固态雷达这类视场角受限的传感器,纯定位模式还有另一个注意点:尽量安装时让它朝向定位区域内信息量最丰富的方向。3D 雷达的视场角受限意味着某个方向可能长期没有点云,如果那个方向恰好是机器人导航的主要方向,定位会在接近前进方向时缺少约束,导致“越开越偏”。
7. 写在最后的长期运维心得
聊了这么多配置和排错,最后分享一点我从项目维护角度总结的心得。
纯定位模式最容易被忽视的是“地图生命周期管理”。地图不是建一次就永远不变的。厂房改造、货架位置调整、墙面装饰变化,都会让旧地图和现实环境逐渐脱节。我见过不少项目在部署初期定位精度很好,跑了半年后开始频繁跳变,最后排查发现是环境变了,地图却没更新。
我的做法是:在系统里加一个简单的“环境漂移监控”模块。定期统计定位过程中 scan-to-map 匹配的残差分布,如果长期运行中残差均值缓慢上升,就提醒运维人员重新建图或对局部环境重新扫描更新。这个模块不需要多复杂,一个 Python 脚本订阅/tracked_pose和点云话题就能算出来,但对长期稳定性非常有用。
另外,纯定位模式的日志一定要保留。Cartographer 的日志信息量很大,很多问题通过日志就能一眼定位——比如Truncated、Unchanged、Transform timed out,每个关键词都是一个故障类型的指纹。运维团队拿到日志就能判断是传感器问题、驱动问题还是配置问题,不需要每次都远程拉实时数据。
希望这篇分享能帮你少走一些弯路。做 3D 纯定位这件事本身不难,难的是把每一个细节都做扎实。地图质量、传感器标定、参数一致性、时间同步,这四件事做好了,定位自然就稳了。