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_time | False;仿真True | 不适用,布尔 | 上实物前必须核对并正确配置 | 选择节点ROS时钟 | 不适用 | 不适用 | 此键在两套配置都存在,slam_launch会重写;不改变图片坐标/格距;保存等图超时不是按/clock走的周期任务 |
save_map_timeout | 5.0;源码默认2.0 | s,一次订阅等图的等待期限 | 通常保持当前值,不作为优先调试项 | 保存时等待收到一份OccupancyGrid的上限 | 可容忍更慢到图,也更晚报告等待失败 | 缺图时更快失败,可能来不及取得正常地图 | 不是每5 s保存,不是完整写盘操作的总期限;只作用于使用此参数的保存器,CLI需单独设置 |
free_thresh_default | 0.25 | 无量纲,占用比例;当前对应25 | 上实物前必须核对并正确配置 | 请求free_thresh为0时的默认值;三态保存把已知值≤25写为空闲色 | 保存阶段更多已知格判空闲、中间灰色减少 | 保存阶段更少已知格判空闲、中间灰色增加 | 输入-1仍先写未知灰;但当前灰205在0.25下重载成空闲,需看下方验证;请求非零值覆盖此默认 |
occupied_thresh_default | 0.65 | 无量纲,占用比例;当前对应65 | 实物运行时重点观察,有问题再针对性调整 | 请求occupied_thresh为0时的默认值;三态保存把已知值≥65写成占用色 | 保存阶段更少已知格判占用,更多进入中间灰区 | 保存阶段更多已知格判占用,中间灰区减少 | 必须大于有效free门槛、且≤1;非零请求覆盖默认;灰区重载还受free门槛影响 |
map_subscribe_transient_local | True | 不适用,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、100 | 0.25,当前配置 | 0、0、0、0、0、100、100 |
| -1、0、25、26、64、65、100 | 0.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/--mode | trinary/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=True | slam_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_server | configure时读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对齐、当前保存方式是否取得需要的参数;本次未对真实建图或导航任务执行保存。