# 9.2 Gmapping
## 9.2.1 Gmapping SLAM軟件包
Gmapping算法是目前基于**激光雷達**和**里程計**方案里面比較可靠和成熟的一個算法,它基于粒子濾波,采用RBPF的方法效果穩定,許多基于ROS的機器人都跑的是gmapping_slam。這個軟件包位于ros-perception組織中的[slam_gmapping](https://github.com/ros-perception/slam_gmapping)倉庫中。
其中的`slam_gmapping`是一個metapackage,它依賴了`gmapping`,而算法具體實現都在`gmapping`軟件包中,該軟件包中的`slam_gmapping`程序就是我們在ROS中運行的SLAM節點。如果你感興趣,可以閱讀一下`gmapping`的源代碼。
如果你的ROS安裝的是desktop-full版本,應該默認會帶gmapping。你可以用以下命令來檢測gmapping是否安裝
```bash
apt-cache search ros-$ROS_DISTRO-gmapping
```
如果提示沒有,可以直接用apt安裝
```bash
sudo apt-get install ros-$ROS_DISTRO-gmapping
```
gmapping在ROS上運行的方法很簡單
```bash
rosrun gmapping slam_gmapping
```
但由于gmapping算法中需要設置的參數很多,這種啟動單個節點的效率很低。所以往往我們會把gmapping的啟動寫到launch文件中,同時把gmapping需要的一些參數也提前設置好,寫進launch文件或yaml文件。
具體可參考教學軟包中的`slam_sim_demo`中的`gmapping_demo.launch`和`robot_gmapping.launch.xml`文件。
## 9.2.2 Gmapping SLAM計算圖
gmapping的作用是根據激光雷達和里程計(Odometry)的信息,對環境地圖進行構建,并且對自身狀態進行估計。因此它得輸入應當包括激光雷達和里程計的數據,而輸出應當有自身位置和地圖。
下面我們從計算圖(消息的流向)的角度來看看gmapping算法的實際運行中的結構:

位于中心的是我們運行的`slam_gmapping`節點,這個節點負責整個gmapping SLAM的工作。它的輸入需要有兩個:
### 輸入
* `/tf`以及`/tf_static`: 坐標變換,類型為第一代的`tf/tfMessage`或第二代的`tf2_msgs/TFMessage`
其中一定得提供的有兩個tf,一個是`base_frame`與`laser_frame`之間的tf,即機器人底盤和激光雷達之間的變換;一個是`base_frame`與`odom_frame`之間的tf,即底盤和里程計原點之間的坐標變換。`odom_frame`可以理解為里程計原點所在的坐標系。
* `/scan` :激光雷達數據,類型為`sensor_msgs/LaserScan`
`/scan`很好理解,Gmapping SLAM所必須的激光雷達數據,而`/tf`是一個比較容易忽視的細節。盡管`/tf`這個Topic聽起來很簡單,但它維護了整個ROS三維世界里的轉換關系,而`slam_gmapping`要從中讀取的數據是`base_frame`與`laser_frame`之間的tf,只有這樣才能夠把周圍障礙物變換到機器人坐標系下,更重要的是`base_frame`與`odom_frame`之間的tf,這個tf反映了里程計(電機的光電碼盤、視覺里程計、IMU)的監測數據,也就是機器人里程計測得走了多少距離,它會把這段變換發布到`odom_frame`和`laser_frame`之間。
因此`slam_gmapping`會從`/tf`中獲得機器人里程計的數據。
### 輸出
* `/tf`: 主要是輸出`map_frame`和`odom_frame`之間的變換
* `/slam_gmapping/entropy`: `std_msgs/Float64`類型,反映了機器人位姿估計的分散程度
* `/map`: `slam_gmapping`建立的地圖
* `/map_metadata`: 地圖的相關信息
輸出的`/tf`里又一個很重要的信息,就是`map_frame`和`odom_frame`之間的變換,這其實就是對機器人的定位。通過連通`map_frame`和`odom_frame`,這樣`map_frame`與`base_frame`甚至與`laser_frame`都連通了。這樣便實現了機器人在地圖上的定位。
同時,輸出的Topic里還有`/map`,在上一節我們介紹了地圖的類型,在SLAM場景中,地圖是作為SLAM的結果被不斷地更新和發布。
## 9.2.3 里程計誤差及修正
目前ROS中常用的里程計廣義上包括車輪上的光電碼盤、慣性導航元件(IMU)、視覺里程計,你可以只用其中的一個作為odom,也可以選擇多個進行數據融合,融合結果作為odom。通常來說,實際ROS項目中的里程計會發布兩個Topic:
* `/odom`: 類型為`nav_msgs/Odometry`,反映里程計估測的機器人位置、方向、線速度、角速度信息。
* `/tf`: 主要是輸出`odom_frame`和`base_frame`之間的tf。這段tf反映了機器人的位置和方向變換,數值與`/odom`中的相同。
由于以上三種里程計都是對機器人的位姿進行估計,存在著累計誤差,因此當運動時間較長時,`odom_frame`和`base_frame`之間變換的真實值與估計值的誤差會越來越大。你可能會想,能否用激光雷達數據來修正`odom_frame`和`base_frame`的tf。事實上gmapping不是這么做的,里程計估計的是多少,`odom_frame`和`base_frame`的tf就顯示多少,永遠不會去修正這段tf。gmapping的做法是把里程計誤差的修正發布到`map_frame`和`odom_frame`之間的tf上,也就是把誤差補償在了地圖坐標系和里程計原點坐標系之間。通過這種方式來修正定位。
這樣`map_frame`和`base_frame`,甚至和`laser_frame`之間就連通了,實現了機器人在地圖上的定位。
* `/odom`
## 9.2.4 服務
`slam_gmapping`也提供了一個服務:
* `/dynamic_map`: 其srv類型為nav_msgs/GetMap,用于獲取當前的地圖。
該srv定義如下:
nav_msgs/GetMap.srv
```cpp
# Get the map as a nav_msgs/OccupancyGrid
---
nav_msgs/OccupancyGrid map
```
可見該服務的請求為空,即不需要傳入參數,它會直接反饋當前地圖。
## 9.2.5 參數
`slam_gmapping`需要的參數很多,這里以`slam_sim_demo`教學包中的`gmapping_demo`的參數為例,注釋了一些比較重要的參數,具體請查看`ROS-Academy-for-Beginners/slam_sim_demo/launch/include/robot_gmapping.launch.xml`
```xml
<node pkg="gmapping" type="slam_gmapping" name="slam_gmapping" output="screen">
<param name="base_frame" value="$(arg base_frame)"/> <!--底盤坐標系-->
<param name="odom_frame" value="$(arg odom_frame)"/> <!--里程計坐標系-->
<param name="map_update_interval" value="1.0"/> <!--更新時間(s),每多久更新一次地圖,不是頻率-->
<param name="maxUrange" value="20.0"/> <!--激光雷達最大可用距離,在此之外的數據截斷不用-->
<param name="maxRange" value="25.0"/> <!--激光雷達最大距離-->
<param name="sigma" value="0.05"/>
<param name="kernelSize" value="1"/>
<param name="lstep" value="0.05"/>
<param name="astep" value="0.05"/>
<param name="iterations" value="5"/>
<param name="lsigma" value="0.075"/>
<param name="ogain" value="3.0"/>
<param name="lskip" value="0"/>
<param name="minimumScore" value="200"/>
<param name="srr" value="0.01"/>
<param name="srt" value="0.02"/>
<param name="str" value="0.01"/>
<param name="stt" value="0.02"/>
<param name="linearUpdate" value="0.5"/>
<param name="angularUpdate" value="0.436"/>
<param name="temporalUpdate" value="-1.0"/>
<param name="resampleThreshold" value="0.5"/>
<param name="particles" value="80"/>
<param name="xmin" value="-25.0"/>
<param name="ymin" value="-25.0"/>
<param name="xmax" value="25.0"/>
<param name="ymax" value="25.0"/>
<param name="delta" value="0.05"/>
<param name="llsamplerange" value="0.01"/>
<param name="llsamplestep" value="0.01"/>
<param name="lasamplerange" value="0.005"/>
<param name="lasamplestep" value="0.005"/>
<remap from="scan" to="$(arg scan_topic)"/>
</node>
```
### 演示截圖
gmapping算法演示效果圖如下:

- 前言
- 第一章 ROS簡介
- 機器人時代的到來
- ROS發展歷程
- 什么是ROS
- 安裝ROS
- 安裝ROS-Academy-for-Beginners教學包
- 二進制與源碼包
- 安裝RoboWare Studio
- 單元測試一
- 第二章 ROS文件系統
- Catkin編譯系統
- Catkin工作空間
- Package軟件包
- CMakeLists.txt
- package.xml
- Metapacakge軟件元包
- 其他常見文件類型
- 單元測試二
- 第三章 ROS通信架構(一)
- Node & Master
- Launch文件
- Topic
- Msg
- 常見msg類型
- 單元測試三
- 第四章 ROS通信架構(二)
- Service
- Srv
- Parameter server
- Action
- 常見srv類型
- 常見action類型
- 單元測試四
- 第五章 常用工具
- Gazebo
- RViz
- Rqt
- Rosbag
- Rosbridge
- moveit!
- 單元測試五
- 第六章 roscpp
- Client Library與roscpp
- 節點初始、關閉與NodeHandle
- Topic in roscpp
- Service in roscpp
- Param in roscpp
- 時鐘
- 日志與異常
- 第七章 rospy
- Rospy與主要接口
- Topic in rospy
- Service in rospy
- Param與Time
- 第八章 TF與URDF
- 認識TF
- TF消息
- tf in c++
- tf in python
- 統一機器人描述格式
- 附錄:TF數學基礎
- 三維空間剛體運動---旋轉矩陣
- 三維空間剛體運動---歐拉角
- 三維空間剛體運動---四元數
- 第九章 SLAM
- 地圖
- Gmapping
- Karto
- Hector
- 第十章 Navigation
- Navigation Stack
- move_base
- costmap
- Map_server & Amcl
- 附錄:Navigation工具包說明
- amcl
- local_base_planner
- carrot_planner
- clear_costmap_recovery
- costmap_2d
- dwa_local_planner
- fake_localization
- global_planner
- map_server
- move_base_msg
- move_base
- move_slow_and_clear
- navfn
- nav_core
- robot_pose_ekf
- rotate_recovery