日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

ROS2+cartorgrapher+激光雷达建图并保存

發(fā)布時間:2023/12/20 编程问答 102 豆豆
生活随笔 收集整理的這篇文章主要介紹了 ROS2+cartorgrapher+激光雷达建图并保存 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.

目錄

  • 寫在前面
  • 修改記錄
  • 安裝cartographer
    • 查看cartographer包名字并安裝
  • cartographer初了解
  • 激光雷達(dá)底層驅(qū)動
    • 編譯
      • 編譯驅(qū)動時遇到的bug
      • 編譯完后文件樹
    • 配置文件
      • 啟動文件
    • 配置文件
      • frame_id理解理解學(xué)習(xí)
      • ROS坐標(biāo)系
  • cartographer建圖過程
    • .lua文件
      • map_builder.lua 參數(shù)理解
      • trajectory_builder_2d.lua 參數(shù)理解
    • .launch.py文件
    • 自己的.launch文件
  • 補(bǔ)充
  • 啟動及其調(diào)試
    • 善用rqt_graph
      • 安裝并運(yùn)行rqt
    • 善用rqt_tf_tree
  • 建圖效果
  • 地圖保存
  • 后記

寫在前面

最近在做一個類似菜鳥外賣小車的項(xiàng)目,需要在ROS2下進(jìn)行建圖以及導(dǎo)航,但是國內(nèi)ROS2參考的資料還挺少,遇到許多bug都需要在外網(wǎng)進(jìn)行查閱。這里把自己最近單單使用激光雷達(dá)建圖的過程記錄下來,以便于未來翻閱。

環(huán)境:

  • Ubuntu22.04
  • ROS2 humble
  • 激光雷達(dá):鐳神激光雷達(dá)M10P 網(wǎng)口版

修改記錄

2023.3.5:我們后續(xù)測試了將里程計(jì)數(shù)據(jù)和IMU數(shù)據(jù)進(jìn)行融合再將其添加到cartographer建圖中,發(fā)現(xiàn)效果更好了,后續(xù)寫一篇博客補(bǔ)上這部分的內(nèi)容


2023.2.9:修改了文章順序和一些錯誤,并添加了自己的建圖結(jié)果


安裝cartographer

查看cartographer包名字并安裝

由于ROS2發(fā)布了許多的版本,因此,我們在要安裝適配于自己版本的包之前可以先查看能適配的包

# 查找符合的包 sudo apt-cache search cartographer

返回了許多與humble有關(guān)的包,我們安裝這兩個

sudo apt install ros-humble-cartographer ros-humble-cartographer-ros

等待安裝完之后,查看是否有安裝好

ros2 pkg list | grep cartogrpaher

返回 即代表安裝好

cartographer_ros cartographer_ros_msgs

魚香ROS有解釋,為啥看不到cartographer,這里貼出來
魚香ROS動手學(xué)習(xí)ROS2 安裝cartographer
“可能你會好奇為什么沒有cartographer,因?yàn)閏artographer包的編譯類型原因造成的,不過沒關(guān)系,cartographer_ros依賴于cartographer,所以有cartographer_ros一定有cartographer。”

在安裝完了cartographer之后,其實(shí)我們就可以理解成我們在Ubuntu里面安裝了一個ROS2的功能包,只是這個功能包并不在我們自己的功能區(qū)下面,所以我們只需要啟動這個功能包,他就能發(fā)布他自己的node和topic。

cartographer初了解

在建圖的過程當(dāng)中,傳感器的信息非常重要。在ROS2當(dāng)中,我們需要使用的傳感器信息的方式非常的簡單,即訂閱傳感器topic發(fā)布的信息,在接收信息之后,cartographer包會利用其內(nèi)部的算法對其進(jìn)行解析構(gòu)建,進(jìn)而建圖。cartographer整體算法主要依賴的是激光雷達(dá)的數(shù)據(jù)。
因此我們在使用cartographer建圖之前,我們需要先看懂這個算法是怎么拿到激光雷達(dá)的數(shù)據(jù)。我的入手點(diǎn)是從商家的激光雷達(dá)驅(qū)動文件入手進(jìn)行學(xué)習(xí)。
整體學(xué)習(xí)的邏輯順序如下:

