第 5.7 节

small_gicp_relocalization(先验点云地图重定位)

作者:董佳辉

small_gicp_relocalization(先验点云地图重定位)

实车先看:地图、起点和云台零位

这个节点在已有地图导航模式 slam:=False 下,把当前配准点云与先验PCD地图对齐,输出 map → odom 的位置、朝向修正。它不直接控制车轮或云台,也不是重新创建地形障碍的节点;slam:=True 的整套启动不运行它。

实车上遇到的问题优先核对用直白的话解释
启动后定位不对或找不到地图prior_pcd_file、地图来源与坐标要提供当前场地的三维PCD;Nav2二维地图与PCD还必须使用对应的位置和朝向
车停在地图中某个位置,却一开始就对不上init_pose本参数直接给 map→odom 一个初始猜测,不是一律填写“底盘在地图中的位置”
RViz点了初始位置,朝向却偏了一个云台角robot_base_frame当前消息被当作 gimbal_yaw 的地图位姿;箭头应对应云台yaw层朝向,不能一律当底盘车头
同一地图,换个云台零位启动后整体方向不同base_frame、lidar_frame 与PCD建图时的参考源码启动时用当时的底盘←雷达变换处理整份地图;雷达随yaw转,建图和当前零位是否对应必须核实
定位跳到相似墙面、柱子或另一条走廊初值、max_dist_sq 和现场可见结构匹配距离放太宽可能接受错误对应,但初值、重复结构或坐标错误也不能只靠收紧距离解决
定位计算慢、细节不足两个 leaf_size、num_neighbors、num_threads先确认点云正确且足够,再权衡细节和计算量;更密的点不保证更准确

当前没有实物,也没有本节点的有效重定位测试。下文分别说明“代码如何解释这些值”和“实车是否适用”,不把源码流程当作已经验证的定位效果。

参数

ros__parameters(ROS节点参数)

参数表

前12项对应当前实物 YAML;prior_pcd_file 虽被注释,却由 launch另行传入;input_cloud_topic 没写在这份 YAML中,仍使用源码默认值。这两项也列出,便于完整理解输入来源。

small_gicp_relocalization:
  ros__parameters:
    use_sim_time: False
    num_threads: 4
    num_neighbors: 20
    global_leaf_size: 0.1
    registered_leaf_size: 0.05
    max_dist_sq: 3.0
    map_frame: "map"
    odom_frame: "odom"
    base_frame: "base_footprint"
    robot_base_frame: "gimbal_yaw"
    lidar_frame: "front_mid360"
    init_pose: [0.0, 0.0, 0.0, 0.0, 0.0, 0.0]   # [x, y, z, roll, pitch, yaw]
    # The prior_pcd_file does not need to be specified since it going to be set by defaults in launch.
    # prior_pcd_file: ""
