您好,登錄后才能下訂單哦!
本篇內容介紹了“C++ Cartographer的入口node main源碼分析”的有關知識,在實際案例的操作過程中,不少人都會遇到這樣的困境,接下來就讓小編帶領大家學習一下如何處理這些情況吧!希望大家仔細閱讀,能夠學有所成!
void Run() { constexpr double kTfBufferCacheTimeInSeconds = 10.; tf2_ros::Buffer tf_buffer{::ros::Duration(kTfBufferCacheTimeInSeconds)}; // 開啟監聽tf的獨立線程 tf2_ros::TransformListener tf(tf_buffer); NodeOptions node_options; TrajectoryOptions trajectory_options; // c++11: std::tie()函數可以將變量連接到一個給定的tuple上,生成一個元素類型全是引用的tuple // 讀取Lua文件內容,把Lua文件內容給到node_options和trajectory_options std::tie(node_options, trajectory_options) = LoadOptions(FLAGS_configuration_directory, FLAGS_configuration_basename); // MapBuilder類是完整的SLAM算法類 // 包含前端(TrajectoryBuilders,scan to submap) 與 后端(用于查找回環的PoseGraph) auto map_builder = cartographer::mapping::CreateMapBuilder(node_options.map_builder_options);//在map_builder.cc中實現,工廠函數 //在這里,實例化一個MapBuilder, 而MapBuilder是MapBuilderInterface的子類 //MapBuilder的AddTrajectoryBuilder實例化了CollatedTrajectoryBuilder // c++11: std::move 是將對象的狀態或者所有權從一個對象轉移到另一個對象, // 只是轉移, 沒有內存的搬遷或者內存拷貝所以可以提高利用效率,改善性能.. // 右值引用是用來支持轉移語義的.轉移語義可以將資源 ( 堆, 系統對象等 ) 從一個對象轉移到另一個對象, // 這樣能夠減少不必要的臨時對象的創建、拷貝以及銷毀, 能夠大幅度提高 C++ 應用程序的性能. // 臨時對象的維護 ( 創建和銷毀 ) 對性能有嚴重影響. // Node類的初始化, 開啟訂閱,發布topic和service,將ROS的topic傳入SLAM, 也就是MapBuilder Node node(node_options, std::move(map_builder), &tf_buffer, FLAGS_collect_metrics); // 如果加載了pbstream文件, 就執行這個函數,為定位 if (!FLAGS_load_state_filename.empty()) { node.LoadState(FLAGS_load_state_filename, FLAGS_load_frozen_state); } // 使用默認topic 開始軌跡 if (FLAGS_start_trajectory_with_default_topics) { node.StartTrajectoryWithDefaultTopics(trajectory_options); } ::ros::spin(); // 結束所有處于活動狀態的軌跡 node.FinishAllTrajectories(); // 當所有的軌跡結束時, 再執行一次全局優化 node.RunFinalOptimization(); // 如果save_state_filename非空, 就保存pbstream文件 if (!FLAGS_save_state_filename.empty()) { node.SerializeState(FLAGS_save_state_filename, true /* include_unfinished_submaps */); } } } // namespace } // namespace cartographer_ros
Run函數主要做了一下幾件事:
讀取Lua配置文件中的內容,確定節點構造的方式和軌跡構造的方式與參數。
實例化map_builder,map_builder是完整的SLAM算法類,包含了前端和后端。具體時間方式是通過工廠模式。
初始化Node,通過初始化Node,開啟訂閱,發布topic與service,還將topic帶的傳感器數據傳入MapBuilder。
判斷是否為定位還是建圖,并開啟軌跡
死循環,不停地接受topic并運行Cartographer
結束時停止所用傳感器數據的訂閱,并且執行一次全局優化,保存pbstream地圖文件
其中std::tie很有意思,可以實現多個不同類型的返回值. 很多時候我們想通過一個函數丟出去多個結果,但一個函數只能有一個返回值,于是我們可以用std::make_tuple把多個返回值打包成std::tuple類型的數據,這時候返回值只是tuple類型了,所以沒有違反只能返回一個返回值的規定.這點很類似Python中的pickle和tuple,啥都可以裝在一起丟出去. 實現文件在node_options.cc
/** * @brief 加載lua配置文件中的參數 * * @param[in] configuration_directory 配置文件所在目錄 * @param[in] configuration_basename 配置文件的名字 * @return std::tuple<NodeOptions, TrajectoryOptions> 返回節點的配置與軌跡的配置 */ std::tuple<NodeOptions, TrajectoryOptions> LoadOptions( const std::string& configuration_directory, const std::string& configuration_basename) { // 獲取配置文件所在的目錄 auto file_resolver = absl::make_unique<cartographer::common::ConfigurationFileResolver>( std::vector<std::string>{configuration_directory}); // 讀取配置文件內容到code中 const std::string code = file_resolver->GetFileContentOrDie(configuration_basename); // 根據給定的字符串, 生成一個lua字典 cartographer::common::LuaParameterDictionary lua_parameter_dictionary( code, std::move(file_resolver)); // 創建元組tuple,元組定義了一個有固定數目元素的容器, 其中的每個元素類型都可以不相同 // 將配置文件的內容填充進NodeOptions與TrajectoryOptions, 并返回 return std::make_tuple(CreateNodeOptions(&lua_parameter_dictionary), CreateTrajectoryOptions(&lua_parameter_dictionary)); }
Cartographer_ros和Cartographer是兩個部分,一個是數據處理與分配,一個才是真正的Cartographer算法代碼的部分,代碼上把ros和算法庫分得很開,讓我們移植和開發很容易.那么如何讓ros數據和Cartographer算法建立聯系呢?第一步就是地圖構建器.
地圖構建器的大致作用是調用Cartographer的算法.
地圖構建器通過配置文件中node_options中map_builder_options部分去初始化一個地圖.這個地圖構建器的作用以后再說.先來看看他是怎么實現的.
由node_main.cc調用map_builder中的CreateMapBuilder函數,這個函數只有一個參數,就是上一行從lua中讀取的配置文件內容. 進入map_builder.cc中:
// 工廠函數,生成接口API std::unique_ptr<MapBuilderInterface> CreateMapBuilder( const proto::MapBuilderOptions& options) { return absl::make_unique<MapBuilder>(options); }
發現這個就是一個接口函數. 但這個函數也有用到一些cpp的技巧,值得學習:
返回值是一個unique_ptr的MapBuilder類型的類,而返回類型卻定于為MapBuilder的父類MapBuilderInterface類,這在cpp中是允許的,而且這樣做更能讓返回值類型更加有包容性,實現工廠模式.
MapBuilder這個類是SLAM算法的入口類十分重要,用來初始化pose_graph,創建軌跡等.會在另一篇中詳細介紹.
Node類的作用主要是傳感器數據的獲取和處理,讓數據與MapBuilder構建聯系,從而使獲取的raw sensor data能夠灌入Cartographer算法庫,實現定位建圖等功能.
在node_main.cc中初始化方式如下:
// Node類的初始化, 開啟訂閱,發布topic和service,將ROS的topic傳入SLAM, 也就是MapBuilder Node node(node_options, std::move(map_builder), &tf_buffer, FLAGS_collect_metrics);
這一行代碼也有值得學習的地方,就是std::move這個函數,他通過把某個實例化的類變為右值引用然后直接轉移給某個對象,從而實現高效的"轉移".
舉個簡單的不太恰當的例子,你想要我的西瓜,有兩種方式,一個是我不遠千里坐車給你,還有一種是給西瓜貼上你的名字,別人問我就說我說了不算,問你去. std::move就是后者(如有錯請指出哈).所以這樣可以直接從一個對象轉移到另一對象(貼名字),取消了不必要的臨時對象的創建拷貝與銷毀(運輸西瓜需要位子還要搬上搬下). 對于占用很大的類的轉移就很節約開銷(一億噸西瓜咋運啊).大致就這個意思.
Node類的內容在node.cc中,主要作用是實現傳感器數據的訂閱發布以及初始處理, 以及傳遞給mapbuilder.具體內容在后面會詳細介紹.
在上面實例化了Node類之后,我們就可以調用node中的方法去建圖. 建圖就不用加載地圖了,畢竟是建圖,所以直接調用node開始軌跡,然后在進入ros中的死循環,不停地接受新的數據,處理并運算,輸出結果, 直到按下ctrl+c去終止程序,跳出死循環,執行結束輸入數據和進行最終優化.
其實看程序就可以知道,Cartographer的建圖和定位是一樣的,只是建圖的時候不加載地圖并且在結束的時候保存地圖,定位的時候加載地圖,可以不保存地圖,也可不進行最終優化.其實我測試的不進行最終優化也是可以的,畢竟定位是實時的,就算最終優化使之前的定位結果有變化,機器人也回不去了.所以我認為是可以去掉的.
// 如果加載了pbstream文件, 就執行這個函數,為定位 if (!FLAGS_load_state_filename.empty()) { node.LoadState(FLAGS_load_state_filename, FLAGS_load_frozen_state); } // 使用默認topic 開始軌跡 if (FLAGS_start_trajectory_with_default_topics) { node.StartTrajectoryWithDefaultTopics(trajectory_options); } ::ros::spin(); // 結束所有處于活動狀態的軌跡 node.FinishAllTrajectories(); // 當所有的軌跡結束時, 再執行一次全局優化 node.RunFinalOptimization(); // 如果save_state_filename非空, 就保存pbstream文件 if (!FLAGS_save_state_filename.empty()) { node.SerializeState(FLAGS_save_state_filename, true /* include_unfinished_submaps */); }
LoadState作用是加載地圖文件.這個地圖不同于可以可視化的地圖,這個地圖里面包含了位姿圖pose_graph,傳感器數據和landmark_pose等其他信息,不單單是一個地形圖一樣的地圖.調用的最終函數是Cartographer算法部分的map_builder.cc中的同名函數,調用流程一環套一環(Cartographer整體框架就是這樣,復雜但都是必要的).調用的流程如下:
只有最后一層的map_builder.cc才是Cartographer算法部分的內容,才是真正實現加載地圖的功能. 這部分程序又臭又長,大家可以自己看看,實現功能加載posegraph和舊地圖的傳感器數據與landmark.
StartTrajectoryWithDefaultTopics實際上是調用了node.cc的AddTrajectory,去讓map_builder創建一個軌跡,并且新增位姿估計器,傳感器數據采樣器,訂閱topic以及調用回調函數的功能. 這個函數建立了數據與算法的統一. 詳細會在Node中解析.
FinishAllTrajectories調用node.cc中的FinishTrajectoryUnderLock去結束傳感器訂閱,然后調用map_builder的FinishTrajectory()進行軌跡的結束
node::RunFinalOptimization調用map_builder的pose_graph的RunFinalOptimization實現結束建圖后所有位姿圖的最終優化.
由此可見, Node類通過類方法,實現了傳感器數據的處理與使用.具體的方式是用了sensor_bridge和map_builder_bridge,把傳感器數據轉換并且給了Cartographer的算法部分, 實現了建圖與定位.
“C++ Cartographer的入口node main源碼分析”的內容就介紹到這里了,感謝大家的閱讀。如果想了解更多行業相關的知識可以關注億速云網站,小編將為大家輸出更多高質量的實用文章!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。