
干移动机器人定位这一行的基本都绕不开一个问题手里没有地图机器人就不知道自己在哪但没有定位建出来的图又很难精确。激光雷达定位之所以吃香就是因为它直接拿点云和已知地图做“参照系比对”不依赖GPS那样受环境遮挡限制也不像纯视觉那样对光照敏感。这篇文章就把“基于地图的激光雷达定位算法”这件事从原理到开源实现完整捋一遍重点讲清楚2D和3D场景下各有哪些主流方案、各自适合什么场景、怎么选型、怎么上手跑通顺便把我踩过的坑也抖出来。如果你是在做扫地机、仓储AGV、园区巡检车或者刚开始接触自动驾驶定位模块这篇内容应该能帮你省不少折腾时间。文章不会堆大段公式尽量用工程视角讲“为什么这么设计”和“实际怎么用”。1. 基于地图的激光雷达定位它到底在解决什么问题1.1 地图是坐标基准不是数据包袱很多人一开始会把“建图”和“定位”混为一谈。其实基于地图的定位有个大前提地图是提前建好的、相对固定的一份环境模型。定位要做的事情是实时拿到一帧激光雷达点云后想办法算出“当前这帧点云在地图坐标系下的哪个位置、什么朝向”。这个思路很像人走进一栋商场你手里有一张楼层平面图地图眼睛看到周围的门店、柱子、电梯口实时感知然后脑子里不断比对这些信息就能知道自己站在哪里。机器人的激光雷达定位也是同样的逻辑只是它用点云匹配来替代人眼。为什么要强调“地图是坐标基准”因为在基于地图的定位框架里所有定位结果最终都表达成“map坐标系下的x、y、yaw2D或x、y、z、roll、pitch、yaw3D”。后续导航、规划、控制都依赖这个位姿。如果地图本身建歪了、坐标系不统一那定位再准也没有意义。这也是为什么很多项目在定位阶段频繁出问题根因往往是地图质量不行。1.2 三种主流地图表示栅格地图、点云地图、特征地图基于地图的定位除了算法不同地图的“形态”也很关键。常见的地图表示有三类我这里直接写成一个对比表方便你按需求选地图类型存的是什么典型格式/工具适合场景优点缺点2D栅格地图每个格子的占据概率分free/occupied/unknown.pgm.yamlROS map_serverGmapping/Cartographer室内扫地机、AGV存储小、导航直接可用、定位收敛快丢失高度信息不适合复杂3D环境3D点云地图稠密/稀疏点云坐标与反射强度.pcd/.plyPCLLOAM/LIO-SAM室外园区、矿区、自动驾驶信息丰富可直接配准定位文件大需要高性能算力特征地图几何特征平面、角点、圆柱或语义特征车道线、杆子自定义格式常用于高精地图固定线路、半结构化道路匹配稳定、抗动态干扰强特征提取工程量大通用性一般还有一个高频出现的是“八叉树地图OctoMap”它在三维栅格基础上用八叉树做内存压缩每个节点保持占据概率。常用于3D导航和动态避障也能用于定位但定位精度通常不如直接配准点云地图所以更多是“导航地图”而非“定位地图”。1.3 从数学本质上理解定位状态估计与最大似然抛开复杂公式基于地图的定位在数学上就是一个“状态估计”问题。机器人有一个状态位姿有一堆传感器观测雷达帧还有一个先验地图。定位算法要做的是在已知地图的前提下找到最可能产生当前观测的那个位姿。用大白话说就是“我站在哪个位置才能让雷达扫出来的东西和地图长得最像”。实现“找这个位置”的路线大致分两派第一派是滤波类方法比如粒子滤波、卡尔曼滤波。它维护一个对位姿的“置信分布”每一帧观测来了就更新分布。AMCL就是典型。第二派是配准/优化类方法比如ICP、NDT、scan-to-map。它直接把当前帧点云往地图上做刚体变换配准通过迭代找到误差最小的位姿。Cartographer的实时位姿估计、NDT定位都属于这一类。这两派没有绝对优劣。滤波类对初始位姿容忍度更高、更适合全局定位配准类精度高、计算高效但通常需要较好的初始值。工程上常常两者结合先用滤波或描述子拿到粗位姿再用配准精匹配。2. 2D栅格地图定位AMCL与Cartographer的开源实现2.1 AMCL教科书级的粒子滤波定位如果你做过ROS机器人大概率听过AMCLAdaptive Monte Carlo Localization。它是ROS navigation栈里内置的自适应蒙特卡洛定位方案也是2D激光雷达定位的入门首选。AMCL的核心思路是粒子滤波。简单说代码里维护一堆“粒子”每个粒子代表一个可能的机器人位姿x, y, yaw。初始阶段粒子会在地图里撒开可能几百到几千个随着雷达观测不断进来算法会给每个粒子打分——谁的位姿更符合当前激光扫描谁权重就高然后根据权重重采样差的粒子被淘汰好的粒子周围长出更多新粒子最后用所有粒子的加权平均或聚类作为当前估计位姿。ROS里调用AMCL非常方便roslaunch amcl amcl.launch如果走纯命令行也可以先启动地图服务rosrun map_server map_server my_map.yaml然后设置参数initial_pose_x、initial_pose_y、initial_pose_a把初始位姿给进去或者在Rviz里用“2D Pose Estimate”按钮手工指定。实操中AMCL有几个值得注意的调参点min_particles/max_particles粒子数太少容易发散太多浪费CPU。室内小场景建议最低200、最高2000大场景再往上加。update_min_d和update_min_a控制机器人至少移动多少距离/角度才更新粒子。如果设成0原地不动时也会因为传感器噪声反复重采样容易把粒子“抽没了”导致定位发散。laser_z_hit、laser_z_rand等噪声模型参数室内环境用默认值通常没问题但如果激光雷达比较便宜、噪声大需要适当调高laser_z_rand的权重。我试过在校园建筑里用2D雷达AMCL跑巡检车粒子数设成500左右在没有大改动的走廊里能稳定跑一整天。但一旦遇到长走廊这种退化场景横向有特征、纵向没有AMCL的粒子会沿走廊方向散开定位会飘这时候就需要靠里程计或者额外约束来救。2.2 Scan-to-Map配准Cartographer的实时位姿估计Cartographer是Google开源的一套SLAM库既能建图也能定位。它内部的核心是“扫描到子图”scan-to-submap匹配属于配准派。Cartographer用了一个叫Ceres Solver的非线性优化库每来一帧激光就把当前帧与最近的子图submap做最小二乘匹配同时考虑里程计、IMU的预测作为先验约束。这种“先预测、再匹配、后优化”的套路让它在2D激光雷达上能达到很高的精度和实时性。Cartographer也自带定位模式cartographer_ros里的occupancy_grid_2d方式但说实话大多数人用Cartographer更多是为了建图然后拿栅格地图给AMCL或别的前端做定位。如果你想直接用Cartographer做“实时定位”需要固定地图并启动它的localization模式配置会稍复杂文档里也有对应示例。从选型角度看如果项目只需要2D定位我建议建图用Cartographer定位用AMCL这套组合在ROS社区里最成熟资料也多。Cartographer在室内小场景建图效果很能打尤其是回环检测能纠正不少漂移。2.3 2D定位的工程关键初始位姿和里程计质量2D激光雷达定位看似简单但在工程落地时问题往往不是算法不行而是输入不行。有两个输入最容易被低估第一个是里程计/运动预测。AMCL虽然主要靠激光匹配定位但它需要运动模型来“预测”粒子的下一步位置。如果底盘轮子打滑严重、或者没有准确的里程计粒子预测会偏很多激光匹配都救不回来。这种情况可以考虑加IMU做航迹推算或者换成全向底盘。第二个是初始位姿。AMCL是局部定位算法不是全局定位算法。你把机器人从A点搬到B点如果不重新给初始位姿它基本无法自己找回位置。我见过不少新手在这上面卡了一下午启动AMCL后粒子到处飞其实只是没告诉机器人“你在哪”。3. 3D点云地图定位ICP/NDT到LOAM系的开源实现3.1 为什么NDT比ICP更适合激光雷达定位到了3D场景最经典的定位方法就是点云配准。ICPIterative Closest Point大家应该都听过它的思路是对于当前帧的每个点在地图点云里找最近邻点然后计算一个刚体变换让两组点尽量重合不断迭代。ICP在小范围、初值好的情况下精度很高但有两个明显短板一是慢每一帧都要做最近邻搜索二是容易陷入局部最优初值不好就会配到错误位置尤其是在特征稀疏的环境里。NDTNormal Distributions Transform正态分布变换换了一种思路它不直接比较点对点而是把空间划分成体素格子每个格子用高斯分布均值协方差来建模点云分布。匹配的时候当前帧点落在哪个格子就计算这个点与该格子分布之间的“概率距离”再通过优化求位姿。因为NDT把离散点云变成了可以求导的连续分布所以计算效率高对初值的鲁棒性也比ICP好一些是3D激光雷达定位里更实用的选择。PCL里已经实现了NDTlibpointmatcher里也有直接调用即可。实际项目里常见做法是拿NDT做“scan-to-map”的精匹配并配合IMU/里程计做帧间预测。比如开源的ndt_scan_matcher包就是专门用NDT做3D激光雷达定位的ROS节点加载一张.pcd点云地图后就能实时输出map→base_link的位姿。3.2 LOAM系方案建图与定位一起解决LOAMLiDAR Odometry and Mapping是激光雷达里程计和建图的经典框架后来衍生出A-LOAM、LEGO-LOAM、LIO-SAM、FAST-LIO等一堆变体。很多人首先会拿这些框架来“建图”但其实它们也包含了定位能力只是“定位”方式各有区别。LOAM系的核心思路是“特征提取帧间配准图优化”。它先从原始点云里提取角点和平面点然后用这些特征做scan-to-scan的里程计估计再和局部地图做scan-to-map匹配减小累积漂移。如果你已经有一张地图输出也可以把它的前端当作一个“基于特征的实时定位器”。不同变体的选型我做一个简单对比框架传感器需求特点适合场景A-LOAM3D雷达带噪声较小时效果才好代码结构清晰学习用最合适入门学习、快速验证LEGO-LOAM3D雷达IMU可选在地面分割后分别优化计算量更小地面车辆、低算力平台LIO-SAM3D雷达IMUGPS可选紧耦合IMU回环检测建图质量高园区、城区、无人机FAST-LIO3D雷达IMU紧耦合迭代卡尔曼滤波计算效率极高无人机、高速运动、实时性要求高的场景LIO-SAM和FAST-LIO都属于“紧耦合”方案把雷达点云和IMU数据放进同一个状态估计器里IMU给运动预测雷达点云做修正。这样即使雷达帧率低、或者运动很快导致点云畸变大定位也基本能稳住。我在移动机器人上跑过FAST-LIO在快速旋转和急加速的场景下位姿输出依然很平滑这是纯NDT或者纯LOAM比不了的。需要注意这些SLAM框架默认做的是“同时建图和定位”不完全是“基于给定地图的定位”。如果你想拿它们做纯定位地图固定、只求位姿通常需要改造比如LIO-SAM可以加载预建地图进行定位或者干脆把建好的地图转成点云地图用NDT配准来做定位部分。3.3 全局重定位Scan Context这类描述子怎么用前面说的NDT、LOAM都依赖一个不错的初始位姿。但现实里机器人可能突然被搬动、开机地点不确定、或者中途定位丢了这时候就需要“全局重定位”——让机器人不看GPS直接根据当前一帧点云从整张地图里猜出自己大概在哪。最经典的开源方案之一叫Scan Context。它的做法很直观把一帧3D点云按“极坐标”方式划分成扇区和环每个区域统计一些特征高度、密度等编码成一个“指纹”描述子。当需要重定位时把当前帧的指纹和地图里所有关键帧的指纹比对找到最相似的几个候选再用ICP/NDT精配准确认。类似的还有基于直方图、基于几何描述子的方法但Scan Context因为实现简单、效率高一直是社区里很受欢迎的选择。工程上我比较推荐做“多级定位”架构平时用NDT或LIO-SAM做实时跟踪维护当前位姿一旦发现匹配分数低、协方差变大就触发Scan Context全局检索找回一个粗位姿再重新初始化NDT。这套组合能显著提升系统的鲁棒性。4. 从建图到定位的完整实操流程一套可参考的复现步骤4.1 建图阶段路线规划、标定与数据采集基于地图的定位前提是先有一张高质量地图。地图怎么建才能让定位好用我总结了一套基本流程。第一步是传感器准备和标定。2D方案至少需要2D激光雷达和轮式里程计3D方案需要3D激光雷达和IMU。雷达与IMU之间的外参必须标定不然紧耦合SLAM会直接出现明显的漂移。雷达的内参比如旋转速度、角度偏移在出厂校准后一般不用动但如果你用的是拼接式雷达或者固态雷达驱动里的时间同步和去畸变设置一定要确认。第二步是设计建图路线。建图不是随便推着走一圈就行要保证场景特征能被充分观测。重点包括走廊两端都走到便于形成闭环房间门口回字形绕一下让雷达看到室内结构不要长时间停在原地避免点云累积误差“糊”在一起。第三步是录制数据包。用rosbag记录雷达话题、IMU话题、里程计话题和必要的时间同步信息。录制时最好带上GPS室外可选项后面建图优化时可以做全局校正。第四步是离线建图。推荐用LIO-SAM或FAST-LIO这类紧耦合SLAM离线跑一遍bag建图质量通常比在线实时建图更稳定。建完之后保存地图2D存成.pgm .yaml3D存成.pcd或.ply。我在实际项目里遇到过一个问题地图看起来建得很好但定位始终对不齐。后来发现是建图时激光雷达驱动里的frame_id写错了导致点云被错误地变换到世界坐标系。所以建图前一定要反复确认TF树map → odom → base_link → laser_link这条链路必须通且方向正确。4.2 定位阶段加载地图、给定初始位姿、输出位姿地图建好之后定位阶段相对简单但有几个环节很容易忽略。以3D NDT定位为例典型流程是启动点云地图加载节点把.pcd地图发布成/map_cloud话题。启动NDT配准节点订阅实时雷达点云/velodyne_points或/rslidar_points并通过tf监听IMU/里程计预测输出map→base_link位姿。在Rviz里设置初始位姿或者写死在配置里。观察配准分数如果分数稳定在阈值以上说明跟踪正常。如果是2D AMCL定位流程更简单roslaunch amcl amcl.launch但注意仅仅启动AMCL还不够你必须先设置初始位姿。很多人在Rviz里忘了点“2D Pose Estimate”导致粒子云一直乱飞。还有一个常见坑是地图坐标系和雷达坐标系不匹配启动后即便有初始位姿点云和地图照样“重叠不上”。定位输出的位姿一般通过TF发布最核心的是map→odom变换和odom→base_link变换。AMCL会发布map→odom里程计发布odom→base_link最终组合起来就是map→base_link。如果你自己写定位节点最好也用这套TF结构避免下游导航模块习惯性依赖标准坐标系。4.3 精度怎么评估用evo工具算ATE和RPE定位做得好不好不能只看Rviz里“看起来差不多”要有量化指标。最常见的是ATE绝对轨迹误差和RPE相对位姿误差。你可以用evo这个开源Python工具几行命令就能对比两条轨迹的误差pip install evo evo_ape tum groundtruth.txt estimated.txt -a evo_rpe tum groundtruth.txt estimated.txt -r trans_part这里groundtruth.txt来自高精度定位系统如RTK、动捕系统estimated.txt来自你的激光雷达定位模块。-a表示先做整体对齐再算误差这样能剥离掉坐标系平移/旋转的差异更公平地反映定位精度本身。如果你没有高精度真值退而求其次可以用SLAM回环后的位姿当“参考轨迹”但要注意这只能做相对评估不能等同于真实精度。在评估时还要关注误差的分布是偶尔跳一下但整体准还是一直缓慢漂移前者通常是某个传感器帧出了噪声后者更可能是地图或外参问题。这两类问题排查方向完全不同。5. 激光雷达定位常见问题排查与工程避坑5.1 定位漂移和跳变先查输入再查算法定位漂移是最常见的故障表现是机器人动一会儿后点云和地图错位越来越大。排查顺序我建议“从下往上”先看雷达点云本身是否干净有没有运动畸变、丢帧、时间戳乱跳。再看TF和里程计轮式里程计是否标定过、IMU是否长时间没有收敛、外参对不对。最后再怀疑算法参数匹配窗口是否太小、优化迭代次数是否不足。有一个真实案例项目里FAST-LIO定位输出在高速转弯时突然跳了一大截排查后发现是IMU数据频率设置错误驱动实际输出200Hz配置里写成了100Hz导致帧间积分差了一倍。所以IMU话题的频率、时间戳稳定性一定要实测确认不能只看驱动默认值。5.2 初始位姿给错了定位一直发散怎么办初始位姿不对AMCL粒子会散开找不到位置NDT则可能收敛到局部最优解。处理办法有几个界面重新指定Rviz里重新点“2D Pose Estimate”。全局定位兜底如果地图不是特别大可以把AMCL粒子数临时调大并设置initial_pose为地图中心让粒子快速炸开搜索。描述子重定位3D场景下用Scan Context先做一次全局定位再把结果当成初始位姿交给NDT。需要注意AMCL的全局搜索能力有限地图太大或场景对称性太强比如多个一模一样的房间粒子再多也可能迷糊。这种环境最好人为给一个大致区域或者布置二维码、反光柱等人工特征辅助初始定位。5.3 地图和当前环境对不上定位效果直线下降环境是会变的。搬了新货架、门被打开、墙上贴了大海报对2D雷达来说都是“新信息”。如果地图太旧定位就会在中途突然偏移。工程上的应对手段定期重扫地图尤其是场景大改之后。做地图更新机制如果定位系统发现当前帧与地图的匹配残差持续偏高可以采集当前帧点云融合进旧地图的更新通道。这个做起来复杂但长期项目里很值得投入。动态物体过滤在点云配准之前先剔除疑似动态物体点比如高度异常的物体、距离突变区域能有效减少干扰。5.4 退化场景怎么救长走廊、隧道、空旷广场长走廊是激光雷达定位的“天敌”雷达在这个方向上看不到有效特征配准沿走廊方向会“滑移”。隧道、地下车库、大型厂房也有类似问题。对策分三档轻量档提高IMU在融合中的权重让运动预测拉住短时的漂移。中量档引入轮式里程计或车速信号作为约束条件。重量档增加人造特征比如反光柱、二维码、特征标牌或者加装UWB、RTK等辅助定位源。另外在纯NDT定位里可以通过分析配准协方差矩阵的特征值来判断退化方向。协方差矩阵对应某个方向的特征值远大于另一个方向说明那个方向约束弱此时应当降低对匹配结果的信任、提高其他传感器权重。5.5 坐标系、外参和时间同步问题速查下面这张表是我处理过的项目里出现频率最高的几类“低级但致命”的问题现象可能原因排查方法点云和地图错一个固定角度外参标定错雷达安装角没写对用标定工具重新标定外参点云上下抖动但水平正常IMU与雷达时间同步差存在延迟检查时间戳做时间补偿地图建好后启动定位雷达帧比地图“胖一圈”雷达驱动里点云畸变校正没开启开启去畸变/运动补偿长时间运行后位姿缓慢漂移雷达或IMU数据流存在间歇性丢失检查话题频率做丢帧监控定位在某个区域反复跳变该区域地图质量差或发生环境变化对比原始点云补扫地图6. 开源代码与资料速查手册这个章节把上面提到的开源工程汇总一下方便你按需取用功能开源项目链接备注2D粒子滤波定位ROS AMCLhttps://github.com/ros-planning/navigationROS内置经典2D激光SLAM与定位Cartographerhttps://github.com/cartographer-project/cartographer建图精度高3D配准库PCLhttps://github.com/PointCloudLibrary/pcl包含NDT/ICP3D配准库轻量libpointmatcherhttps://github.com/ethz-asl/libpointmatcher高效适合嵌入式3D NDT定位ROS节点ndt_scan_matcherhttps://github.com/rsasaki0109/ndt_scan_matcher加载PCD地图即可定位LOAM系列学习用A-LOAMhttps://github.com/HKUST-Aerial-Robotics/A-LOAM代码清晰地面车辆优化版LOAMLEGO-LOAMhttps://github.com/RobustFieldAutonomyLab/LeGO-LOAM轻量紧耦合雷达IMULIO-SAMhttps://github.com/TixiaoShan/LIO-SAM建图质量高紧耦合快速状态估计FAST-LIOhttps://github.com/hku-mars/FAST_LIO实时性好全局重定位描述子Scan Contexthttps://github.com/irapkaist/scancontext位置识别与全局定位八叉树导航地图OctoMaphttps://github.com/OctoMap/octomap3D导航避障轨迹精度评估工具evohttps://github.com/MichaelGrupp/evoATE/RPE分析再说个经验不要为了“炫技”一上来就堆多个方案。如果是项目首次落地建议先验证“最朴素”的链路——2D场景用AMCL3D场景用NDT点云地图先把整条链路跑通、精度达标了再去优化实时性、鲁棒性。如果一开始就上LIO-SAMScan ContextGPU加速这类重装备调参会消耗大量时间问题还容易“多点开花”很难定位到根因。我在实际项目中最喜欢的组合是Cartographer建图 AMCL跑2D室内定位FAST-LIO建图 NDT配准跑3D室外定位。前者稳定成熟后者兼顾实时性和精度。如果预算和算力允许再叠加一个Scan Context做全局重定位兜底基本能应对绝大多数现场故障。最后再分享一个细节不管用哪套方案上线前一定做一次长时间压力测试至少连续运行8小时以上同时记录位姿曲线和点云匹配分数。很多定位问题不是一开始就爆而是运行几个小时后因为累计误差、内存泄漏、话题阻塞才慢慢显现。能扛住长跑的方案才是能落地的方案。