参数名当前值单位实车处理建议作用调大效果调小效果生效条件/备注
use_sim_timeFalse;仿真 True不适用,布尔上实物前必须核对并正确配置控制节点与TF缓存的ROS时钟不适用不适用地图初始化查TF使用节点当前时间;匹配与发布定时器用真实时间,True不会使其按仿真速率运行
num_threads4线程数,计数通常保持当前值,不作为优先调试项给邻域估计、建树与匹配计算设置并行线程CPU资源足够时可能更快,也可能与Point-LIO等争抢资源并行度降低,计算可能更慢应为正数;不是匹配频率;降采样调用未传此值,当前库默认4线程
num_neighbors20邻点数量,计数实物运行时重点观察,有问题再针对性调整用每点附近的邻点描述局部墙面、地面等形状描述范围通常更大,计算增加,可能混入墙角两侧等不同结构描述更局部、更快,但可能对稀疏点和噪声更敏感同时用于地图和实时点云;不是20帧或20个匹配候选;当前库邻点不足5时不能形成正常局部估计
global_leaf_size0.1m,三维体素边长实物运行时重点观察,有问题再针对性调整启动时将先验PCD降采样后用于匹配地图点通常更少,负担下降,细节减少地图点通常更多,启动和匹配负担增加不修改PCD文件或二维地图分辨率;应为正数;对目标地图只在启动时处理
registered_leaf_size0.05m,三维体素边长实物运行时重点观察,有问题再针对性调整每轮匹配前将累计输入点云降采样输入点通常更少,计算减少,细小结构可能减少输入点通常更多,负担上升;噪声、动态点也可能保留更多不修改Point-LIO降采样设置;应为正数;没有另行剔除人、敌车等动态点
max_dist_sq3.0m²,对应点距离的平方实物运行时重点观察,有问题再针对性调整拒绝当前猜测下距离太远的对应点对允许更远的点配对,也增加相似结构误配可能配对更严格,初值较差时可能剩余点对不足当前距离门槛约√3=1.73 m;不是3 m、定位精度或允许的总位置误差
map_frame"map"不适用,坐标系名称上实物前必须核对并正确配置指定输出TF的父系,约定为目标地图参考系不适用不适用改名称不自动转换PCD坐标;需与二维地图和全局定位约定一致
odom_frame"odom"不适用,坐标系名称上实物前必须核对并正确配置指定输出TF的子系,约定为输入配准点云参考系不适用不适用当前输入应为odom坐标;节点不按该名称转换输入点,也不校验消息frame_id与它一致
base_frame"base_footprint"不适用,坐标系名称上实物前必须核对并正确配置启动时查询它←雷达的变换,用于处理先验PCD不适用不适用当前是底盘参考层;只取一次成功查询,不随随后云台转动重转地图;适用前提与PCD来源有关
robot_base_frame"gimbal_yaw"不适用,坐标系名称上实物前必须核对并正确配置指定initialpose消息所代表的机器人部位,并参与换算不适用不适用仅手动初始位姿回调使用;不是每轮匹配的目标系,也不修改输入点坐标;此处是实际yaw层
lidar_frame"front_mid360"不适用,坐标系名称上实物前必须核对并正确配置与base_frame一起确定启动时处理PCD的变换不适用不适用仅地图加载过程用它查TF;不能由此认定实时输入是雷达自身系;MID360随yaw、不随pitch转
init_pose[0.0, 0.0, 0.0, 0.0, 0.0, 0.0]前3项m,后3项rad上实物前必须核对并正确配置初始化匹配变换,即当前约定下map←odom的猜测不适用,应按参考系逐项设置不适用,应按参考系逐项设置顺序x/y/z/roll/pitch/yaw,旋转按Rz×Ry×Rx;全零为单位变换,不保证机器人位于地图原点
prior_pcd_fileYAML未配置;由launch的 prior_pcd_file 提供不适用,文件路径上实物前必须核对并正确配置加载当前场地的三维先验地图不适用不适用整套启动后面的参数字典传入路径;当前默认实物路径不存在,仿真默认PCD存在;不能用空路径代替
input_cloud_topic"registered_scan",源码默认;YAML未配置不适用,话题名称上实物前必须核对并正确配置接收用于匹配的配准点云不适用不适用当前应来自loam_interface、坐标为odom;不是sensor_scan、terrain_map或原始雷达局部点云;类型PointCloud2

本块没有当前完全不使用的自定义参数:robot_base_frame 只在收到手动初始位姿时参与,init_pose 只在启动时设初值,它们不是持续调节项。话题、坐标系和路径不能按数值大小调参。

参数详解
输入与输出:匹配的是哪两份点云
方向数据代码实际用途
读取文件prior_pcd_file加载PCD,启动时变换、降采样,建立固定目标地图
订阅registered_scan,可由input_cloud_topic更名复制XYZ坐标并累加多帧,作为本轮匹配点云;不用反射强度或点内时间
订阅initialpose,PoseWithCovarianceStamped用人工给定的位姿重设匹配初值;话题名写死为相对名
广播map → odom发布点云匹配得到的参考系修正;不发布独立位姿或匹配点云话题

实时点云已经由Point-LIO与接口节点配准到 odom,本节点直接累加坐标,不再逐帧转雷达系,也不额外按云台yaw旋转这些点。云台旋转时,静态环境应仍在共同的odom坐标下对齐;这个结论依赖上游位姿、安装外参和时间接口正确,目前没有独立yaw验证。

