robot-notes — 機器人知識筆記 GitHub ↗
本章與本頁目次

模擬

robot-notes /模擬/在 Gazebo 倉庫用 slam_toolbox 建圖

在 Gazebo 倉庫用 slam_toolbox 建圖(可重跑教學)

把一台舵輪叉車開進 AWS Small Warehouse,用 slam_toolbox 邊走邊建出 2D 地圖。這篇是可重跑的步驟教學:從「幫機器人裝雷射」到「RViz 看著地圖長出來」。

這裡的車跟前面兩篇不是同一台,先講清楚:SDF 模型篇做的是一台差速搬運車(最簡構型,方便講 URDF);叉車專案篇為了最快接通 Nav2,把叉車簡化成 DiffDrive這篇用的是舵輪(traction + steer 兩個指令),也就是叉車專案 §3.1 說的 TricycleSteering 那條「擬真」路線。 對 SLAM 建圖來說底盤怎麼驅動不影響結果——雷射掃到什麼跟車怎麼開沒關係——所以這裡直接用比較像真車的那一種。

前置:用 Gazebo + ROS2 模擬 AMR(gz/ROS2 版本對應、ros_gz 橋接)、2D SLAM 建圖原理(occupancy grid、scan matching、loop closure)。實作素材(叉車模型、倉庫世界)在 aws_warehouse_model_for_gazebo_harmonic

誠實前提:slam_toolbox 是 ROS 2 套件,雷射 gpu_lidarrender(GPU/EGL)。所以實際建圖要在有 GPU 的機器或 GPU runner + ROS 2 環境跑;免費 GitHub runner 的軟體渲染不穩(見 GitHub Actions × gz sim playbook)。CI 能驗的是「模型/世界 SDF 結構、/scan 有沒有宣告」,不能穩定跑出整張地圖。


1. 全貌:SLAM 要哪些零件

slam_toolbox 要的東西其實很少,但每個都不能缺:

要件 從哪來 沒有會怎樣
/scan(sensor_msgs/LaserScan) 機器人身上的 2D LiDAR(gz gpu_lidar)→ ros_gz_bridge 沒雷射就沒得比對,SLAM 不動
odom → base_link 的 tf 里程計(gz OdometryPublisher 或輪速估算)→ bridge slam_toolbox 用它當「兩幀之間大概移動多少」的初值
base_link → lidar_link 的 tf robot_state_publisher 讀 URDF(機器人固定幾何) 雷射點對不到車身座標
/clock(模擬時間) gz → bridge,use_sim_time:=true 時間對不上,tf 外插失敗

slam_toolbox 吃 /scan + tf,產出 /map(OccupancyGrid)並補上 map → odom 這段 tf(把里程漂移修回全域地圖)。

資料流跟 Nav2 閉迴路 同源,只是這裡終點是 slam_toolbox 而非 Nav2。

2. 機器人要會出 /scanodom→base_link(已做進叉車模型)

叉車模型 robot/forklift/model.sdf 已經補上兩樣 SLAM 必需品:

3. world 要掛 Sensors system

gpu_lidar 要真的出資料,world 一定要掛 gz-sim-sensors-system(沒掛 sensor 就閒置不出 /scan)。遷移後的倉庫世界 worlds/small_warehouse.sdf / no_roof_small_warehouse.sdf 已經有了。建圖建議用 no_roof 版(雷射在車高掃不到屋頂,但 no_roof 對視覺/除錯友善)。

4. Docker 環境(ROS 2 Jazzy + Gazebo Harmonic + slam_toolbox)

不污染主機,用容器(對應 model repo 的 Docker 規劃):

FROM osrf/ros:jazzy-desktop-full
RUN apt-get update && apt-get install -y \
    ros-jazzy-ros-gz \           # gz Harmonic + ros_gz 橋接(Jazzy 官方配對)
    ros-jazzy-slam-toolbox \
    ros-jazzy-teleop-twist-keyboard

