Gazebo 感測器模擬

2026-07-18
  • ros2
  • gazebo
  • sensor
  • lidar
  • camera

問題定義

機器人之所以能感知環境,靠的是雷射雷達、相機、IMU 這些感測器。上一節已經能把機器人模型放進 Gazebo 世界,這一節要處理的是讓這個模型「長出」感測器——不只是外觀上加裝一個造型,而是要讓 Gazebo 真的根據模擬世界裡的幾何與光線,計算出對應的感測器資料,這些資料會經過橋接送回 ROS2,成為後面導航(第 6 章)、感知(第 8 章)章節演算法的輸入來源。

核心概念說明

感測器插件:在 <gazebo> 標籤裡描述感測器行為

延續上一節「<gazebo> 標籤放模擬專屬設定」的概念,感測器本身也是透過在對應的 link 底下加上 <sensor> 元素定義的。一個感測器定義通常包含幾個關�键資訊:感測器類型(雷射、相機、IMU)、更新頻率、量測範圍,以及該類型特有的參數(雷射的角度範圍、相機的解析度與視角)。

以雷射雷達為例:

xml
<gazebo reference="lidar_link">
  <sensor name="lidar" type="gpu_lidar">
    <update_rate>10</update_rate>
    <lidar>
      <scan>
        <horizontal>
          <samples>360</samples>
          <min_angle>-3.14159</min_angle>
          <max_angle>3.14159</max_angle>
        </horizontal>
      </scan>
      <range>
        <min>0.12</min>
        <max>10.0</max>
      </range>
    </lidar>
    <topic>scan</topic>
  </sensor>
</gazebo>

噪聲模型:為什麼模擬資料不該是完美無誤差

真實感測器的量測值一定帶有雜訊——雷射測距在邊緣角度誤差較大、相機在低光源下雜訊明顯增加。如果模擬感測器輸出完美無誤差的數值,用這種資料開發出來的演算法,遇到真實世界的雜訊時很可能直接失效(例如一個沒有做誤差容忍設計的濾波器,在乾淨模擬資料下運作正常,換成有雜訊的真實資料就發散)。因此感測器插件通常會加上 <noise> 設定,模擬合理程度的量測誤差:

xml
<noise>
  <type>gaussian</type>
  <mean>0.0</mean>
  <stddev>0.01</stddev>
</noise>

實作範例:幫機器人加上雷射雷達與相機

延續前面章節的模型,在 base_link 上額外加一個感測器掛載用的 link。

1. 在 xacro 裡加入感測器 link 與插件設定

xml
<link name="lidar_link">
  <visual>
    <geometry>
      <cylinder radius="0.03" length="0.04"/>
    </geometry>
  </visual>
  <collision>
    <geometry>
      <cylinder radius="0.03" length="0.04"/>
    </geometry>
  </collision>
  <inertial>
    <mass value="0.1"/>
    <inertia ixx="0.0001" ixy="0" ixz="0" iyy="0.0001" iyz="0" izz="0.0001"/>
  </inertial>
</link>

<joint name="lidar_joint" type="fixed">
  <parent link="base_link"/>
  <child link="lidar_link"/>
  <origin xyz="0 0 0.08" rpy="0 0 0"/>
</joint>

<gazebo reference="lidar_link">
  <sensor name="lidar" type="gpu_lidar">
    <update_rate>10</update_rate>
    <lidar>
      <scan>
        <horizontal>
          <samples>360</samples>
          <min_angle>-3.14159</min_angle>
          <max_angle>3.14159</max_angle>
        </horizontal>
      </scan>
      <range>
        <min>0.12</min>
        <max>10.0</max>
      </range>
      <noise>
        <type>gaussian</type>
        <mean>0.0</mean>
        <stddev>0.01</stddev>
      </noise>
    </lidar>
    <topic>scan</topic>
    <gz_frame_id>lidar_link</gz_frame_id>
  </sensor>
</gazebo>

lidar_jointfixed 類型,呼應第 4.3 節提到的原則——感測器跟機身之間是不隨時間變化的關係,理當用固定 joint(對應 TF2 那一側的 StaticTransformBroadcaster 邏輯是一致的)。

2. 重新展開並 spawn

bash
xacro ~/ros2_ws/src/my_package/urdf/dual_arm.xacro > /tmp/dual_arm_expanded.urdf
gz sim empty.sdf &
ros2 run ros_gz_sim create -file /tmp/dual_arm_expanded.urdf -name dual_arm -z 0.5

3. 橋接雷射掃描資料