回调只保存最近输入的frame_id与时间戳,没有检查多帧是否同系,也没有在frame_id变化时清空累计点。PCD没有ROS消息那样的frame_id,本节点按自己的来源假设处理;文件名和坐标系字符串不能证明地图已经对应。

每500 ms真实时间尝试匹配一次,有点才处理;每轮降采样、估计局部形状、搜索地图对应点,最多做10次优化迭代。匹配报告收敛才更新结果,未收敛则沿用旧结果,但本轮累计点仍会清空。收敛表示算法停止条件满足,不证明匹配到了场地中正确的位置;源码没有再按匹配误差、有效对应比例等检查可信度。

prior_pcd_file:先确认用的是自己的场地地图

整套启动的默认路径在安装后的 pb2025_nav_bringup 共享目录:

启动方式默认PCD路径规则当前默认文件检查
实物pcd/reality/<world>.pcd;world默认 rmul_2024rmul_2024.pcd 不存在
仿真pcd/simulation/<world>.pcd;world默认 rmuc_2025rmuc_2025.pcd 存在

相应实物默认二维地图 map/reality/rmul_2024.yaml 也不存在;仿真默认二维地图存在。这只是当前默认文件检查,没有读取运行节点参数,也不排除使用者另传world、PCD或二维地图路径。上实物前需要准备当前场地的对应地图,不能把仿真PCD存在当成实物地图已准备。

localization_launch.py 在参数文件之后,额外传入 {"prior_pcd_file": prior_pcd_file},因此注释掉这一行不表示节点不使用先验PCD。它与Point-LIO是否启用 prior_pcd_en 是不同功能:Point-LIO可不加载先验,重定位节点仍需要先验PCD。

文件加载失败只记录错误并返回,构造函数仍继续准备匹配目标,没有明确阻止空目标进入后续匹配;不能把“节点起来了”视为地图加载成功。源码不会在运行时重新读取PCD,也不会替你核对它与Nav2二维地图的位置、朝向、米制尺度或场地版本是否一致。

base_frame 与 lidar_frame:启动时会转动整份地图

PCD读入后,源码查询 base_footprint ← front_mid360,再把这个矩阵左乘到整份地图的每个点。它按节点当前时间查询,失败后每秒重试,成功一次就继续;没有查询次数上限,也没有在后续云台转动时更新目标地图。

这对应源码的一个来源假设:PCD基准是初始 lidar_odom,用初始底盘与雷达关系把地图转换到所需参考中。但当前Point-LIO实际保存的是 camera_init 下的配准世界点,且其位姿原点涉及内部IMU;不是每一帧的雷达自身坐标。把这类PCD当初始雷达系直接套机械变换,是否与实际建图基准一致,必须复核,不能仅照README的命名认定转换正确。

雷达在云台上,这一步特别依赖零位。 同一份PCD若建图时与重新启动时云台相对yaw不同,当前 base←lidar 会含不同角度;按源码计算,目标地图可能被不同矩阵转动,而独立加载的二维地图不会自动跟着调整。是否实际造成错位,需要结合PCD生成时的起点、云台零位、底盘方向与地图原点判断,目前没有测试结论。

该查询与 loam_interface 的一次性基准转换意图相关,但两节点分别查询,不保证取到同一时刻的云台角。这里的机械关系始终保留:MID360挂在 gimbal_yaw,只有实际yaw会带动它,实际pitch位于后续层;两个odom关节固定。重力参考、内部IMU→雷达关系或地图来源错误,不能通过增大匹配距离掩盖。

包内README还写过“未设置时使用空变换”,当前实现没有对应的单位变换旁路:若PCD能读取但frame为空或TF缺失,会进入重试。应以当前源码为准,本轮不修改节点或地图转换。

init_pose 与 robot_base_frame:两种初始位姿不是同一个对象

YAML的 init_pose 直接写入匹配矩阵。 在当前输入为odom、目标为map的约定下,它是 map←odom 初值,即“odom原点在map中的位置和方向”。