為何是 Jazzy + Harmonic:見 ROS2↔Gazebo 版本對應。主機若是更新的 gz(如 Jetty),容器自帶 Harmonic、互不干擾。

5. ros_gz_bridge:把 gz 訊息接成 ROS

slam_toolbox 只懂 ROS topic/tf,gz 講自己的話,中間靠 ros_gz_bridge。最少要橋這幾條(@ROS型別@gz型別,方向見 §4 橋接語法):

/scan@sensor_msgs/msg/LaserScan@gz.msgs.LaserScan        # gz → ROS(雷射)
/clock@rosgraph_msgs/msg/Clock@gz.msgs.Clock             # 模擬時間
/model/forklift/odometry@nav_msgs/msg/Odometry@gz.msgs.Odometry
/forklift/traction@std_msgs/msg/Float64@gz.msgs.Double   # ROS → gz(舵輪驅動)
/forklift/steer@std_msgs/msg/Float64@gz.msgs.Double      # ROS → gz(轉向)

tf 部分:gz 的 OdometryPublisher 會發 odom→base_link;用 ros_gz_bridge 把 gz 的 tf(/model/forklift/pose 或 odom 的 tf)接成 ROS /tf,或在 ROS 端用 robot_state_publisher(讀 URDF)補 base_link→lidar_link

6. tf 樹:三方各補一段(slam_toolbox 的關鍵)

SLAM tf 樹:slam_toolbox 補 map→odom、里程計補 odom→base_link、robot_state_publisher 補 base_link→lidar_link

這段 tf 誰提供 意義
map → odom slam_toolbox(建圖時即時算) 把里程漂移修回全域地圖
odom → base_link 里程計(gz OdometryPublisher) 相對里程原點的位姿(會漂)
base_link → lidar_link robot_state_publisher(URDF) 車身到雷射的固定幾何

三段接起來,slam_toolbox 才知道「這幀雷射打在地圖的哪裡」。少任何一段,RViz 會報 tf 斷裂。

7. 起 slam_toolbox + 驅動繞倉庫 + RViz

  1. 起模擬:gz simno_roof_small_warehouse.sdf(設 GZ_SIM_RESOURCE_PATHmodels/robot/),spawn 叉車進倉庫走道。
  2. 起 bridge + robot_state_publisher(§5)。
  3. 起 slam_toolbox:ros2 launch slam_toolbox online_async_launch.py use_sim_time:=true,參數檔設:
    odom_frame: odom
    map_frame: map
    base_frame: base_link
    scan_topic: /scan
    mode: mapping
    
  4. 驅動繞一圈:對 /forklift/traction/forklift/steer 發指令(或接 teleop),讓叉車沿走道繞倉庫一圈、回到起點觸發 loop closure
  5. RViz 看圖長出來:Fixed Frame = map,加 Display:Map(/map)、LaserScan(/scan)、TFRobotModel。地圖會隨車走逐漸補滿貨架輪廓。
  6. 滿意後存圖:ros2 run nav2_map_server map_saver_cli -f warehouse_mapwarehouse_map.pgm + .yaml

8. 在 CI 能驗到哪、不能驗到哪(誠實)

項目 免費 CI(無 GPU)
叉車/世界 SDF 結構、/scan 有無宣告、model:// 解析、world 載入 ✅ 可驗(gz sdf -kgz sim -s headless)
叉車在倉庫會動、走什麼軌跡 ✅ 可驗(位姿判定 + 軌跡圖,純物理)
gpu_lidar 真的出 /scan、slam_toolbox 建出地圖 ❌ 要 render(GPU)+ ROS2 Docker;免費 runner 不穩

所以這篇是「把環境與步驟備齊到一鍵可跑」;真正那張倉庫地圖,等有 GPU 的機器(本機或 GPU runner)跑一次截圖即可。為什麼這樣切,見 playbook 的「需不需 render」分水嶺


來源