第 5.15 节

map_saver(二维栅格地图保存)

作者:董佳辉

map_saver(二维栅格地图保存)

实车先看:保存哪个话题,文件有没有保留未知区域

map_saver接收一份OccupancyGrid,写出图片及配套地图YAML。 当前slam_launch启动map_saver_server可执行程序,其节点名为map_saver;配置这些参数或启动服务不会自动把地图保存到磁盘,仍需发起保存。

slam_toolbox → namespace/map(OccupancyGrid)
                 ├─ SaveMap服务请求 → 建图launch中的map_saver
                 │     → 临时订阅、等待一份地图 → 写图片 + 地图YAML
                 │
                 └─ map_saver_cli → 新建一个独立MapSaver
                       → 自己订阅、等待一份地图 → 写图片 + 地图YAML

保存的图片 + 地图YAML → map_server → 重新得到OccupancyGrid

map_saver_server、map_saver_cli和节点名map_saver需要区分。 两个可执行程序使用同一个MapSaver类,但CLI自行保存,不通过正在运行的服务器发送请求;因此当前launch中的5秒参数不会自动传给另一条CLI命令。保存器实现、CLI入口

实车上看到的现象先核对什么当前配置怎样理解
建图结束却没出现PGM/YAML是否真正调用保存服务或CLI、返回结果与目录启动保存器不是自动保存,也不是退出时自动导出二维图
YAML设了5 s,CLI却很快等图失败保存的是哪个进程及CLI参数未给CLI加载/设置参数时,它用源码默认2 s,不沿用launch保存器的5 s
保存不到图,一直等到超时map_topic、namespace、QoS及地图发布状态当前每次保存才临时订阅一份地图,True要求兼容的Transient Local发布端
改默认阈值,却看不到这次输出变化SaveMap请求或CLI是否明确指定阈值、map_mode请求非零阈值优先;三态与其他模式的编码不同;地图若只含0、100、-1,许多门槛变化不改变保存阶段分类
图片是灰色,重新加载后却成空地trinary写出的灰205与地图YAML free_thresh当前0.25/0.65组合确有读写后的未知身份丢失,已用安装库临时地图验证
保存二维图后没有PCD/posegraph保存器的输入与输出类型本节点只处理OccupancyGrid;PCD、slam_toolbox内部图状态需要各自保存

本次安装的nav2_map_server为1.1.20,map_saver/map_io安装头文件与该版本一致。当前reality参数与安装副本一致;simulation段仅use_sim_time=True不同。

参数

保留发送的5项原配置。具体保存请求的输入话题、输出路径、模式、格式和请求阈值另列,不能把这些请求字段当成这里已经写好的节点参数。

map_saver:
  ros__parameters:
    use_sim_time: False
    save_map_timeout: 5.0
    free_thresh_default: 0.25
    occupied_thresh_default: 0.65
    map_subscribe_transient_local: True

ros__parameters(等待、默认阈值与地图订阅)

参数表
参数名当前值单位实车处理建议作用调大效果调小效果生效条件/备注
use_sim_timeFalse;仿真True不适用,布尔上实物前必须核对并正确配置选择节点ROS时钟不适用不适用此键在两套配置都存在,slam_launch会重写;不改变图片坐标/格距;保存等图超时不是按/clock走的周期任务
save_map_timeout5.0;源码默认2.0s,一次订阅等图的等待期限通常保持当前值,不作为优先调试项保存时等待收到一份OccupancyGrid的上限可容忍更慢到图,也更晚报告等待失败缺图时更快失败,可能来不及取得正常地图不是每5 s保存,不是完整写盘操作的总期限;只作用于使用此参数的保存器,CLI需单独设置
free_thresh_default0.25无量纲,占用比例;当前对应25上实物前必须核对并正确配置请求free_thresh为0时的默认值;三态保存把已知值≤25写为空闲色保存阶段更多已知格判空闲、中间灰色减少保存阶段更少已知格判空闲、中间灰色增加输入-1仍先写未知灰;但当前灰205在0.25下重载成空闲,需看下方验证;请求非零值覆盖此默认
occupied_thresh_default0.65无量纲,占用比例;当前对应65实物运行时重点观察,有问题再针对性调整请求occupied_thresh为0时的默认值;三态保存把已知值≥65写成占用色保存阶段更少已知格判占用,更多进入中间灰区保存阶段更多已知格判占用,中间灰区减少必须大于有效free门槛、且≤1;非零请求覆盖默认;灰区重载还受free门槛影响
map_subscribe_transient_localTrue不适用,QoS耐久性选择上实物前必须核对并正确配置保存请求期间用Transient Local、Reliable、KeepLast(1)订阅地图不适用不适用便于从兼容发布端取得保留地图;不是保存文件缓存,也不是“节点重启不保留地图”的开关;False仍默认Reliable,改为Volatile、深度10
参数详解
save_map_timeout(等待一份图,不是整个保存任务计时)