#mermaid-svg-IEb7yQV71xrlVHM3 {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-IEb7yQV71xrlVHM3 .error-icon{fill:#552222;}#mermaid-svg-IEb7yQV71xrlVHM3 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-IEb7yQV71xrlVHM3 .edge-thickness-normal{stroke-width:2px;}#mermaid-svg-IEb7yQV71xrlVHM3 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-IEb7yQV71xrlVHM3 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-IEb7yQV71xrlVHM3 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-IEb7yQV71xrlVHM3 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-IEb7yQV71xrlVHM3 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-IEb7yQV71xrlVHM3 .marker.cross{stroke:#333333;}#mermaid-svg-IEb7yQV71xrlVHM3 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-IEb7yQV71xrlVHM3 .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-IEb7yQV71xrlVHM3 .cluster-label text{fill:#333;}#mermaid-svg-IEb7yQV71xrlVHM3 .cluster-label span{color:#333;}#mermaid-svg-IEb7yQV71xrlVHM3 .label text,#mermaid-svg-IEb7yQV71xrlVHM3 span{fill:#333;color:#333;}#mermaid-svg-IEb7yQV71xrlVHM3 .node rect,#mermaid-svg-IEb7yQV71xrlVHM3 .node circle,#mermaid-svg-IEb7yQV71xrlVHM3 .node ellipse,#mermaid-svg-IEb7yQV71xrlVHM3 .node polygon,#mermaid-svg-IEb7yQV71xrlVHM3 .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-IEb7yQV71xrlVHM3 .node .label{text-align:center;}#mermaid-svg-IEb7yQV71xrlVHM3 .node.clickable{cursor:pointer;}#mermaid-svg-IEb7yQV71xrlVHM3 .arrowheadPath{fill:#333333;}#mermaid-svg-IEb7yQV71xrlVHM3 .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-IEb7yQV71xrlVHM3 .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-IEb7yQV71xrlVHM3 .edgeLabel{background-color:#e8e8e8;text-align:center;}#mermaid-svg-IEb7yQV71xrlVHM3 .edgeLabel rect{opacity:0.5;background-color:#e8e8e8;fill:#e8e8e8;}#mermaid-svg-IEb7yQV71xrlVHM3 .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-IEb7yQV71xrlVHM3 .cluster text{fill:#333;}#mermaid-svg-IEb7yQV71xrlVHM3 .cluster span{color:#333;}#mermaid-svg-IEb7yQV71xrlVHM3 div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-IEb7yQV71xrlVHM3 :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;}激光雷達(dá)如何發(fā)布數(shù)據(jù)cartographer如何接收數(shù)據(jù)基本參數(shù)如何配置調(diào)試及建圖

激光雷達(dá)底層驅(qū)動

任何一家激光雷達(dá)商家都會給你他們驅(qū)動文件,我們需要在自己的工作區(qū)下編譯商家給的驅(qū)動文件,然后驅(qū)動文件中的激光雷達(dá)的節(jié)點(diǎn),這樣才能在ROS2的環(huán)境下接收其數(shù)據(jù)。這里拿鐳神的這個M10P激光雷達(dá)驅(qū)動做例子。

編譯

進(jìn)入其驅(qū)動含有README.md的同層目錄,使用colcon build編譯

編譯驅(qū)動時遇到的bug

在編譯驅(qū)動的驅(qū)動的時候,遇到了這么一個bug

針對這個問題,其實(shí)就是安裝一個包即可,如果同類型的問題也是這個解決思路
ROS2報(bào)錯缺少“diagnostic_updater“,CMake did not find diagnostic_updater. 解決思路

編譯完后文件樹

鐳神給的驅(qū)動文件文件樹在工作區(qū)

. ├── build ├── install ├── lslidar_driver ├── lslidar_msgs ├── README.md └── version.txt

配置文件

然后來查看激光雷達(dá)的啟動和配置文件,這樣有助于后續(xù)建圖過程的搭建。

啟動文件

lsm10p_net_launch.py

'''省略import文件部分''' def generate_launch_description():driver_dir_1 = os.path.join(get_package_share_directory('lslidar_driver'), 'params', 'lsx10_1.yaml')driver_node_1 = LifecycleNode(package='lslidar_driver',executable='lslidar_driver_node',name='lslidar_driver_node', #設(shè)置激光數(shù)據(jù)topic名稱output='screen',emulate_tty=True,namespace='lidar_1',parameters=[driver_dir_1],)rviz_dir = os.path.join(get_package_share_directory('lslidar_driver'), 'rviz', 'lslidar.rviz')rviz_node = Node(package='rviz2',namespace='',executable='rviz2',name='rviz2',arguments=['-d', rviz_dir],output='screen')return LaunchDescription([driver_node_1,rviz_node,])

可以看到商家的啟動文件就是啟動了兩個節(jié)點(diǎn)
首先是第一個driver_node_1,這個就是雷達(dá)的驅(qū)動文件節(jié)點(diǎn)了,使用的配置參數(shù)文件是lsx10_1.yaml,因此后續(xù)修改驅(qū)動文件的時候只需要修改這個文件即可。
然后是第二個節(jié)點(diǎn)rviz_node,這個其實(shí)就是ROS2里面的可視化工具,非常的好用,在后續(xù)我們也會用上。這里面的啟動節(jié)點(diǎn)名字、路徑位置都可以自定義修改

配置文件

/lidar_1/lslidar_driver_node:ros__parameters: frame_id: laser_link #激光坐標(biāo)group_ip: 224.1.1.2add_multicast: false device_ip: 192.168.1.200 #雷達(dá)目的ipdevice_ip_difop: 192.168.1.102 #雷達(dá)源IPmsop_port: 2368 #雷達(dá)目的端口號difop_port: 2369 #雷達(dá)源端口號lidar_name: M10_P #雷達(dá)選擇:M10 M10_P M10_PLUS M10_GPS N10angle_disable_min: 0.0 #角度裁剪開始值angle_disable_max: 0.0 #角度裁剪結(jié)束值min_range: 0.0 #雷達(dá)接收距離最小值max_range: 200.0 #雷達(dá)接收距離最大值use_gps_ts: false #雷達(dá)是否使用GPS授時scan_topic: /scan #設(shè)置激光數(shù)據(jù)topic名稱interface_selection: net #接口選擇:net 為網(wǎng)口,serial 為串口。serial_port_: /dev/ttyUSB0 #串口連接時的串口號 # pcap: /home/ls/work/2211/M10_P_gps.pcap #雷達(dá)是否使用pcap包讀取功能

這里,其實(shí)大部分都沒有什么東西,但是我們需要重點(diǎn)關(guān)注的是frame_id這個東西,因?yàn)橹皼]有接觸過,因此這里就開始學(xué)習(xí)這部分

