ROS2+cartorgrapher+激光雷达建图并保存
目錄
- 寫在前面
- 修改記錄
- 安裝cartographer
- 查看cartographer包名字并安裝
- cartographer初了解
- 激光雷達底層驅動
- 編譯
- 編譯驅動時遇到的bug
- 編譯完后文件樹
- 配置文件
- 啟動文件
- 配置文件
- frame_id理解理解學習
- ROS坐標系
- cartographer建圖過程
- .lua文件
- map_builder.lua 參數理解
- trajectory_builder_2d.lua 參數理解
- .launch.py文件
- 自己的.launch文件
- 補充
- 啟動及其調試
- 善用rqt_graph
- 安裝并運行rqt
- 善用rqt_tf_tree
- 建圖效果
- 地圖保存
- 后記
寫在前面
最近在做一個類似菜鳥外賣小車的項目,需要在ROS2下進行建圖以及導航,但是國內ROS2參考的資料還挺少,遇到許多bug都需要在外網進行查閱。這里把自己最近單單使用激光雷達建圖的過程記錄下來,以便于未來翻閱。
環境:
- Ubuntu22.04
- ROS2 humble
- 激光雷達:鐳神激光雷達M10P 網口版
修改記錄
2023.3.5:我們后續測試了將里程計數據和IMU數據進行融合再將其添加到cartographer建圖中,發現效果更好了,后續寫一篇博客補上這部分的內容
2023.2.9:修改了文章順序和一些錯誤,并添加了自己的建圖結果
安裝cartographer
查看cartographer包名字并安裝
由于ROS2發布了許多的版本,因此,我們在要安裝適配于自己版本的包之前可以先查看能適配的包
# 查找符合的包 sudo apt-cache search cartographer返回了許多與humble有關的包,我們安裝這兩個
sudo apt install ros-humble-cartographer ros-humble-cartographer-ros等待安裝完之后,查看是否有安裝好
ros2 pkg list | grep cartogrpaher返回 即代表安裝好
cartographer_ros cartographer_ros_msgs魚香ROS有解釋,為啥看不到cartographer,這里貼出來
魚香ROS動手學習ROS2 安裝cartographer
“可能你會好奇為什么沒有cartographer,因為cartographer包的編譯類型原因造成的,不過沒關系,cartographer_ros依賴于cartographer,所以有cartographer_ros一定有cartographer。”
在安裝完了cartographer之后,其實我們就可以理解成我們在Ubuntu里面安裝了一個ROS2的功能包,只是這個功能包并不在我們自己的功能區下面,所以我們只需要啟動這個功能包,他就能發布他自己的node和topic。
cartographer初了解
在建圖的過程當中,傳感器的信息非常重要。在ROS2當中,我們需要使用的傳感器信息的方式非常的簡單,即訂閱傳感器topic發布的信息,在接收信息之后,cartographer包會利用其內部的算法對其進行解析構建,進而建圖。cartographer整體算法主要依賴的是激光雷達的數據。
因此我們在使用cartographer建圖之前,我們需要先看懂這個算法是怎么拿到激光雷達的數據。我的入手點是從商家的激光雷達驅動文件入手進行學習。
整體學習的邏輯順序如下:
激光雷達底層驅動
任何一家激光雷達商家都會給你他們驅動文件,我們需要在自己的工作區下編譯商家給的驅動文件,然后驅動文件中的激光雷達的節點,這樣才能在ROS2的環境下接收其數據。這里拿鐳神的這個M10P激光雷達驅動做例子。
編譯
進入其驅動含有README.md的同層目錄,使用colcon build編譯
編譯驅動時遇到的bug
在編譯驅動的驅動的時候,遇到了這么一個bug
針對這個問題,其實就是安裝一個包即可,如果同類型的問題也是這個解決思路
ROS2報錯缺少“diagnostic_updater“,CMake did not find diagnostic_updater. 解決思路
編譯完后文件樹
鐳神給的驅動文件文件樹在工作區
. ├── build ├── install ├── lslidar_driver ├── lslidar_msgs ├── README.md └── version.txt配置文件
然后來查看激光雷達的啟動和配置文件,這樣有助于后續建圖過程的搭建。
啟動文件
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', #設置激光數據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,])可以看到商家的啟動文件就是啟動了兩個節點
首先是第一個driver_node_1,這個就是雷達的驅動文件節點了,使用的配置參數文件是lsx10_1.yaml,因此后續修改驅動文件的時候只需要修改這個文件即可。
然后是第二個節點rviz_node,這個其實就是ROS2里面的可視化工具,非常的好用,在后續我們也會用上。這里面的啟動節點名字、路徑位置都可以自定義修改
配置文件
/lidar_1/lslidar_driver_node:ros__parameters: frame_id: laser_link #激光坐標group_ip: 224.1.1.2add_multicast: false device_ip: 192.168.1.200 #雷達目的ipdevice_ip_difop: 192.168.1.102 #雷達源IPmsop_port: 2368 #雷達目的端口號difop_port: 2369 #雷達源端口號lidar_name: M10_P #雷達選擇:M10 M10_P M10_PLUS M10_GPS N10angle_disable_min: 0.0 #角度裁剪開始值angle_disable_max: 0.0 #角度裁剪結束值min_range: 0.0 #雷達接收距離最小值max_range: 200.0 #雷達接收距離最大值use_gps_ts: false #雷達是否使用GPS授時scan_topic: /scan #設置激光數據topic名稱interface_selection: net #接口選擇:net 為網口,serial 為串口。serial_port_: /dev/ttyUSB0 #串口連接時的串口號 # pcap: /home/ls/work/2211/M10_P_gps.pcap #雷達是否使用pcap包讀取功能這里,其實大部分都沒有什么東西,但是我們需要重點關注的是frame_id這個東西,因為之前沒有接觸過,因此這里就開始學習這部分
frame_id理解理解學習
參考博客
ROS探索總結(二十)——發布導航需要的傳感器信息
要看懂這個參數,我們先來看一下ros2中激光雷達的消息接口有一些什么消息即這個接口
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.消息頭有三部分的數據,int32 sec,uint32 nanosec,string frame_id,這三個參數的理解就是
sec和nanosec就是時間戳,代表著發布消息的秒和納秒。
frame_id 是消息中與數據相關聯的參考系id,例如在在激光數據中,frame_id對應激光數據采集的參考系(坐標系)。
因此,frame_id就是某一個物體的參考系的坐標名字。然后,我們需要學習另一個東西,什么是ROS的常見坐標系
ROS坐標系
原文鏈接:
ROS坐標系統,常見的坐標系及含義
ros-rep-0105
1.base_link
base_link坐標系和機器人的底盤直接連接。其具體位置和方向都是任意的。對于不同的機器人平臺,底盤上會有不同的參考點。不過ROS也給了推薦的坐標系取法。
x 軸指向機器人前方
y 軸指向機器人左方
z 軸指向機器人上方
2.odom
odom是一個固定在環境中的坐標系也就是world-fixed。它的原點和方向不會隨著機器人運動而改變。但是odom的位置可以隨著機器人的運動漂移。漂移導致odom不是一個很有用的長期的全局坐標。然而機器人的odom坐標必須保證是連續變化的。也就是在odom坐標系下機器人的位置必須是連續變化的,不能有突變和跳躍。
在一般使用中odom坐標系是通過里程計信息計算出來的。比如輪子的編碼器或者視覺里程計算法或者陀螺儀和加速度計。odom是一個短期的局域的精確坐標系。但是卻是一個比較差的長期大范圍坐標。
3.map
map和odom一樣是一個固定在環境中的世界坐標系。map的z軸是向上的。機器人在map坐標系下的坐標不應該隨著時間漂移。但是map坐標系下的坐標并不需要保證連續性。也就是說在map坐標系下機器人的坐標可以在任何時間發生跳躍變化。
一般來說map坐標系的坐標是通過傳感器的信息不斷的計算更新而來。比如激光雷達,視覺定位等等。因此能夠有效的減少累積誤差,但是也導致每次坐標更新可能會產生跳躍。
map坐標系是一個很有用的長期全局坐標系。但是由于坐標會跳躍改變,這是一個比較差的局部坐標系(不適合用于避障和局部操作)。
而在開放環境中,我們需要定義一個全球坐標系
如果在開發中這個約定不能完全保證,也要求盡量滿足。比如對于沒有GPS,指南針等傳感器的機器人,仍然可以保證坐標系z軸向上的約定。如果有指南針傳感器,這樣就能保證x和y軸的初始化方向。
在結構化的環境中(比如室內),在定義坐標系時和環境保持對應更有用。比如對于有平面圖的建筑,坐標系可以和平面圖對應。類似的對于室內環境地圖可以和建筑物的層相對應。對于有多層結構的建筑物,對每一層單獨有一個坐標系也是合理的。
4.earth
這個坐標系是為了多個機器人相互交互而設計的。當有多個機器人的時候,每個機器人都有自己的map坐標系,他們之間的map坐標系并不相同。如果想要在不同的機器人間共享數據,則需要這個坐標系來進行轉化。
如果map坐標系是一個全局坐標系,那么map到earth坐標系的變化可以是一個靜態變換。如果不是的話,就要每次計算map坐標系的原點和方向。
在剛啟動的時候map坐標系的全局位置可能是不知道的。這時候可以先不發布到earth的變換,直到有了比較精確的全局位置。
坐標系之間的關系
坐標系之間的關系可以用樹圖的方式表示。每一個坐標系只能有一個父坐標系和任意多個子坐標系。
earth -> map -> odom -> base_link
按照之前的說明,odom和map都應該連接到base_link坐標系。但是這樣是不允許的,因為每一個坐標系只能有一個父坐標系。
坐標系變換的計算
odom到base_link的變換由里程計數據源中的一個發布
map到base_link通過定位組件計算得出。但是定位組件并不發布從map到base_link的變換。它首先獲取odom到base_link的變換然后利用定位信息計算出map到odom的變換。
earth到map的變換是根據map坐標系選取所發布的一個靜態變換。如果沒有設置,那么就會使用機器人的初始位置作為坐標原點。
Map之間的切換
如果機器人的運動范圍很大,那么極有可能是要切換地圖的。在室內環境下,在不同的建筑物中,和不同的樓層地圖都會不同。
在不同的地圖間切換的時候,定位組件要恰當的把odom的parent替換成新的地圖。主要是map到base_link之間的變換要選取恰當的地圖,然后在轉換成map到odom之間的變換。
odom坐標系的連續性
在切換地圖的時候,odom坐標系不應該受到影響。odom坐標系要保證連續性。可能影響連續性的情況包括進出電梯,機器人自身沒有運動,但是周圍環境發生很大的變化。還有可能由于運動距離太遠,造成數據溢出。這些都要特殊進行處理。
看完了這部分,我們也就知道了,frame_id其實就是標注了這部分數據的來源參考id,在配置文件參數中寫的laser也就是來源于laser代表的參考系ID。
cartographer建圖過程
開始接觸cartographer的時候,對ROS2這種節點的概念還沒有完全建立,最開始理解要使用這個包來建圖的時候,我以為需要打開什么客戶端,或者說跑一個什么程序。但是隨著學習的深入,我逐漸理解到
cartographer就是ROS2的一個功能包,和我們自己在ROS2的工作空間下建立的功能包是一個道理。我們如果需要使用這個功能包,其實只需要簡單的用launch文件啟動這個功能包里面所帶有的節點,然后啟動我們自己激光雷達的節點,然后cartographer訂閱激光雷達節點發布的消息,當然,接收的消息格式和接收的節點的名字都需要我們一開始配置好,也就是使用.lua和.launch.py(在ROS2中)文件。
即使用ros2 launch cartographer_ros filenames.launch
而要完成使用Cartographer進行建圖,需要兩個節點的參與,整個過程的計算流圖如下:
/cartographer_node節點:
該節點從/scan和/odom話題接收數據進行計算,輸出/submap_list數據.
該節點需要接收一個參數配置文件(第二部分寫的那個)參數。
/occupancy_grid_node節點:
該節點接收/submap_list子圖列表,然后將其拼接成map并發布
該節點需要配置地圖分辨率和更新周期兩個參數。
參考原網址:小魚的文檔
那么其實我們需要學習理解的就是catorgrapher的.lua和.launch.py文件是如何配置的,就能夠學會怎么運行起來這個算法
.lua文件
根據網上給出的建議,我們最好從cartographer官方給出的配置文件進行修改,因此,拿出官網的一個.lua文件進行學習和修改,來看backpack2d.lua。
注:我后面增加了一些其他可以修改的參數,然后我基于官方的backpack2d.lua改成自己需要的配置文件,我的激光雷達的frame_ID是laser_link
ros2 humble cartographer會下載到電腦的路徑為/opt/ros/humble/share/cartographer_ros/configuration_files/
參考網址:
cartographer官網
https://zhuanlan.zhihu.com/p/563264225
參數含義:
-
map_frame: 構建地圖所使用的坐標系,一般就使用我們前面提到的map即可
-
tracking_frame: SLAM算法跟蹤的幀的ROS幀ID。如果要使用IMU,它應該在這個地方被選用,盡管它可能會漂移。常見的選擇是“imu_link”。
? tracking_frame一般設置為發布頻率最高的傳感器的frame_id,cartographer將會把其他數據都轉移到該坐標系下進行計算。如果只使用雷達數據進行2D建圖,那就只需要將其設置為雷達數據話題的frame_id,一般為laser。如果使用雷達數據+IMU進行2D或者3D建圖,因為IMU的發布頻率明顯高于雷達,所以需要設置為imu數據話題的frame_id,一般imu_link。 -
published_frame: 要用作發布坐標的子幀的ROS幀ID。例如,如果“odom”框架由系統的不同部分提供,則設置為“odom“。在這種情況下,將發布map_frame中“odom”的坐標。否則,將其設置為“base_link”可能是合適的。
cartographer發布的tf樹最后將指向published_frame,即published_frame不是cartographer提供的,這里如果沒設置正確,tf樹就不能連接成功,建圖也就不能正常進行。這個一般設置為底盤的frame_id,也就是URDF文件中的底盤的link name,一般為base_link、base_footprint之類的名字。 -
odom_frame: 僅當provide_odom_frame為true時使用。通常是“odom”。
-
provide_odom_frame: 如果enable, 則local, non-loop-closed, continuous pose 將作為 odom_frame發布在 map_frame.
- 在大多數情況下,設置 provide_odom_frame 為 true 是有意義的,因為它可以為其他節點提供一個 odom 坐標系,這些節點可以使用機器人的里程計數據來估計機器人的運動,而不必直接處理傳感器數據。例如,可以使用 robot_localization 包來融合多個傳感器數據,并估計機器人的位姿。
-
publish_frame_projected_to_2d: 如果enable, 則發布姿態將嚴格限制在純2D位姿下(不包含roll pitch和z-offset坐標),這個可以防止出現一些由于pose extrapolation step步驟出現的預期之外不需要的平面外姿態
-
use_odometry: 如果enable,則訂閱topic為odom中的nav_msgs/Odometry。這種情況下必須提供里程計.在SLAM過程中也會使用這個消息進行建圖。注意:這里如果設置為true,則需要在ROS2節點中發布一個名為odom的topic讓carto訂閱
-
use_nav_sat: 如果enable, 則訂閱主題為fix中的sensor_msgs/NavSatFix。這種情況下必須要使用導航數據
-
use_landmarks:如果enable,則訂閱主題為landmarks中的cartographer_ros_msgs/LandmarkList,必須提供LandmarkLists數據,如1cartographer_ros_msgs/LandmarkEntry中的cartographer_ros_msgs/LandmarkList
-
num_laser_scans: 要訂閱的laser scan的主題數量。為1時,訂閱sensor_msgs/LaserScan中的scan主題,或者為多臺激光掃描訂閱主題的scan_1,scan_2
-
num_multi_echo_laser_scans: 要訂閱的multi-echo laser scan的主題數量,為1時,訂閱echoes下的sensor_msgs/MultiEchoLaserScan,或者多個echoes_1, echoes_2
-
num_subdivisions_per_laser_scan: 將每個接收到的(多回波)激光掃描分成的點云數。細分掃描可以使掃描儀移動時獲取的掃描不變形。有一個相應的軌跡生成器選項,可以將細分的掃描累積到一個點云中,用于掃描匹配。若把默認10改為1,1/1=1等于不分割
-
num_point_clouds: 要訂閱的point cloud的主題數量。為1時,訂閱points2主題的sensor_msgs/PointCloud2,或者為多臺點云訂閱主題的points2_1,points2_2
-
lookup_transform_timeout_sec: 用于使用tf2查找轉換的超時秒數。
-
submap_publish_period_sec: 發布子圖姿勢的時間間隔(以秒為單位),例如 0.3 秒。
-
pose_publish_period_sec: 發布姿勢的時間間隔(以秒為單位),例如 5e-3 表示頻率為 200 Hz。
-
publish_to_tf: 啟用或禁用提供 TF 轉換
-
publish_tracked_pose: 允許將跟蹤姿勢作為geometry_msgs/PoseStamped 發布到主題“tracked_pose”。
-
trajectory_publish_period_sec: 發布軌跡標記的時間間隔(以秒為單位),例如 30e-3 30 毫秒。
-
rangefinder_sampling_ratio:測距儀消息的固定比率采樣。
-
odometry_sampling_ratio: 里程計消息的固定比率采樣。
-
fixed_frame_sampling_ratio: 固定幀消息的固定比率采樣。
-
imu_sampling_ratio IMU: IMU消息的固定比率采樣。
-
landmarks_sampling_ratio: 地標消息的固定比率采樣。
-
use_pose_extrapolator: Node里的位姿估計器,作用是融合里程計和IMU,推測出一個位姿。 如果use_pose_extrapolator參數為true,發布出的這個位姿不準,因為是先驗的位姿,沒有經過雷達校準,除非IMU和里程計特別準。因此這個參數一般都是false。如果參數publish_tracked_pose為false,use_pose_extrapolator其實就無效了
TF2是ROS2使用的坐標轉換的工具
因此,我們其實可以根據我們自己的需求來配置我們所需要的.lua文件
除此之外,我們還可以配置兩個文件中的參數,也就是頭文件中引入的"map_builder.lua" 和 "trajectory_builder.lua"
map_builder.lua 參數理解
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, //用于控制是否將數據按照軌跡進行分組。//如果將 collate_by_trajectory 設置為 true,則 Cartographer 將會按照每個軌跡的 ID 將數據進行分組。在建圖過程中,Cartographer 將每個軌跡的數據單獨處理,然后將它們合并到最終地圖中。這對于多個軌跡的數據進行建圖時非常有用。//如果將 collate_by_trajectory 設置為 false,則 Cartographer 將忽略軌跡信息,并將所有數據都視為同一個軌跡進行處理。在這種情況下,Cartographer 會將所有數據合并到一起進行建圖,生成一個單一的地圖。}這其中又含有pose_grapher.lua文件,其中的參數解析參考
cartographer pose_graph.lua 參數解析
trajectory_builder_2d.lua 參數理解
查看這篇網址即可
cartographer trajectory_builder_2d.lua參數備忘
.launch.py文件
在下載了cartographer包之后,我們可以根據launch文件的名字來選擇我們需要的文件,命名規則如下:
按照功能劃分,分為以下幾類:
(1)利用已有數據集進行2d/3d建圖,如demo_backpack_2d.launch(其又調用了backpack_2d.launch)
(2)利用先驗地圖及數據集進行全局定位,如demo_backpack_2d_localization.launch
(3)顯示pbstream文件
launch文件命名規則標明了其作用:用戶根據需要選擇launch文件
- offline_backpack_2d.launch:離線快速構建全局地圖,事先記錄的數據集被多倍快速播放
- demo_backpack_2d_localization.launch:基于先驗地圖進行全局定位
- demo_backpack_2d.launch:同時定位和建圖,需要跑數據包
- backpack_2d.launch:同時定位和建圖,使用真實的傳感器數據
- assets_writer_my_robot.launch:用于從.pbstream先前 Cartographer 執行的記錄中提取數據。
來源于https://blog.csdn.net/qq_18276949/article/details/113174339
我們就需要使用launch文件來啟動我們的cartographer功能包的節點,同樣,我們打開前面.lua文件對應的.launch文件——backpack_2d.launch.py(在ROS2中,由于python語言特性,已經從.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 *****# 是否使用仿真時間,真實的機器人我們不需要,設置為Falseuse_sim_time_arg = DeclareLaunchArgument('use_sim_time', default_value = 'False')## ***** File paths ******# 找到cartographer功能包的地址pkg_share = FindPackageShare('cartographer_ros').find('cartographer_ros')## ***** Nodes *****#=====================聲明三個節點,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')# 可視化節點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 節點中有一個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,])補充
在進行了融合IMU和里程計數據之后,對carto的使用有了新的理解
對于carto而言,其核心需要使用的數據是激光雷達發布的/scan話題,因此我們一定需要提供/scan話題,并提供對應的數據形式(無論你的數據是激光雷達得到的數據亦或者是其他方式得到的)。而對于carto的使用而言,它訂閱數據和frameID是沒有任何關系的,對于ROS2里面,拿到數據的方法是訂閱topic,因此參數中的map_frame,tracking_frame,published_frame都和carto訂閱數據無關,這些frame_ID影響的是其TF樹的建立,而與數據訂閱無關。
在后續的測試當中,我們發現carto訂閱的激光雷達數據就是/scan話題,訂閱的里程計數據就是/odom話題,我們暫時還沒找到有參數修改能改變其訂閱的數據話題名字,比如將其改變為訂閱odom_optimize,后續如果繼續學習的過程中發現方法,也會繼續補充,也歡迎理解的朋友在評論區補充,感謝!
啟動及其調試
我們使用ros2 launch cartographer_ros my_robot.launch.py 注意,這里的my_robot.launch.py是自定義的啟動文件,需要自己配置和修改。
在啟動之前注意要先啟動激光雷達的驅動,讓激光雷達發布自己的數據。
善用rqt_graph
rqt_graph是一個非常好用的一個工具,我們一定要靈活的使用它
當我打開了雷達的驅動節點之后,其顯示為
當我再把cartographer啟動之后,節點就變成了
這個工具非常有利于我們看不同的節點是否成功訂閱了話題
安裝并運行rqt
sudo apt update # 注意,這里需要安裝符合自己ros版本的rqt # 可以使用sudo apt-cache search 包名字 在apt源里尋找庫sudo apt install ros-humble-rqt*# 運行 rqt_graph善用rqt_tf_tree
我們也可以使用該工具來查看各個坐標之間的變換關系
安裝方式如下:
# 查看tf2坐標關系 # 安裝 sudo apt install ros-humble-tf2-toolsros2 run tf2_tools view_frames # 查看tf坐標關系建圖效果
我最開始建圖的方式是在vmware虛擬機上跑Ubuntu接激光雷達,然后用手拿著激光雷達在家里進行建圖,但是我發現建出來的圖,始終在亂飛,效果如下圖所示:
搜索了很多資料,都沒有發現能夠徹底解決的方式。于是后面想把IMU的數據也加到carto的算法當中去,但是發現里面的配置參數,urdf學起來一陣頭大,陸續學了一段時間,搞不定。(后續已經解決,未來會寫一篇博客,記得關注噢)
最近開學測試,直接使用Ubuntu,而不是虛擬機來跑carto算法,但是把雷達放到了自己要做的小車上推著進行建圖。同樣的配置參數和代碼,發現建圖效果好了起來,如下圖所示:
這其中,我認為可能是激光雷達傳到虛擬機中出現了一些時間上的差異導致了建圖時間不匹配出現建圖亂飛,由于時間所限,也沒繼續深究了。
地圖保存
參考:
https://blog.csdn.net/PC2721/article/details/128303807
保存地圖需要另一個叫map_saver的節點
在建圖完畢之后,我們在我們想要保存地圖的地方運行
注:這里保存用到了nav2,下一篇繼續總結。
ros2 run nav2_map_server map_saver_cli -f map注意:在調用map_saver節點之前不要關閉Cartographer節點,不然會丟失地圖
會生成.pgm和.yaml兩個文件
后記
后面會繼續琢磨用nv2來導航,繼續努力
總結
以上是生活随笔為你收集整理的ROS2+cartorgrapher+激光雷达建图并保存的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: http——基础知识
- 下一篇: 线性稳压器与开关稳压器的对比分析