每次保存创建一个临时OccupancyGrid订阅,将收到的第一份图放进future,再用executor.spin_until_future_complete等待,当前超时5 s。成功收到图后才调用saveMapToFile写图片和YAML,后面的文件编码/写入不被这个5 s等待包住。订阅与等待代码

因此增大它能容忍慢到图,却不能修复错误topic、namespace、QoS、不存在的发布者或无效目录;更不会提高建图频率。地图已通过兼容Transient Local发布时,晚加入的保存订阅通常可直接取得最近一份,不必等下一次更新。

该等待使用executor的chrono超时,不能把它当成必须让仿真/clock前进5 s才结束的定时保存任务。没有获得完整图,函数返回False;“服务调用成功到达”与“响应result=True、文件完整可读”仍须分开。

map_subscribe_transient_local(取得最后地图的条件)

True时采用Transient Local、Reliable、KeepLast(1);False分支以默认QoS深度10订阅,耐久性为Volatile、可靠性仍Reliable。True可从兼容发布端取已保留的完整图;它不让一个没有Transient Local能力的发布端凭空保存历史消息。

map_server使用Transient Local发布完整图,slam_toolbox当前建图也有自己的地图发布配置,应核对实际端点。若可靠性或耐久性不兼容,延长save_map_timeout不保证收到消息。保存订阅QoS

当前保存器不在启动后持续缓存所有地图版本;订阅在每次保存中创建,取得一份后移除。若发布端保留的是旧图,本次可能迅速保存旧图,等待时间短不证明地图新鲜或建图完整。

free_thresh_default与occupied_thresh_default(请求覆盖与三态保存边界)

SaveMap请求的free_thresh或occupied_thresh为0时,保存器分别使用节点default值;非零请求则使用请求值。CLI的--free、--occ写入其独立保存器的本次SaveParameters,效果同样是明确指定保存阈值。0在这里是“未指定”,不能当成要求精确使用零门槛。

三态保存先将门槛乘100并用rint取整数,再分类。当前0.25/0.65得到25/65:

输入OccupancyGrid的值当前三态保存阶段写出的8位灰度
-1,未知先按未知处理,不参与已知值门槛比较205
0~25,含25空闲254
26~64中间/不确定205
65~100,含65占用0

这不是“雷达点高于25 cm”或“障碍强度大于0.65”,也不是inflation_layer的内部代价阈值。输入应是slam_toolbox的二维占用图,不是terrain_map_ext点云,也不要把具有膨胀颜色的costmap误当原始场地图保存。

当前原始地图若只含-1、0、100,保持合法门槛且不碰端点时,改变门槛常常不改变保存阶段的黑/白/灰分类;它仍会改变写进地图YAML的阈值,并可能改变重新读取后的结果。三态保存比较

有效非零门槛应满足0<free<occupied≤1。参数0会经过默认替代;两门槛相等或反序会使保存失败。trinary按门槛编码图片,scale主要写连续灰度及未知透明通道,raw直接编码占用数;不能把三态分类效果套给所有模式。保存参数检查

已验证的读写问题:trinary、灰205与free=0.25

保存阶段的未知/中间格写成灰度205,并把free_thresh=0.25、occupied_thresh=0.65、negate=0写入配套YAML。重新加载无透明通道三态图时,MapIO使用像素占用比例1−灰度/255,严格小于free_thresh便设空闲0:

灰205的占用比例 = 1 − 205 / 255 ≈ 0.196078
0.196078 < 0.25
所以当前三态保存的灰205,在按配套YAML重新读取时变成0