[0,0,0,0,0,0] 表示先猜两系完全重合;这只有在起始参考关系吻合时才合适,不表示算法已知道机器人在地图中的真实位置。位置单位m,角度rad;偏航90°应为约1.5708,而不是填90。旋转按yaw、pitch、roll组成 Rz×Ry×Rx。

例如:若odom起点恰好与所描述的机器人原点重合且朝向一致,那么机器人起始地图位姿可能就是map←odom;若odom原点与云台存在高度或安装偏移,或机器人已在odom内移动,就不能直接照抄机器人位置。当前底盘、云台、雷达原点不同,需按实际参考关系计算。

话题 initialpose 则被当作 robot_base_frame=gimbal_yaw 在地图中的位姿。 回调再查询最新 gimbal_yaw←输入扫描系,当前扫描系应为odom,两者相乘得到map←odom:

map←odom = 人工给定的 map←gimbal_yaw × TF中的 gimbal_yaw←odom

所以RViz箭头若被你理解成底盘车头,而当前云台相对底盘左转90°,就可能少算一个云台角。位置也对应云台原点:若人工消息给z=0,源码把它当作云台原点位于map的z=0,而不是“底盘地面高度为0”;高度应按这份地图的基准核对。只改 robot_base_frame 的名字也需要重新核对整条关系,不能把“机器人”三个字自动等同于底盘。本参数仅该回调用,不在每次点云匹配时扣机械角。

回调没有按消息header.frame_id转换位姿、没有使用其协方差,也没有按消息时间查TF,而是取最新变换;收到首帧扫描前,扫描frame_id还为空,查询可能失败。手动设初值只改当前和下一轮匹配猜测,不会重转PCD,也不清空已累计点。云台快速转动时,人工位姿对应的朝向与最新TF是否同一时刻也要核对。

max_dist_sq:1.73 m是配对门槛,不是定位误差指标

当前构建使用的small_gicp代码直接判断“对应点距离平方是否大于 max_dist_sq”;超出就拒绝。源点先按当前猜测变换到地图,再找最近地图点,才检查这项,因此在米制点云中单位为m²:

max_dist_sq = 3.0 m² → 最大对应点距离约 sqrt(3) = 1.73 m
希望门槛为0.5 m → 平方值为0.25 m²

第二行仅解释单位换算,不是建议修改当前值。它不限制机器人总移动距离,不表示允许定位偏差1.73 m,更不是扫描量程。错误初值下,源点仍可能碰巧靠近另一面墙并通过门槛。

实车中,放宽可留下更多点对,但重复柱子、相似走廊和动态物体也可能参与误配;收紧会减少远距离错配,却可能让真实初值偏差稍大时无足够点对。先确认地图、初值和参考系,再结合正确匹配/误匹配情况调整,不能单独把“更小”理解成“更准”。

该单位按当前构建缓存库源码核实,库提交为 fa0cfc983417c044b6036f71dfa448038bb9c820。CMake引用master,重新获取依赖后应复核版本,不把当前缓存行为当成所有版本的固定承诺。

两个leaf_size、num_neighbors 与 num_threads(点云精简、邻点数量与计算线程)

global_leaf_size=0.1 m 处理先验地图;registered_leaf_size=0.05 m 处理每轮累计实时点云。两者不要求相同,也不等于地形分析格子、Nav2地图分辨率或定位精度。地图处理不会改原PCD,实时处理不会改变Point-LIO本身的输出。

调小通常保留更多细节,代价是计算量增加;原始数据没有的细节不会因调小而出现。远处点稀疏、墙角或小柱子能否稳定描述,需看实际数据;人员、敌车等动态点在本节点没有专门筛除,实时点更多也可能带来更多干扰。

num_neighbors=20 指每点用于描述局部形状的邻点数,地图与实时点都用它。它不是20个匹配候选或20帧累计。范围太大可能混入墙角两面、坡面与障碍等不同结构,太小又可能受噪声影响;当前库实际找到的邻点不足5时,会设为无效局部特征的默认值,因此稀疏区域与降采样尺度要一起看。