bash
ros2 run ros_gz_bridge parameter_bridge /scan@sensor_msgs/msg/LaserScan[gz.msgs.LaserScan

4. 在 Gazebo 世界裡放一個障礙物,觀察掃描資料變化

在 Gazebo 視窗裡用工具列放置一個箱子到機器人附近,然後檢查掃描資料:

bash
ros2 topic echo /scan --once

預期輸出

text
header:
  frame_id: lidar_link
angle_min: -3.14159
angle_max: 3.14159
range_min: 0.12
range_max: 10.0
ranges: [3.201, 3.198, 3.205, ..., 1.412, 1.409, 1.415, ...]

其中一段連續的 ranges 數值會明顯比周圍小很多(例如上面的 1.4 左右那一段),對應到箱子所在的角度範圍——這是驗證感測器插件正確反映了場景幾何最直接的方式:移動箱子的位置,重新查詢應該要看到對應角度範圍的數值跟著改變。

常見錯誤與除錯技巧

錯誤一:/scan 主題完全沒有資料,橋接指令也沒有報錯

原因:最常見的落差點在於 SDF 感測器插件裡的 <topic> 名稱,跟橋接指令裡寫的主題名稱沒有對上(例如插件裡設成 scan,但世界內部實際發布的完整主題路徑可能帶有模型名稱前綴,跟橋接指令裡寫的路徑不一致)。

排除方式:先用 Gazebo Transport 自己的工具確認資料有沒有在 Gazebo 那一側產生,繞開橋接這個環節單獨排查:

bash
gz topic -l | grep scan
text
/world/empty/model/dual_arm/link/lidar_link/sensor/lidar/scan

如果實際主題路徑跟橋接指令裡寫的不一樣,把橋接指令改成這裡看到的完整路徑即可解決。

錯誤二:ranges 陣列裡出現大量 infnan

text
ranges: [inf, inf, inf, 3.201, inf, inf, ...]

原因:這通常不是錯誤,而是正常行為——inf 代表該角度方向的雷射沒有在設定的 <range><max> 範圍內偵測到任何物體(射向空曠遠方),這是真實雷射雷達也會有的正常回報方式,下游演算法(例如 SLAM、避障)本來就需要正確處理這種「沒有量測到」的情況,而不是假設每個角度都會有一個有限數值。

排除方式:如果预期範圍內明明有物體卻還是全部回報 inf,才需要進一步排查——檢查物體是否真的在 <range><min><range><max> 之間,以及該物體是否有正確的碰撞幾何(雷射偵測的是碰撞幾何,不是純視覺 mesh,這呼應第 4.1 節提到 visual 與 collision 各自獨立的概念)。

小結

Gazebo 的感測器插件讓模型不只是外觀相似,還能根據場景幾何真正計算出雷射、相機等感測資料,噪聲模型的存在是為了讓開發出來的演算法從一開始就具備處理真實雜訊的能力,而不是只在完美資料下才能運作。到這裡,第 5 章的模擬環境基礎(啟動世界、匯入模型、感測器模擬)已經具備,足以支撐後面三條支線章節(導航、機械手臂操作、感知)分別展開的實作內容。

延伸閱讀

常見問題

faq_01.log
模擬出來的感測器資料跟真實感測器會一模一樣嗎?
不會,也不應該追求完全一樣。模擬感測器提供的是『結構正確、統計特性接近真實』的資料,用來驗證演算法邏輯是否正確;真實世界還有模擬很難完全複現的因素(光線反射、電磁雜訊、硬體老化),因此在模擬中跑通的演算法,實機測試時仍然需要留出調校空間,模擬的價值在於先過濾掉邏輯層面的錯誤,而不是取代實機測試。
faq_02.log
感測器插件裡的 update_rate 可以隨便設多高嗎?
技術上可以,但要留意這只是『模擬產生資料的頻率』,不代表下游運算或整體系統真的能撐住這個頻率。設定遠高於真實硬體規格的更新頻率做測試,可能會讓你誤以為演算法在某個頻率下沒問題,實際換上真實感測器(頻率通常較低)時才發現時序假設不成立,測試時務必參考預計採用的真實硬體規格設定合理的頻率。
faq_03.log
為什麼加了 LaserScan 插件,RViz2 裡卻看不到掃描線?
最常見的原因跟第 4.4 節提到的一樣:LaserScan 訊息的 frame_id 沒有對應到 TF 樹裡真實存在的座標系,或是橋接沒有正確設定導致資料根本沒有從 Gazebo 傳到 ROS2。先用 ros2 topic echo 確認資料本身有沒有正確送達,再檢查 RViz2 這一側的 Display 設定,是比較有效率的排查順序。