本次没有只靠公式推断:直接调用容器中已安装的libmap_io,用临时7×1小地图保存并重新读取,得到下面结果。两组occupied_thresh均为0.65、mode为trinary、图片为PGM;保存成功,加载状态均为LOAD_MAP_SUCCESS。

输入7个格子的值保存/加载使用的free门槛重新读取结果
-1、0、25、26、64、65、1000.25,当前配置0、0、0、0、0、100、100
-1、0、25、26、64、65、1000.196,对照配置-1、0、-1、-1、-1、100、100

第一组不仅未知-1变空闲,中间26/64也经灰205变空闲;不能说“25~65一定会在下一次导航时保持未知”。第二组保留灰205对应的未知身份,但已知25也由空闲改为灰区,因为保存整数free门槛变成rint(19.6)=20。0.196不是只修元数据、完全保持原分类的无副作用替换。 灰205写入与YAML门槛、加载灰度判断

如果任务需要保留未知区,可以在三态重新导出时明确使用经此版本核对的free=0.196,并复核20附近的已知占用分类;也可以选择其他合适编码模式,但必须一起核对map_server和StaticLayer的解释,不能只改mode字符串。对既有文件应先检查实际像素和元数据,不能断言来自任意工具的灰色图片都有此问题。

这也解释了前面global_costmap的边界:顶层track_unknown_space=True只能保留/map消息里仍为-1的格;若地图加载已把未知转为0,代价层和Theta*的allow_unknown不能恢复丢失身份。地图加载、未知区与全局规划

本文保留原0.25,没有自动改工程参数或已有地图。这里记录的是本安装版本、指定三态读写条件下已经验证的行为;当前实车地图是否采用相同像素,仍需核对。

保存请求与命令行(不属于原5项节点参数)

保存请求字段
SaveMap请求字段/CLI选项单位或类型当前用法
map_topic/-t话题名称指定OccupancyGrid输入;空请求退回相对map,namespace为/red_standard_robot1时即该namespace/map;绝对话题可明确来源
map_url/-f输出文件路径前缀例如目录/site_01会生成site_01.yaml和site_01.pgm;不是输入地图路径,不应把.yaml再当作前缀后缀填入
image_format/--fmt格式名称未指定时trinary/raw默认pgm,scale默认png;未知透明通道需选支持它的格式
map_mode/--modetrinary/scale/raw未识别值会警告并回退trinary;需要明确自己采用的像素解释
free_thresh/--free无量纲比例0走节点默认;非零覆盖本次值,不自动改节点default参数
occupied_thresh/--occ无量纲比例0走节点默认;非零覆盖本次值,仍需和free一起满足关系

服务默认名为map_saver/save_map,机器人namespace存在时例如/red_standard_robot1/map_saver/save_map。请求响应result表示保存函数的成功/失败,不包含PCD或位姿图。SaveMap接口

接口注释虽举file://与package://形式,本次1.1.20的保存实现直接把map_url当文件名前缀交给图片写入和文件流,没有对应URI解析;这里应使用实际可写文件系统路径,不把注释示例当已经支持的协议。

保存会写图片和YAML两个文件,目录需要预先存在且可写;重复同一路径会覆盖同名输出。默认空路径会生成带系统时间秒数的map_前缀,仍不保证自动按场地归档。文件名生成显式使用系统时钟,不由use_sim_time决定。文件名与写出

CLI为何不继承此处5秒设置

1.1.20的map_saver_cli在当前进程中新建MapSaver,调用on_configure读取自身参数,然后直接调用saveMapTopicToFile;没有给另一个map_saver_server发SaveMap请求。若没有给CLI提供ROS参数或匹配其节点名/namespace的参数文件,它仍使用源码默认save_map_timeout=2.0,而不是建图launch中的5.0。

本工程README给出的CLI命令只传输出前缀和namespace,没有传这份参数文件;因此不要把它当成已使用这里全部设置。显式--free/--occ可以改变本次阈值,--ros-args后的-p可以设置该CLI保存器自己的等待与订阅配置。CLI新建保存器