frame_id理解理解學(xué)習(xí)

參考博客

ROS探索總結(jié)(二十)——發(fā)布導(dǎo)航需要的傳感器信息

要看懂這個參數(shù),我們先來看一下ros2中激光雷達(dá)的消息接口有一些什么消息即這個接口
Sensor_msgs/msg/LaserScan

使用查看一下接口的消息類型

ros2 interface show sensor_msgs/msg/LaserScan

返回

# Single scan from a planar laser range-finder # # If you have another ranging device with different behavior (e.g. a sonar # array), please find or create a different message, since applications # will make fairly laser-specific assumptions about this datastd_msgs/Header header # timestamp in the header is the acquisition time ofbuiltin_interfaces/Time stampint32 secuint32 nanosecstring frame_id# the first ray in the scan.## in frame frame_id, angles are measured around# the positive Z axis (counterclockwise, if Z is up)# with zero angle being forward along the x axisfloat32 angle_min # start angle of the scan [rad] float32 angle_max # end angle of the scan [rad] float32 angle_increment # angular distance between measurements [rad]float32 time_increment # time between measurements [seconds] - if your scanner# is moving, this will be used in interpolating position# of 3d points float32 scan_time # time between scans [seconds]float32 range_min # minimum range value [m] float32 range_max # maximum range value [m]float32[] ranges # range data [m]# (Note: values < range_min or > range_max should be discarded) float32[] intensities # intensity data [device-specific units]. If your# device does not provide intensities, please leave# the array empty.

消息頭有三部分的數(shù)據(jù),int32 sec,uint32 nanosec,string frame_id,這三個參數(shù)的理解就是

sec和nanosec就是時間戳,代表著發(fā)布消息的秒和納秒。

frame_id 是消息中與數(shù)據(jù)相關(guān)聯(lián)的參考系id,例如在在激光數(shù)據(jù)中,frame_id對應(yīng)激光數(shù)據(jù)采集的參考系(坐標(biāo)系)。

因此,frame_id就是某一個物體的參考系的坐標(biāo)名字。然后,我們需要學(xué)習(xí)另一個東西,什么是ROS的常見坐標(biāo)系

ROS坐標(biāo)系

原文鏈接:

ROS坐標(biāo)系統(tǒng),常見的坐標(biāo)系及含義

ros-rep-0105

1.base_link
base_link坐標(biāo)系和機(jī)器人的底盤直接連接。其具體位置和方向都是任意的。對于不同的機(jī)器人平臺,底盤上會有不同的參考點(diǎn)。不過ROS也給了推薦的坐標(biāo)系取法。

x 軸指向機(jī)器人前方
y 軸指向機(jī)器人左方
z 軸指向機(jī)器人上方

2.odom
odom是一個固定在環(huán)境中的坐標(biāo)系也就是world-fixed。它的原點(diǎn)和方向不會隨著機(jī)器人運(yùn)動而改變。但是odom的位置可以隨著機(jī)器人的運(yùn)動漂移。漂移導(dǎo)致odom不是一個很有用的長期的全局坐標(biāo)。然而機(jī)器人的odom坐標(biāo)必須保證是連續(xù)變化的。也就是在odom坐標(biāo)系下機(jī)器人的位置必須是連續(xù)變化的,不能有突變和跳躍。
在一般使用中odom坐標(biāo)系是通過里程計(jì)信息計(jì)算出來的。比如輪子的編碼器或者視覺里程計(jì)算法或者陀螺儀和加速度計(jì)。odom是一個短期的局域的精確坐標(biāo)系。但是卻是一個比較差的長期大范圍坐標(biāo)。

3.map
map和odom一樣是一個固定在環(huán)境中的世界坐標(biāo)系。map的z軸是向上的。機(jī)器人在map坐標(biāo)系下的坐標(biāo)不應(yīng)該隨著時間漂移。但是map坐標(biāo)系下的坐標(biāo)并不需要保證連續(xù)性。也就是說在map坐標(biāo)系下機(jī)器人的坐標(biāo)可以在任何時間發(fā)生跳躍變化。
一般來說map坐標(biāo)系的坐標(biāo)是通過傳感器的信息不斷的計(jì)算更新而來。比如激光雷達(dá),視覺定位等等。因此能夠有效的減少累積誤差,但是也導(dǎo)致每次坐標(biāo)更新可能會產(chǎn)生跳躍。
map坐標(biāo)系是一個很有用的長期全局坐標(biāo)系。但是由于坐標(biāo)會跳躍改變,這是一個比較差的局部坐標(biāo)系(不適合用于避障和局部操作)。

