32-1 nav2_map_server实际作用与用法图解

配套文档:32-nav2_map_server解读.md(源码级分析)

本文目标:用大白话讲清楚 map_server 到底干嘛、怎么用、跟 costmap 怎么配合


1. 一句话理解

map_server 就是 Nav2 的"地图文件阅读器"------它读一个 YAML+图片文件,变成 ROS 里的 OccupancyGrid 消息发到 topic 上,让下游(AMCL、costmap)能拿到地图数据。

它不管"图层叠加",那是 costmap_2d 的事。它只管"读文件→发消息"这一件事。


先搞清楚一张地图从文件到被机器人使用,经过哪些环节:

复制代码
  你的磁盘                          ROS2 网络                    Nav2 下游
  ────────                          ──────────                    ──────────

  map.yaml ─┐                                                    ┌→ AMCL(定位)
            │                                                    │
  map.pgm ──┤                                                    │
            │   ┌──────────┐   /map topic    ┌──────────────┐   │
            └──→│map_server│────────────────→│ StaticLayer   │───┘
                │ (读文件   │   OccupancyGrid  │ (costmap_2d) │
                │  转消息)  │                 └──────────────┘
                └──────────┘
                 ↑
                 │ 还有两个服务:
                 │ /map_server/map     ← GetMap(问它要当前地图)
                 │ /map_server/load_map← LoadMap(运行时换地图)

就这一条链路。 map_server 读文件 → 发 topic → StaticLayer 订阅 → 写入 costmap。


3. map_server 只负责"一张静态图"

很多人以为 map_server 管所有地图,其实不是。Nav2 的地图分三类,各管各的:

地图类型 谁负责 数据来源 给谁用
SLAM 静态地图 map_server YAML+PGM 文件 StaticLayer
动态障碍物 ObstacleLayer(在costmap里) 激光雷达/摄像头实时数据 ObstacleLayer 自己
语义掩码(禁行区/限速区) 另一个 map_server 实例或自定义节点 YAML+PGM 文件 CostmapFilter

map_server 不管动态障碍物,那玩意是 ObstacleLayer 直接订阅激光雷达 topic 拿到的,跟 map_server 毫无关系。


4. 如果只有一张 SLAM 地图(最常见场景)

90% 的场景只需要一张地图。启动命令:

bash 复制代码
ros2 run nav2_map_server map_server \
  --ros-args -p yaml_filename:=/maps/map.yaml

就这么简单。此时:

复制代码
节点名:   map_server(默认)
发布topic: /map(默认,latched)
服务:     /map_server/map      (GetMap)
          /map_server/load_map (LoadMap)

下游怎么拿到地图?

  • StaticLayer 在 nav2_params.yaml 里配 map_topic: /map
  • AMCL 默认订阅 /map topic
  • 因为用了 transient_local QoS(latched),后启动的节点也能收到地图

5. 如果要同时有 SLAM 地图 + 禁行区掩码

比如扫地机有"不能进的房间"(禁行区),需要两张地图:

复制代码
  磁盘文件:
  ├── map.yaml + map.pgm        ← SLAM建的地图(墙、空地)
  └── keepout.yaml + keepout.png ← 禁行区(黑=禁行,白=可通行)

5.1 要跑三个进程

复制代码
  进程1: map_server (SLAM地图)        进程2: map_server (禁行mask)
  ┌──────────────────────┐            ┌──────────────────────┐
  │ 包名: nav2_map_server │            │ 包名: nav2_map_server │  ← 同一个包
  │ 可执行: map_server    │            │ 可执行: map_server    │  ← 同一个exe
  │                        │            │                        │
  │ 改了什么:              │            │ 改了什么:              │
  │  yaml_filename=map    │            │  yaml_filename=keepout │
  │  topic_name=map(默认)  │            │  topic_name=keepout_  │  ← 关键!
  │  节点名=map_server(默认)│           │   mask                │
  │                        │            │  节点名=map_server_  │  ← 关键!
  │                        │            │   keepout             │
  │                        │            │                        │
  │ 发布: /map             │            │ 发布: /keepout_mask   │
  │ 服务: /map_server/map  │            │ 服务: /map_server_    │
  │       /map_server/     │            │   keepout/map         │
  │       load_map         │            │       /map_server_    │
  │                        │            │   keepout/load_map    │
  └──────────────────────┘            └──────────────────────┘

  进程3: costmap_filter_info_server (接线员)
  ┌──────────────────────────────┐
  │ 包名: nav2_map_server         │  ← 同一个包!
  │ 可执行: costmap_filter_info   │
  │   _server                     │
  │                               │
  │ 参数:                         │
  │   type: 0                     │  ← 0=禁行, 1=限速, 2=二值
  │   mask_topic: "keepout_mask"  │  ← 告诉filter去订阅这个topic
  │   base: 0.0                   │
  │   multiplier: 1.0            │
  │                               │
  │ 发布: /costmap_filter_info    │  ← 只发一次(latched)
  └──────────────────────────────┘