以下只是后续手动保存示例,未执行。路径是示例,需要换成实际存在且可写的目录;/red_standard_robot1/map也需要按实际机器人修改。此例使用对照门槛0.196,另给CLI独立名字,避免与已有map_saver同名:

ros2 run nav2_map_server map_saver_cli \
  -t /red_standard_robot1/map \
  -f /home/tungchiahui/UserFolder/MyMap/site_01/site_01 \
  --free 0.196 --occ 0.65 --fmt pgm --mode trinary \
  --ros-args -r __ns:=/red_standard_robot1 -r __node:=map_saver_cli \
  -p use_sim_time:=False -p save_map_timeout:=5.0 \
  -p map_subscribe_transient_local:=True

仿真时按实际时间域设置该CLI的use_sim_time。程序自己的-t、-f、--free等选项应放在--ros-args前;参数文件若采用map_saver分组,而CLI被改名为map_saver_cli,还要调整节点匹配,不能只传文件就认为所有参数已加载。

文件内容、启动条件与验证范围

保存了什么,哪些还需要另存
输出或数据当前保存器是否负责应怎样理解
二维图片与配套地图YAML是写输入网格的尺寸/分辨率/原点x、y、yaw及本次mode、阈值;后续由map_server读取
OccupancyGrid.header.frame_id不作为独立frame_id字段写进地图YAML后续由map_server自己的frame_id参数设置消息坐标名称;不能靠文件自动恢复所有坐标关系
三维PCD否由Point-LIO等点云保存路径处理,供small_gicp定位
slam_toolbox的.posegraph/.data否由slam_toolbox序列化接口另存,供继续建图或其内部定位模式使用
全套TF、轨迹、车辆包络与可爬坡能力否二维图不是完整机器人状态快照,也不能证明坡道机械可通行

保存不新增格距或重新计算地面:输入是什么分辨率,输出就按其元数据记录。本版本YAML输出使用Precision(3),非简单小数的原点/格距需要在保存后复核;不能一概称为逐位精度不变。图片中的相对image路径只写文件名,通常和YAML成套保存、成套搬移。元数据写出

当前启动与运行中修改
情况当前实现及作用
slam=Trueslam_launch启动map_saver_server,生命周期管理列表为map_saver;提供保存服务,但launch不自动调用保存
slam=False当前定位分支启动map_server和small_gicp,不由这条分支启动map_saver_server;参数文件里有分组不代表节点一定运行
reality/simulation两份map_saver段仅时钟False/True不同,其他4项一致;slam_launch重写已有use_sim_time
map_saver_serverconfigure时读4项专用参数并创建SaveMap服务;激活阶段建立生命周期bond
map_saver_cli新建自己的保存器并手动on_configure后直接保存;不依赖远端服务器是否Active
运行中设置4项专用参数当前没有动态回调更新已读取成员;设置成功不保证生效,需要重新配置/重启;单次阈值可用请求覆盖

MapSaver的SaveMap回调没有MapServer的Active状态拒绝检查;服务在configure阶段创建。工程正常由lifecycle_manager_slam管理,但不能根据MapServer行为就断言此版本保存器在Inactive时一定拒绝请求。保存服务与生命周期

本次验证与实物待检查
  • 本次已核对: nav2_map_server 1.1.20与安装头文件;源码/安装参数段一致;建图launch实际启动map_saver_server、管理map_saver且没有自动保存调用;CLI独立对象路径及默认2 s。
  • 本次已离线验证: 用安装的libmap_io和临时7×1地图,分别以free=0.25、0.196、occupied=0.65完成trinary/PGM保存再读取;确认前者把未知变空闲、后者保留该未知格,并确认25附近分类也改变。没有启动ROS节点、地图话题或保存服务,临时图片与YAML已随临时目录清理。
  • 源码能确认: 5 s只包等图阶段;请求阈值优先、0为默认哨兵;三态包含边界的保存比较、严格边界的加载比较;QoS和临时订阅、路径前缀、输出元数据及动态参数限制。
  • 实物待核实: 实际map_topic及QoS、消息新鲜度、保存调用返回和文件完整性、真实地图的像素/未知区重新加载、二维图与PCD对齐、当前保存方式是否取得需要的参数;本次未对真实建图或导航任务执行保存。