而在開放環(huán)境中,我們需要定義一個全球坐標(biāo)系

  • 默認(rèn)的方向要采用 x軸向東,y軸向北,z軸向上
  • 如果沒有特殊說明的話z軸為零的地方應(yīng)該在WGS84橢球上(WGS84橢球是一個全球定位坐標(biāo)。大致上也就是z代表水平面高度)
    如果在開發(fā)中這個約定不能完全保證,也要求盡量滿足。比如對于沒有GPS,指南針等傳感器的機(jī)器人,仍然可以保證坐標(biāo)系z軸向上的約定。如果有指南針傳感器,這樣就能保證x和y軸的初始化方向。
  • 在結(jié)構(gòu)化的環(huán)境中(比如室內(nèi)),在定義坐標(biāo)系時和環(huán)境保持對應(yīng)更有用。比如對于有平面圖的建筑,坐標(biāo)系可以和平面圖對應(yīng)。類似的對于室內(nèi)環(huán)境地圖可以和建筑物的層相對應(yīng)。對于有多層結(jié)構(gòu)的建筑物,對每一層單獨(dú)有一個坐標(biāo)系也是合理的。

    4.earth
    這個坐標(biāo)系是為了多個機(jī)器人相互交互而設(shè)計(jì)的。當(dāng)有多個機(jī)器人的時候,每個機(jī)器人都有自己的map坐標(biāo)系,他們之間的map坐標(biāo)系并不相同。如果想要在不同的機(jī)器人間共享數(shù)據(jù),則需要這個坐標(biāo)系來進(jìn)行轉(zhuǎn)化。
    如果map坐標(biāo)系是一個全局坐標(biāo)系,那么map到earth坐標(biāo)系的變化可以是一個靜態(tài)變換。如果不是的話,就要每次計(jì)算map坐標(biāo)系的原點(diǎn)和方向。
    在剛啟動的時候map坐標(biāo)系的全局位置可能是不知道的。這時候可以先不發(fā)布到earth的變換,直到有了比較精確的全局位置。

    坐標(biāo)系之間的關(guān)系
    坐標(biāo)系之間的關(guān)系可以用樹圖的方式表示。每一個坐標(biāo)系只能有一個父坐標(biāo)系和任意多個子坐標(biāo)系。

    earth -> map -> odom -> base_link
    按照之前的說明,odom和map都應(yīng)該連接到base_link坐標(biāo)系。但是這樣是不允許的,因?yàn)槊恳粋€坐標(biāo)系只能有一個父坐標(biāo)系。

    坐標(biāo)系變換的計(jì)算

    odom到base_link的變換由里程計(jì)數(shù)據(jù)源中的一個發(fā)布

    map到base_link通過定位組件計(jì)算得出。但是定位組件并不發(fā)布從map到base_link的變換。它首先獲取odom到base_link的變換然后利用定位信息計(jì)算出map到odom的變換。

    earth到map的變換是根據(jù)map坐標(biāo)系選取所發(fā)布的一個靜態(tài)變換。如果沒有設(shè)置,那么就會使用機(jī)器人的初始位置作為坐標(biāo)原點(diǎn)。

    Map之間的切換

    如果機(jī)器人的運(yùn)動范圍很大,那么極有可能是要切換地圖的。在室內(nèi)環(huán)境下,在不同的建筑物中,和不同的樓層地圖都會不同。
    在不同的地圖間切換的時候,定位組件要恰當(dāng)?shù)陌裲dom的parent替換成新的地圖。主要是map到base_link之間的變換要選取恰當(dāng)?shù)牡貓D,然后在轉(zhuǎn)換成map到odom之間的變換。

    odom坐標(biāo)系的連續(xù)性
    在切換地圖的時候,odom坐標(biāo)系不應(yīng)該受到影響。odom坐標(biāo)系要保證連續(xù)性。可能影響連續(xù)性的情況包括進(jìn)出電梯,機(jī)器人自身沒有運(yùn)動,但是周圍環(huán)境發(fā)生很大的變化。還有可能由于運(yùn)動距離太遠(yuǎn),造成數(shù)據(jù)溢出。這些都要特殊進(jìn)行處理。

    看完了這部分,我們也就知道了,frame_id其實(shí)就是標(biāo)注了這部分?jǐn)?shù)據(jù)的來源參考id,在配置文件參數(shù)中寫的laser也就是來源于laser代表的參考系ID。

    cartographer建圖過程

    開始接觸cartographer的時候,對ROS2這種節(jié)點(diǎn)的概念還沒有完全建立,最開始理解要使用這個包來建圖的時候,我以為需要打開什么客戶端,或者說跑一個什么程序。但是隨著學(xué)習(xí)的深入,我逐漸理解到

    cartographer就是ROS2的一個功能包,和我們自己在ROS2的工作空間下建立的功能包是一個道理。我們?nèi)绻枰褂眠@個功能包,其實(shí)只需要簡單的用launch文件啟動這個功能包里面所帶有的節(jié)點(diǎn),然后啟動我們自己激光雷達(dá)的節(jié)點(diǎn),然后cartographer訂閱激光雷達(dá)節(jié)點(diǎn)發(fā)布的消息,當(dāng)然,接收的消息格式和接收的節(jié)點(diǎn)的名字都需要我們一開始配置好,也就是使用.lua和.launch.py(在ROS2中)文件。

    即使用ros2 launch cartographer_ros filenames.launch
    而要完成使用Cartographer進(jìn)行建圖,需要兩個節(jié)點(diǎn)的參與,整個過程的計(jì)算流圖如下:

    /cartographer_node節(jié)點(diǎn):

    該節(jié)點(diǎn)從/scan和/odom話題接收數(shù)據(jù)進(jìn)行計(jì)算,輸出/submap_list數(shù)據(jù).

    該節(jié)點(diǎn)需要接收一個參數(shù)配置文件(第二部分寫的那個)參數(shù)。

    /occupancy_grid_node節(jié)點(diǎn):

    該節(jié)點(diǎn)接收/submap_list子圖列表,然后將其拼接成map并發(fā)布

    該節(jié)點(diǎn)需要配置地圖分辨率和更新周期兩個參數(shù)。

    參考原網(wǎng)址:小魚的文檔

    那么其實(shí)我們需要學(xué)習(xí)理解的就是catorgrapher的.lua和.launch.py文件是如何配置的,就能夠?qū)W會怎么運(yùn)行起來這個算法

    .lua文件

    根據(jù)網(wǎng)上給出的建議,我們最好從cartographer官方給出的配置文件進(jìn)行修改,因此,拿出官網(wǎng)的一個.lua文件進(jìn)行學(xué)習(xí)和修改,來看backpack2d.lua。
    注:我后面增加了一些其他可以修改的參數(shù),然后我基于官方的backpack2d.lua改成自己需要的配置文件,我的激光雷達(dá)的frame_ID是laser_link

    ros2 humble cartographer會下載到電腦的路徑為/opt/ros/humble/share/cartographer_ros/configuration_files/

    參考網(wǎng)址:
    cartographer官網(wǎng)
    https://zhuanlan.zhihu.com/p/563264225

    include "map_builder.lua" include "trajectory_builder.lua"options = {map_builder = MAP_BUILDER,trajectory_builder = TRAJECTORY_BUILDER,map_frame = "map",tracking_frame = "laser_link",published_frame = "laser_link",odom_frame = "odom",provide_odom_frame = false,publish_frame_projected_to_2d = false,use_pose_extrapolator = false,use_odometry = false,use_nav_sat = false,use_landmarks = false,num_laser_scans = 1,num_multi_echo_laser_scans = 0,num_subdivisions_per_laser_scan = 1,num_point_clouds = 0,lookup_transform_timeout_sec = 0.2,submap_publish_period_sec = 0.3,pose_publish_period_sec = 5e-3,trajectory_publish_period_sec = 30e-3,rangefinder_sampling_ratio = 1.,odometry_sampling_ratio = 1.,fixed_frame_pose_sampling_ratio = 1.,imu_sampling_ratio = 1.,landmarks_sampling_ratio = 1., }MAP_BUILDER.use_trajectory_builder_2d = trueTRAJECTORY_BUILDER_2D.submaps.num_range_data = 35 TRAJECTORY_BUILDER_2D.min_range = 0. TRAJECTORY_BUILDER_2D.max_range = 200. TRAJECTORY_BUILDER_2D.missing_data_ray_length = 5. TRAJECTORY_BUILDER_2D.use_imu_data = false TRAJECTORY_BUILDER_2D.use_online_correlative_scan_matching = true TRAJECTORY_BUILDER_2D.real_time_correlative_scan_matcher.linear_search_window = 0.1 TRAJECTORY_BUILDER_2D.real_time_correlative_scan_matcher.translation_delta_cost_weight = 10. TRAJECTORY_BUILDER_2D.real_time_correlative_scan_matcher.rotation_delta_cost_weight = 1e-1POSE_GRAPH.optimization_problem.huber_scale = 1e2 POSE_GRAPH.optimize_every_n_nodes = 35 POSE_GRAPH.constraint_builder.min_score = 0.65return options

    參數(shù)含義:

    • map_frame: 構(gòu)建地圖所使用的坐標(biāo)系,一般就使用我們前面提到的map即可

    • tracking_frame: SLAM算法跟蹤的幀的ROS幀ID。如果要使用IMU,它應(yīng)該在這個地方被選用,盡管它可能會漂移。常見的選擇是“imu_link”。
      ? tracking_frame一般設(shè)置為發(fā)布頻率最高的傳感器的frame_id,cartographer將會把其他數(shù)據(jù)都轉(zhuǎn)移到該坐標(biāo)系下進(jìn)行計(jì)算。如果只使用雷達(dá)數(shù)據(jù)進(jìn)行2D建圖,那就只需要將其設(shè)置為雷達(dá)數(shù)據(jù)話題的frame_id,一般為laser。如果使用雷達(dá)數(shù)據(jù)+IMU進(jìn)行2D或者3D建圖,因?yàn)镮MU的發(fā)布頻率明顯高于雷達(dá),所以需要設(shè)置為imu數(shù)據(jù)話題的frame_id,一般imu_link。

    • published_frame: 要用作發(fā)布坐標(biāo)的子幀的ROS幀ID。例如,如果“odom”框架由系統(tǒng)的不同部分提供,則設(shè)置為“odom“。在這種情況下,將發(fā)布map_frame中“odom”的坐標(biāo)。否則,將其設(shè)置為“base_link”可能是合適的。
      cartographer發(fā)布的tf樹最后將指向published_frame,即published_frame不是cartographer提供的,這里如果沒設(shè)置正確,tf樹就不能連接成功,建圖也就不能正常進(jìn)行。這個一般設(shè)置為底盤的frame_id,也就是URDF文件中的底盤的link name,一般為base_link、base_footprint之類的名字。

    • odom_frame: 僅當(dāng)provide_odom_frame為true時使用。通常是“odom”。

    • provide_odom_frame: 如果enable, 則local, non-loop-closed, continuous pose 將作為 odom_frame發(fā)布在 map_frame.

      • 在大多數(shù)情況下,設(shè)置 provide_odom_frame 為 true 是有意義的,因?yàn)樗梢詾槠渌?jié)點(diǎn)提供一個 odom 坐標(biāo)系,這些節(jié)點(diǎn)可以使用機(jī)器人的里程計(jì)數(shù)據(jù)來估計(jì)機(jī)器人的運(yùn)動,而不必直接處理傳感器數(shù)據(jù)。例如,可以使用 robot_localization 包來融合多個傳感器數(shù)據(jù),并估計(jì)機(jī)器人的位姿。
    • publish_frame_projected_to_2d: 如果enable, 則發(fā)布姿態(tài)將嚴(yán)格限制在純2D位姿下(不包含roll pitch和z-offset坐標(biāo)),這個可以防止出現(xiàn)一些由于pose extrapolation step步驟出現(xiàn)的預(yù)期之外不需要的平面外姿態(tài)

    • use_odometry: 如果enable,則訂閱topic為odom中的nav_msgs/Odometry。這種情況下必須提供里程計(jì).在SLAM過程中也會使用這個消息進(jìn)行建圖。注意:這里如果設(shè)置為true,則需要在ROS2節(jié)點(diǎn)中發(fā)布一個名為odom的topic讓carto訂閱

    • use_nav_sat: 如果enable, 則訂閱主題為fix中的sensor_msgs/NavSatFix。這種情況下必須要使用導(dǎo)航數(shù)據(jù)

    • use_landmarks:如果enable,則訂閱主題為landmarks中的cartographer_ros_msgs/LandmarkList,必須提供LandmarkLists數(shù)據(jù),如1cartographer_ros_msgs/LandmarkEntry中的cartographer_ros_msgs/LandmarkList

    • num_laser_scans: 要訂閱的laser scan的主題數(shù)量。為1時,訂閱sensor_msgs/LaserScan中的scan主題,或者為多臺激光掃描訂閱主題的scan_1,scan_2

    • num_multi_echo_laser_scans: 要訂閱的multi-echo laser scan的主題數(shù)量,為1時,訂閱echoes下的sensor_msgs/MultiEchoLaserScan,或者多個echoes_1, echoes_2

    • num_subdivisions_per_laser_scan: 將每個接收到的(多回波)激光掃描分成的點(diǎn)云數(shù)。細(xì)分掃描可以使掃描儀移動時獲取的掃描不變形。有一個相應(yīng)的軌跡生成器選項(xiàng),可以將細(xì)分的掃描累積到一個點(diǎn)云中,用于掃描匹配。若把默認(rèn)10改為1,1/1=1等于不分割

    • num_point_clouds: 要訂閱的point cloud的主題數(shù)量。為1時,訂閱points2主題的sensor_msgs/PointCloud2,或者為多臺點(diǎn)云訂閱主題的points2_1,points2_2

    • lookup_transform_timeout_sec: 用于使用tf2查找轉(zhuǎn)換的超時秒數(shù)。

    • submap_publish_period_sec: 發(fā)布子圖姿勢的時間間隔(以秒為單位),例如 0.3 秒。

    • pose_publish_period_sec: 發(fā)布姿勢的時間間隔(以秒為單位),例如 5e-3 表示頻率為 200 Hz。

    • publish_to_tf: 啟用或禁用提供 TF 轉(zhuǎn)換

    • publish_tracked_pose: 允許將跟蹤姿勢作為geometry_msgs/PoseStamped 發(fā)布到主題“tracked_pose”。

    • trajectory_publish_period_sec: 發(fā)布軌跡標(biāo)記的時間間隔(以秒為單位),例如 30e-3 30 毫秒。

    • rangefinder_sampling_ratio:測距儀消息的固定比率采樣。

    • odometry_sampling_ratio: 里程計(jì)消息的固定比率采樣。

    • fixed_frame_sampling_ratio: 固定幀消息的固定比率采樣。

    • imu_sampling_ratio IMU: IMU消息的固定比率采樣。

    • landmarks_sampling_ratio: 地標(biāo)消息的固定比率采樣。

    • use_pose_extrapolator: Node里的位姿估計(jì)器,作用是融合里程計(jì)和IMU,推測出一個位姿。 如果use_pose_extrapolator參數(shù)為true,發(fā)布出的這個位姿不準(zhǔn),因?yàn)槭窍闰?yàn)的位姿,沒有經(jīng)過雷達(dá)校準(zhǔn),除非IMU和里程計(jì)特別準(zhǔn)。因此這個參數(shù)一般都是false。如果參數(shù)publish_tracked_pose為false,use_pose_extrapolator其實(shí)就無效了

    TF2是ROS2使用的坐標(biāo)轉(zhuǎn)換的工具

    因此,我們其實(shí)可以根據(jù)我們自己的需求來配置我們所需要的.lua文件

    除此之外,我們還可以配置兩個文件中的參數(shù),也就是頭文件中引入的"map_builder.lua" 和 "trajectory_builder.lua"

    map_builder.lua 參數(shù)理解

    include "pose_graph.lua"MAP_BUILDER = {use_trajectory_builder_2d = false, //是否使用2d建圖use_trajectory_builder_3d = false, //是否使用3d建圖num_background_threads = 4, //使用幾線程pose_graph = POSE_GRAPH,collate_by_trajectory = false, //用于控制是否將數(shù)據(jù)按照軌跡進(jìn)行分組。//如果將 collate_by_trajectory 設(shè)置為 true,則 Cartographer 將會按照每個軌跡的 ID 將數(shù)據(jù)進(jìn)行分組。在建圖過程中,Cartographer 將每個軌跡的數(shù)據(jù)單獨(dú)處理,然后將它們合并到最終地圖中。這對于多個軌跡的數(shù)據(jù)進(jìn)行建圖時非常有用。//如果將 collate_by_trajectory 設(shè)置為 false,則 Cartographer 將忽略軌跡信息,并將所有數(shù)據(jù)都視為同一個軌跡進(jìn)行處理。在這種情況下,Cartographer 會將所有數(shù)據(jù)合并到一起進(jìn)行建圖,生成一個單一的地圖。}

    這其中又含有pose_grapher.lua文件,其中的參數(shù)解析參考
    cartographer pose_graph.lua 參數(shù)解析

    trajectory_builder_2d.lua 參數(shù)理解

    查看這篇網(wǎng)址即可

    cartographer trajectory_builder_2d.lua參數(shù)備忘

    .launch.py文件

    在下載了cartographer包之后,我們可以根據(jù)launch文件的名字來選擇我們需要的文件,命名規(guī)則如下:

    按照功能劃分,分為以下幾類:

    (1)利用已有數(shù)據(jù)集進(jìn)行2d/3d建圖,如demo_backpack_2d.launch(其又調(diào)用了backpack_2d.launch)

    (2)利用先驗(yàn)地圖及數(shù)據(jù)集進(jìn)行全局定位,如demo_backpack_2d_localization.launch

    (3)顯示pbstream文件

    launch文件命名規(guī)則標(biāo)明了其作用:用戶根據(jù)需要選擇launch文件

    • offline_backpack_2d.launch:離線快速構(gòu)建全局地圖,事先記錄的數(shù)據(jù)集被多倍快速播放
    • demo_backpack_2d_localization.launch:基于先驗(yàn)地圖進(jìn)行全局定位
    • demo_backpack_2d.launch:同時定位和建圖,需要跑數(shù)據(jù)包
    • backpack_2d.launch:同時定位和建圖,使用真實(shí)的傳感器數(shù)據(jù)
    • assets_writer_my_robot.launch:用于從.pbstream先前 Cartographer 執(zhí)行的記錄中提取數(shù)據(jù)。

    來源于https://blog.csdn.net/qq_18276949/article/details/113174339

    我們就需要使用launch文件來啟動我們的cartographer功能包的節(jié)點(diǎn),同樣,我們打開前面.lua文件對應(yīng)的.launch文件——backpack_2d.launch.py(在ROS2中,由于python語言特性,已經(jīng)從.launch后綴改為了.launch.py后綴)

    from launch import LaunchDescription from launch.actions import DeclareLaunchArgument, IncludeLaunchDescription from launch.conditions import IfCondition, UnlessCondition from launch.substitutions import LaunchConfiguration from launch_ros.actions import Node, SetRemap from launch_ros.substitutions import FindPackageShare from launch.launch_description_sources import PythonLaunchDescriptionSource import osdef generate_launch_description():## ***** Launch arguments *****# 是否使用仿真時間,真實(shí)的機(jī)器人我們不需要,設(shè)置為Falseuse_sim_time_arg = DeclareLaunchArgument('use_sim_time', default_value = 'False')## ***** File paths ******# 找到cartographer功能包的地址pkg_share = FindPackageShare('cartographer_ros').find('cartographer_ros')## ***** Nodes *****#=====================聲明三個節(jié)點(diǎn),cartographer/occupancy_grid_node/rviz_node=================================cartographer_node = Node(package = 'cartographer_ros',executable = 'cartographer_node',parameters = [{'use_sim_time': LaunchConfiguration('use_sim_time')}],arguments = ['-configuration_directory', FindPackageShare('cartographer_ros').find('cartographer_ros') + '/configuration_files','-configuration_basename', 'backpack_2d.lua'],remappings = [('echoes', 'horizontal_laser_2d')],output = 'screen')# 可視化節(jié)點(diǎn)rviz_node = Node(package='rviz2',namespace='rviz2',executable='rviz2',name='rviz2',output='screen')cartographer_occupancy_grid_node = Node(package = 'cartographer_ros',executable = 'cartographer_occupancy_grid_node',parameters = [{'use_sim_time': True},{'resolution': 0.05}],)return LaunchDescription([use_sim_time_arg,# Nodesrviz_node ,cartographer_node,cartographer_occupancy_grid_node,])

    cartographer_node 節(jié)點(diǎn)中有一個remap的一個重映射,意思就是將前一個話題的名字重映射為后面的話題名字,就類似于一個改名的操作。

    自己的.launch文件

    from launch import LaunchDescription from launch.actions import DeclareLaunchArgument, IncludeLaunchDescription from launch.conditions import IfCondition, UnlessCondition from launch.substitutions import LaunchConfiguration from launch_ros.actions import Node, SetRemap from launch_ros.substitutions import FindPackageShare from launch.launch_description_sources import PythonLaunchDescriptionSource import osdef generate_launch_description():## ***** Launch arguments *****use_sim_time_arg = DeclareLaunchArgument('use_sim_time', default_value = 'False')## ***** File paths ******pkg_share = FindPackageShare('cartographer_ros').find('cartographer_ros')# urdf_dir = os.path.join(pkg_share, 'urdf')# urdf_file = os.path.join(urdf_dir, 'backpack_2d.urdf')# with open(urdf_file, 'r') as infp:# robot_desc = infp.read()## ***** Nodes *****# robot_state_publisher_node = Node(# package = 'robot_state_publisher',# executable = 'robot_state_publisher',# parameters=[# {'robot_description': robot_desc},# {'use_sim_time': LaunchConfiguration('use_sim_time')}],# output = 'screen'# )cartographer_node = Node(package = 'cartographer_ros',executable = 'cartographer_node',arguments = ['-configuration_directory', FindPackageShare('cartographer_ros').find('cartographer_ros') + '/configuration_files','-configuration_basename', 'my_robot.lua'],remappings = [('scan', 'scan')],output = 'screen')cartographer_occupancy_grid_node = Node(package = 'cartographer_ros',executable = 'cartographer_occupancy_grid_node',parameters = [{'use_sim_time': False},{'resolution': 0.05}],)rviz_node = Node(package='rviz2',namespace='rviz2',executable='rviz2',name='rviz2',output='screen')return LaunchDescription([use_sim_time_arg,# Nodes# robot_state_publisher_node,rviz_node,cartographer_node,cartographer_occupancy_grid_node,])

    補(bǔ)充

    在進(jìn)行了融合IMU和里程計(jì)數(shù)據(jù)之后,對carto的使用有了新的理解

    對于carto而言,其核心需要使用的數(shù)據(jù)是激光雷達(dá)發(fā)布的/scan話題,因此我們一定需要提供/scan話題,并提供對應(yīng)的數(shù)據(jù)形式(無論你的數(shù)據(jù)是激光雷達(dá)得到的數(shù)據(jù)亦或者是其他方式得到的)。而對于carto的使用而言,它訂閱數(shù)據(jù)和frameID是沒有任何關(guān)系的,對于ROS2里面,拿到數(shù)據(jù)的方法是訂閱topic,因此參數(shù)中的map_frame,tracking_frame,published_frame都和carto訂閱數(shù)據(jù)無關(guān),這些frame_ID影響的是其TF樹的建立,而與數(shù)據(jù)訂閱無關(guān)。

    在后續(xù)的測試當(dāng)中,我們發(fā)現(xiàn)carto訂閱的激光雷達(dá)數(shù)據(jù)就是/scan話題,訂閱的里程計(jì)數(shù)據(jù)就是/odom話題,我們暫時還沒找到有參數(shù)修改能改變其訂閱的數(shù)據(jù)話題名字,比如將其改變?yōu)橛嗛唎dom_optimize,后續(xù)如果繼續(xù)學(xué)習(xí)的過程中發(fā)現(xiàn)方法,也會繼續(xù)補(bǔ)充,也歡迎理解的朋友在評論區(qū)補(bǔ)充,感謝!

    啟動及其調(diào)試

    我們使用ros2 launch cartographer_ros my_robot.launch.py 注意,這里的my_robot.launch.py是自定義的啟動文件,需要自己配置和修改。
    在啟動之前注意要先啟動激光雷達(dá)的驅(qū)動,讓激光雷達(dá)發(fā)布自己的數(shù)據(jù)。

    善用rqt_graph

    rqt_graph是一個非常好用的一個工具,我們一定要靈活的使用它
    當(dāng)我打開了雷達(dá)的驅(qū)動節(jié)點(diǎn)之后,其顯示為

    當(dāng)我再把cartographer啟動之后,節(jié)點(diǎn)就變成了

    這個工具非常有利于我們看不同的節(jié)點(diǎn)是否成功訂閱了話題

    安裝并運(yùn)行rqt

    sudo apt update # 注意,這里需要安裝符合自己ros版本的rqt # 可以使用sudo apt-cache search 包名字 在apt源里尋找?guī)?/span>sudo apt install ros-humble-rqt*# 運(yùn)行 rqt_graph

    善用rqt_tf_tree

    我們也可以使用該工具來查看各個坐標(biāo)之間的變換關(guān)系

    安裝方式如下:

    # 查看tf2坐標(biāo)關(guān)系 # 安裝 sudo apt install ros-humble-tf2-toolsros2 run tf2_tools view_frames # 查看tf坐標(biāo)關(guān)系

    建圖效果

    我最開始建圖的方式是在vmware虛擬機(jī)上跑Ubuntu接激光雷達(dá),然后用手拿著激光雷達(dá)在家里進(jìn)行建圖,但是我發(fā)現(xiàn)建出來的圖,始終在亂飛,效果如下圖所示:

    搜索了很多資料,都沒有發(fā)現(xiàn)能夠徹底解決的方式。于是后面想把IMU的數(shù)據(jù)也加到carto的算法當(dāng)中去,但是發(fā)現(xiàn)里面的配置參數(shù),urdf學(xué)起來一陣頭大,陸續(xù)學(xué)了一段時間,搞不定。(后續(xù)已經(jīng)解決,未來會寫一篇博客,記得關(guān)注噢)
    最近開學(xué)測試,直接使用Ubuntu,而不是虛擬機(jī)來跑carto算法,但是把雷達(dá)放到了自己要做的小車上推著進(jìn)行建圖。同樣的配置參數(shù)和代碼,發(fā)現(xiàn)建圖效果好了起來,如下圖所示:

    這其中,我認(rèn)為可能是激光雷達(dá)傳到虛擬機(jī)中出現(xiàn)了一些時間上的差異導(dǎo)致了建圖時間不匹配出現(xiàn)建圖亂飛,由于時間所限,也沒繼續(xù)深究了。

    地圖保存

    參考:
    https://blog.csdn.net/PC2721/article/details/128303807

    保存地圖需要另一個叫map_saver的節(jié)點(diǎn)
    在建圖完畢之后,我們在我們想要保存地圖的地方運(yùn)行

    注:這里保存用到了nav2,下一篇繼續(xù)總結(jié)。

    ros2 run nav2_map_server map_saver_cli -f map

    注意:在調(diào)用map_saver節(jié)點(diǎn)之前不要關(guān)閉Cartographer節(jié)點(diǎn),不然會丟失地圖

    會生成.pgm和.yaml兩個文件

    后記

    后面會繼續(xù)琢磨用nv2來導(dǎo)航,繼續(xù)努力

    總結(jié)

    以上是生活随笔為你收集整理的ROS2+cartorgrapher+激光雷达建图并保存的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。