5.2 三个名字为什么是独立的?

看源码就明白了:

复制代码
map_server.cpp:66   LifecycleNode("map_server", ...)       ← 节点名(可改)
map_server.cpp:72   declare_parameter("topic_name", "map")  ← topic名(可改)
map_server.cpp:108  service_prefix = get_name() + "/"      ← 服务名=节点名+/
  • 节点名 → 决定服务名(/节点名/map, /节点名/load_map
  • topic_name → 决定发布topic名(跟节点名无关!)
  • 两个独立,所以可以改 topic 不改节点名,也可以改节点名不改 topic

5.3 启动命令

bash 复制代码
# 进程1: SLAM地图(什么都不改,用默认值)
ros2 run nav2_map_server map_server \
  --ros-args -p yaml_filename:=/maps/map.yaml
  # → 节点=map_server, topic=/map

# 进程2: 禁行mask(改节点名+改topic名)
ros2 run nav2_map_server map_server \
  --ros-args -p yaml_filename:=/maps/keepout.yaml \
             -p topic_name:=keepout_mask \
             -r __node:=map_server_keepout
  # → 节点=map_server_keepout, topic=/keepout_mask

# 进程3: 接线员
ros2 run nav2_map_server costmap_filter_info_server \
  --ros-args -p type:=0 \
             -p mask_topic:=keepout_mask \
             -p base:=0.0 \
             -p multiplier:=1.0
  # → 发布 /costmap_filter_info 消息

5.4 下游怎么订阅

复制代码
/map topic
  │
  └──→ StaticLayer 订阅(在 nav2_params.yaml 里配 map_topic: /map)

/keepout_mask topic
  │
  └──→ KeepoutFilter 订阅(不是直接配的!是动态订阅的!)
         │
         │  订阅流程:
         │  1. KeepoutFilter 启动时订阅 /costmap_filter_info
         │  2. 收到 CostmapFilterInfo 消息:
         │     {
         │       type: 0,                    ← 0=禁行
         │       filter_mask_topic: "keepout_mask",
         │       base: 0.0,
         │       multiplier: 1.0
         │     }
         │  3. KeepoutFilter 看到 mask_topic="keepout_mask"
         │     → 动态创建订阅 /keepout_mask
         │  4. 收到 OccupancyGrid mask
         │     → 把黑色区域对应的costmap值设为LETHAL(253)
         │     → 规划器就不会规划路径穿过禁行区了

KeepoutFilter 订阅哪个 topic 不是在配置文件里写的,是 CostmapFilterInfoServer 通过消息告诉它的。 这就是 CostmapFilterInfoServer 这个"接线员"存在的意义。


yaml 复制代码
# nav2_params.yaml (截取 costmap 部分)

global_costmap:
  ros__parameters:
    # 声明要加载哪些 layer 插件
    plugins: ["static_layer", "obstacle_layer", "inflation_layer"]
    filters: ["keepout_filter"]    # ← 声明要加载哪个 filter

    static_layer:
      plugin: "nav2_costmap_2d::StaticLayer"
      map_topic: /map              # ← 直接指定订阅哪个topic

    obstacle_layer:
      plugin: "nav2_costmap_2d::ObstacleLayer"
      # 不需要 map_topic,它订阅的是 /scan 等传感器topic

    inflation_layer:
      plugin: "nav2_costmap_2d::InflationLayer"
      # 纯计算层,不订阅任何外部topic

    keepout_filter:
      plugin: "nav2_costmap_2d::KeepoutFilter"
      filter_info_topic: /costmap_filter_info  # ← 订阅接线员的topic
      # 注意:这里没有 mask_topic 参数!
      # 因为 mask_topic 是从 CostmapFilterInfo 消息里动态获取的

7. 完整接线图(一张图看全)

复制代码
  ┌────────────────┐     /map          ┌──────────────┐
  │ map_server     │──────────────────→│ StaticLayer  │
  │ yaml:=map.yaml │                    └──────┬───────┘
  │ topic:=map     │                           │
  │ node:=map_serv │                           ▼
  └────────────────┘                    ┌──────────────┐
                                        │primary_costmap│
  ┌────────────────┐   /keepout_mask    │  (基础层)     │
  │ map_server     │──────────────────→└──────┬───────┘
  │ yaml:=keepout  │      ┌────────────────────┘
  │ topic:=keepout │      │              ┌──────────────┐
  │ _mask          │      │              │combined_costmap│
  │ node:=map_serv │      │              │  (最终层)      │
  │ _keepout       │      │              └──────┬───────┘
  └────────────────┘      │                     │
                          │                     │
  ┌────────────────┐      │                     │
  │costmap_filter  │      │                     │
  │_info_server    │      │                     │
  │ type:=0        │   /costmap_filter_info     │
  │ mask_topic:=   │──────────────────────┐     │
  │  keepout_mask  │                      │     │
  │ base:=0.0      │              ┌───────▼──────┴───┐
  │ multiplier:=1.0│              │   KeepoutFilter   │
  └────────────────┘              │  1.收info→知道    │
                                  │    要订阅         │
                                  │    keepout_mask   │
                                  │  2.订阅mask→      │
                                  │    把禁行区写入    │
                                  │    combined_costmap│
                                  └───────────────────┘

8. 常见误区纠正

误区1:"map_server 管所有地图图层"

错。 map_server 只管读文件→发topic。图层叠加是 costmap_2d 的 LayeredCostmap 干的。

误区2:"多地图需要多yaml文件传给一个map_server"

错。 一个 map_server 实例只加载一个 yaml。要多张地图,就跑多个 map_server 实例(或自己写节点发布 OccupancyGrid)。

错。 KeepoutFilter 的 mask_topic 是从 CostmapFilterInfo 消息里动态获取的,不是配置文件里写的。配置文件里只配 filter_info_topic(默认 /costmap_filter_info)。

误区4:"topic_name 跟着节点名走"

错。 它们是两个独立参数。源码里 topic_name 直接作为 publisher 的 topic 名,不加节点名前缀。但 service 名会加节点名前缀。

误区5:"README说的multifloor maps是已有功能"

错。 README第31行的 "multifloor maps" 是设计愿景,代码里完全没实现。目前只支持 OccupancyGrid 单层地图。


9. 速查表:两个实例的命名对照

SLAM地图实例 禁行mask实例
ROS包名 nav2_map_server nav2_map_server
可执行文件 map_server map_server
节点名 map_server map_server_keepout
yaml_filename参数 map.yaml keepout.yaml
topic_name参数 map(默认) keepout_mask
发布的topic /map /keepout_mask
GetMap服务 /map_server/map /map_server_keepout/map
LoadMap服务 /map_server/load_map /map_server_keepout/load_map
谁订阅这个topic StaticLayer KeepoutFilter(动态订阅)
怎么告诉下游订阅 nav2_params.yaml直接配 map_topic CostmapFilterInfoServer发消息告诉
相关推荐
米糕闯编程1 天前
鱼香ros2(七)功能包组织C++节点
c++·ros2·cmakelists
米糕闯编程2 天前
鱼香ros2(五)用C++编写节点
c++·ros2·cmakelists·鱼香
blueSatchel4 天前
c++action 编写(ros-humble)
c++·ros2
blueSatchel4 天前
ros-humble仿真-建图-导航
linux·ros2
米糕闯编程6 天前
鱼香ros2(三)Linux操作总结
linux·机器人·ros2
蝙蝠侠的小蝙蝠7 天前
【ROS2 工具】Launch
ros2·launch·多节点启动
某林2128 天前
机器人重启失联:DDS 发现机制与传输层静默故障
人工智能·python·机器人·硬件架构·ros2
米糕闯编程9 天前
鱼香ros2(二)运行第一个机器人
机器人·ros2
蝙蝠侠的小蝙蝠9 天前
【ROS2 通信机制】话题、服务、动作
ros2·服务·动作·话题·通信接口