<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
啃一下谷歌優秀的鐳射SLAM開源框架-Cartographer. 這個框架演演算法簡單,但是程式部分太多需要學習的地方了.不論是整體框架的結構,還是資料的使用,都是非常優美的.不愧是大公司啊.接下來記錄一下每天學習的內容和心得,督促自己堅持下去!
node_main.cc是整個Cartographer程式的入口,用來呼叫整個Cartographer程序。以最基礎的單線雷達和輪速計為例。
整體的程式碼開始是在Run函數中實現的。
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函數主要做了一下幾件事:
其中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詳細講解的文章就介紹到這了,更多相關C++ node_main內容請搜尋it145.com以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援it145.com!
相關文章
<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
综合看Anker超能充系列的性价比很高,并且与不仅和iPhone12/苹果<em>Mac</em>Book很配,而且适合多设备充电需求的日常使用或差旅场景,不管是安卓还是Switch同样也能用得上它,希望这次分享能给准备购入充电器的小伙伴们有所
2021-06-01 09:31:42
除了L4WUDU与吴亦凡已经多次共事,成为了明面上的厂牌成员,吴亦凡还曾带领20XXCLUB全队参加2020年的一场音乐节,这也是20XXCLUB首次全员合照,王嗣尧Turbo、陈彦希Regi、<em>Mac</em> Ova Seas、林渝植等人全部出场。然而让
2021-06-01 09:31:34
目前应用IPFS的机构:1 谷歌<em>浏览器</em>支持IPFS分布式协议 2 万维网 (历史档案博物馆)数据库 3 火狐<em>浏览器</em>支持 IPFS分布式协议 4 EOS 等数字货币数据存储 5 美国国会图书馆,历史资料永久保存在 IPFS 6 加
2021-06-01 09:31:24
开拓者的车机是兼容苹果和<em>安卓</em>,虽然我不怎么用,但确实兼顾了我家人的很多需求:副驾的门板还配有解锁开关,有的时候老婆开车,下车的时候偶尔会忘记解锁,我在副驾驶可以自己开门:第二排设计很好,不仅配置了一个很大的
2021-06-01 09:30:48
不仅是<em>安卓</em>手机,苹果手机的降价力度也是前所未有了,iPhone12也“跳水价”了,发布价是6799元,如今已经跌至5308元,降价幅度超过1400元,最新定价确认了。iPhone12是苹果首款5G手机,同时也是全球首款5nm芯片的智能机,它
2021-06-01 09:30:45