num_threads=4 控制邻域估计、建树及匹配的并行计算。降采样调用没有传这个参数,当前库该函数默认4线程;把参数改大,不等于所有步骤都跟着增加线程。它不是节点频率,也不保证线程翻倍就快一倍,先沿用当前值。

map_frame、odom_frame、use_sim_time 与输出边界(地图、里程计参考系与时间基准)

地图匹配结果是“目标地图←输入累计点云”的变换;输出直接命名为 map→odom。要使这个名字正确,必须先保证目标PCD和输入实际坐标符合约定;本节点没有另行变换到这两个名称指定的系。

输出约20 Hz真实时间重复当前结果,点云匹配则约2 Hz真实时间尝试一次;20 Hz不表示每次都得到新定位。TF时间取最后收到的扫描时间加0.1秒,固定在源码中,不是本块参数;也没有“已成功匹配过”的输出保护,启动初值同样可被发布。这里只记录输出接口,仍不展开此前搁置的导航走停或TF时效故障排查。

use_sim_time 应与输入、机械TF和启动模式一致。True影响节点当前时间与TF缓存,不会让两个wall timer随仿真倍速自动调整,也不会修复此前Point-LIO点时间单位或内部位姿转换问题。

启动来源与当前验证范围
启动方式参数与地图来源当前区别
整套reality导航,slam=False实物nav2_params.yaml,经localization_launch传入;PCD由launch参数字典另传本表值;默认world对应的实物地图文件缺失,需准备当前场地地图
整套simulation导航,slam=False仿真nav2_params.yaml,同样另传PCD除use_sim_time=True外,本块12项相同;默认仿真地图存在
整套slam=True进入slam_launch,不进入localization_launch本节点不运行,不能用建图输出说明本块已验证
包内独立launch自己写的参数字典,不读取整套YAMLnum_neighbors=10,两种leaf_size均0.25,max_dist_sq=1.0,输入cloud_registered,frame与PCD默认空;不能直接当整套入口

13项自定义参数均在构造时声明和读取,没有动态参数更新回调;init_pose 用于启动,人工初值走 initialpose 消息。13项之外的ROS标准 use_sim_time 由节点时钟处理,不能混为算法参数动态更新。

  • 源码能确认: 参数读取、地图一次性转换、累积配准点云、平方距离门槛、启动初值与人工初值的区别、匹配收敛后的更新规则和输出接口。
  • 仿真已观察: 2026-10-08仅验证独立Point-LIO静止和约20 cm往返;未验证本节点、先验地图匹配、独立云台yaw、人工初值或全局定位。
  • 必须实物测量/文件来源核实: PCD与二维地图的共同基准、建图与启动云台零位、雷达/内部IMU/底盘外参、地图米制尺度及实际起点、可见结构与动态干扰、正确位置下的匹配稳定性。当前重力方向和点时间问题继续保留待核实。

开发问题

关掉small_gicp

原模板对应静态版本
localization_launch.pylocalization_static_launch.py
rm_navigation_reality_launch.pyrm_navigation_reality_static_launch.py
rm_navigation_simulation_launch.pyrm_navigation_simulation_static_launch.py

主要调整是关闭 small_gicp、增加静态 map → odom;导航节点继续复用原来的 navigation_launch.py

日志某些报错

日志原文含义
GICP did not converge.本次点云配准没有收敛
No accumulated points to process.没收到可处理的实时点云
TF lookup failed...初始化需要的 TF 查询失败
Couldn't read PCD file...先验 PCD 读取失败
GICP did not converge.

点云对不上,要自己跑一边slam建出来PCD,然后再使用这个PCD进行匹配。

No accumulated points to process.

这条提示表示 small_gicp 的实时点云缓存为空,发生在匹配之前。现在先查数据有没有送到,暂时不用调匹配参数或初始位姿。

它默认订阅的是 /registered_scan,数据链是:

MID360 → Point-LIO → /cloud_registered
                         ↓ loam_interface
                   /registered_scan → small_gicp