1. 程式人生 > >Navigation原始碼閱讀之base_local_planner

Navigation原始碼閱讀之base_local_planner

base_local_planner是直接決策小車以何種軌跡執行、下發何種速度的大類,可能由於開發的歷史原因,下面的檔案全部放在這個包裡,顯得有些雜亂,咱們慢慢捋一捋。

首先看看navigation包的nav_core包,nav_core是一個純虛類,裡面對base_local_planner進行了父類的虛擬函式宣告,它使用預設建構函式,initialize函式接受名字、tf、代價地圖進行“構造”,另外三個介面分別是setPlan(用來將全域性路徑傳到區域性規劃器),computeVelocityCommands(計算下發速度),以及isGoalReached(判斷本次導航是否結束)。這就是base_local_planner虛類的宣告。那麼來繼承這個類的,預設是DWAPlannerROS和TrajectoryPlannerROS以pluginlib的形式繼承,而我們一般使用DWA演算法進行速度計算而不是。dwa_local_planner已經作為一個獨立的包放在外面,這一塊就不再贅述。

我們從DWA演算法開始,依次看看呼叫的base_local_planner的函式流程:

一、從DWAPlannerROS::computeVelocityCommands開始:

1.呼叫了getLocalPlan函式,這個函式是LocalPlannerUtil::getLocalPlan,這個類的作用大概是對發過來的全域性路徑進行tf上的轉化以及裁剪,它依次呼叫的兩個函式均位於goal_functions.cpp中,這個檔案下全是工具函式,不從屬於類。下列均為工具函式:

(1)publishPlan這個函式是將區域性路徑發給rviz,我們在rviz中可以同時觀看兩個nav_msgs/Path格式的topic(全域性、區域性)。

(2)prunePlan將全域性路徑裁剪成前方一小段區域性路徑,採用的方法是用迭代器依次判斷與當前位置的距離,小於某距離時跳出迭代器迴圈,將路徑其他部分剔除。這個方法非常暴力,預設的前方距離是1m,這個數字不是可配置的rosparam,讓人費解。

(3)transformGlobalPlan是利用計算區域性代價地圖的範圍,將範圍之外的剔除,在區域性代價地圖內的路徑點的frame轉化為global_frame。

double dist_threshold = std::max(costmap.getSizeInCellsX() * costmap.getResolution() / 2.0,
                                       costmap.getSizeInCellsY() * costmap.getResolution() / 2.0);

(4)getGoalPose是將全域性路徑的最後一個元素取出,利用tf中的poseStampedMsgToTF獲取goal_pose的各引數,tf的具體操作方法我們之後再分析。

(5)isGoalReached是判斷兩個因素,一個是位置是否足夠接近終點(容忍度之內),一個是車是否停下(按照里程計反饋)。

2.回到computeVelocityCommands,它接著呼叫了DWAPlanner::updatePlanAndLocalCosts函式,而這個函式是依靠那幾個打分項活著的,檢查一下就可以發現該函式中出現的打分項有:path_costs_,goal_costs_,goal_front_costs_,alignment_costs_,一直在圍繞這些打分項做文章。這幾個打分項,其實都屬於base_local_planner::MapGridCostFunction類。轉到map_grid_cost_function.cpp,這裡詳細講述了打分前的準備工作。

(1)在updatePlanAndLocalCosts函式中呼叫的setTargetPoses介面其實就是將外部處理好的區域性路徑傳到成員變數而已,其他什麼都不做。

(2)其他成員函式都呼叫了MapGrid的物件,先轉到map_grid.cpp看看它的實現方式:

①MapGrid反覆使用了裝有MapCell的vector,再轉到map_cell.cpp(這呼叫層級有點厚)~

private:

  std::vector<MapCell> map_; ///< @brief Storage for the MapCells

②MapCell這個類是真的皮,預設建構函式和複製建構函式啥都不做,但是初始化列表裡說明了地圖體素距小車無限遠,暫時先這麼初始化吧。

MapCell::MapCell()
    : cx(0), cy(0),
      target_dist(DBL_MAX),
      target_mark(false),
      within_robot(false)
  {}

③回到MapGrid類,commonInit函式是將整個柵格地圖初始化(其實就是將每個體素塞進vector都標好位置),而getIndex函式就正好是找到vector的下標。sizeCheck函式是檢查柵格地圖是否填充正確,不對就再來一遍。

④MapGrid::updatePathCell是為了檢查小車前方的check_cell和目前位於的current_cell的距離,如果檢查出這個cell是障礙物那麼距離設為障礙物的型別。

unsigned char cost = costmap.getCost(check_cell->cx, check_cell->cy);
if(! getCell(check_cell->cx, check_cell->cy).within_robot &&
   (cost == costmap_2d::LETHAL_OBSTACLE ||
    cost == costmap_2d::INSCRIBED_INFLATED_OBSTACLE ||
    cost == costmap_2d::NO_INFORMATION))
{
  check_cell->target_dist = obstacleCosts();
  return false;
}

⑤MapGrid::adjustPlanResolution是為了根據地圖以及代價地圖的解析度對全域性路徑進行填充,防止路徑點過於稀疏。雖然函式看起來很花哨,但是仔細看就很容易發現意圖。

⑥MapGrid::setTargetCells是為了將global_plan經過的cells儲存佇列path_dist_queue中,這個佇列每當經過一個體素(隊頭)就刪除之,把新的體素塞在隊尾。MapGrid::setLocalGoal函式也類似,用來判斷小車下一時刻將通過哪個體素。

⑦MapGrid::computeTargetDistance函式是這個類的關鍵,它把區域性地圖裡的所有體素均和一個佇列中的元素(其實是路徑點)進行距離計算,但是沒有返回任何物件,有點費解。。。

(3)回到map_grid_cost_function.cpp,很多函式就容易理解了。MapGridCostFunction::scoreTrajectory是為了判斷路徑點前方的體素的評分,如果有錯誤返回負數。一個需要注意的點是,aggregationType_是一個列舉,它分別代表返回最後一點的值(如果沒有處於碰撞危險中)、所有點的和,或者所有非零點的乘積。

3.那麼再回到DWAPlannerROS::computeVelocityCommands,接下來呼叫了LatchedStopRotateController的物件,轉到latched_stop_rotate_controller.cpp分析它的實現:

(1)LatchedStopRotateController::stopWithAccLimits以及rotateToGoal中,加速度的上下限是可配置引數,它們是在一個模擬時間週期內計算出所需的線速度與角速度。

(2)至於具體實現的computeVelocityCommandsStopRotate,是通過里程計讀數的類物件odom_helper_的幫助來判斷小車是否停止旋轉。

①轉到odom_helper_ros.cpp,可以看到它是很簡單的通過接收base_controller的里程計讀數,值得注意的是訊息佇列只有一位,這意味著收一個就扔一個~

odom_sub_ = gn.subscribe<nav_msgs::Odometry>( odom_topic_, 1, boost::bind( &OdometryHelperRos::odomCallback, this, _1 ));

二、接著從computeVelocityCommands呼叫DWAPlannerROS::dwaComputeVelocityCommands,再接著呼叫DWAPlanner::findBestPath:

(1)obstacle_costs_是ObstacleCostFunction的物件,進入obstacle_cost_function.cpp,發現它的建構函式上來就要構建一個costmapModel,不明覺厲啊,趕緊轉到costmap_model.cpp:

①在標頭檔案中,我們可以看到footprintCost函式,看過costmap層級定義的朋友明白這些形參其實就是指小車位置、車輪數(圓形或方形)、內切圓半徑、外接圓半徑。

virtual double footprintCost(const geometry_msgs::Point& position, const std::vector<geometry_msgs::Point>& footprint,
          double inscribed_radius, double circumscribed_radius);

 而這個函式作用是判斷小車模型(圓或者方)最大可能的地圖代價。

②CostmapModel::lineCost是用一條線來檢查小車輪廓邊緣是否侵犯了代價地圖。

(2)ObstacleCostFunction::scoreTrajectory和前面的MapGrid類似,都是錯誤返回負數,並且正確情況分為最後一點的值與所有點的和。getScalingFactor是為了限制小車速度太大。

(3)回到DWAPlanner::findBestPath,設定好obstacle_costs之後,用一個SimpleTrajectoryGenerator初始化計算函式,轉到simple_trajectory_generator.cpp:

①SimpleTrajectoryGenerator::initialise其實就是藉助可配置引數,設定一個可選速度區間,用std::vector<Eigen::Vector3f>儲存。

②SimpleTrajectoryGenerator::generateTrajectory創造了一個時間微分,通過位置與速度計算函式,得到新的軌跡。這時候看一下trajectory.cpp,發現base_local_planner::Trajectory其實就是由三個vector組成,分別儲存X,Y和點之間的夾角。

③SimpleTrajectoryGenerator::computeNewPositions和computeNewVelocities函式均是通過速度區間求解下一個速度與位置。

(4)回到DWAPlanner::findBestPath,接著是SimpleScoredSamplingPlanner類物件scored_sampling_planner_,轉到simple_scored_sampling_planner.cpp,findBestTrajectory是對外呼叫的介面,它通過遍歷all_explored這個儲存Trajectory的vector來尋找最優解,那麼評分的標準在SimpleScoredSamplingPlanner::scoreTrajectory中:

①scoreTrajectory在軌跡上執行所有評分函式,建立正成本的加權和,一旦發現負成本或負成本之和大於正的._traj_cost累積值,就中止。

三、執行到這裡,結果就很明確了,由於result_traj_是由三個vector組成,那分別取其中的xv_,yv_,thetav_,就可以得到本次週期所需要的速度了。當然用一個tf::Pose來返回這個cmd_vel的數值,也是很費解了,明明可以用一個matrix更便於理解,當然可能穩定性稍差。

這就是move_base——dwa_local_planner——base_local_planner的流程,用多層級、分散式的軟體架構實現了導航演算法~

相關推薦

Navigation原始碼閱讀base_local_planner

base_local_planner是直接決策小車以何種軌跡執行、下發何種速度的大類,可能由於開發的歷史原因,下面的檔案全部放在這個包裡,顯得有些雜亂,咱們慢慢捋一捋。 首先看看navigation包的nav_core包,nav_core是一個純虛類,裡面對base_loc

Navigation原始碼閱讀dwa_local_planner(DWA動態視窗法)

DWAPlannerROS是封裝類,提供了與move_base的介面,而DWAPlanner是具體實現類,它非常依賴costmap(當然不指望讓小車動態避障的話就無所謂了),因此我們在使用時需要保證代價地圖的膨脹度以及實時更新頻率。btw:在此類程式碼中,基本上下反覆使用的變

Promise原始碼閱讀建構函式+then過程

前言 Promise是非同步程式設計的一種方案,ES6規範中將其寫入規範標準中,統一了用法。 考慮到瀏覽器的相容性,Vue專案中使用promise,就具體閱讀promise原始碼,看看內部的具體實現。 具體分析 通過具體例項來閱讀promise原始碼的實現,例項如下: new

Netty 原始碼閱讀初始環境搭建

推薦 netty 系列原始碼解析合集 http://www.iocoder.cn/Netty/Netty-collection/?aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3R6c18xMDQxMjE4MTI5L2FydGljbGUvZGV0YWlscy83OD

jdk原始碼閱讀——arraylist

首先看一下他的建構函式: public ArrayList() { this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA; } 其實arraylist還有其他的建構函式,可以指定陣列的長度,這裡先從最基本的入

netty原始碼閱讀效能優化工具類Recycle異執行緒獲取物件

在這篇《netty原始碼閱讀之效能優化工具類之Recycler獲取物件》文章裡面,我們還有一個scavenge()方法沒有解析,也就是在別的執行緒裡面回收物件。下面我們開始介紹,從這個方法開始進入: boolean scavenge() { // con

我的原始碼閱讀路:redux原始碼剖析

前言 用過react的小夥伴對redux其實並不陌生,基本大多數的React應用用到它。一般大家用redux的時候基本都不會單獨去使用它,而是配合react-redux一起去使用。剛學習redux的時候很容易弄混淆redux和react-redux,以為他倆是同一個

netty原始碼閱讀解碼值基於固定長度解碼器分析

固定長度解碼器FixedLengthFrameDecoder比較簡單,我們看下它類的註釋: /** * A decoder that splits the received {@link ByteBuf}s by the fixed number * of bytes.

netty原始碼閱讀解碼基於長度域解碼器引數分析

這篇文章我們放鬆一點,只分析基於長度域解碼器的幾個引數, lengthFieldOffset :長度域的偏移量,也就是長度域要從什麼地方開始 lengthFieldLength:長度域的長度,也就是長度域佔多少個位元組 lengthAdjustment:長度域的值的調整

netty原始碼閱讀解碼基於長度域解碼器分析

基於長度域解碼器LengthFieldBasedFrameDecoder我們主要分析以下三點: 1、計算需要抽取的資料包的長度 2、跳過位元組邏輯處理 3、丟棄模式下的處理 首先原始碼還是LengthFieldBasedFrameDecoder的decode方法:

netty原始碼閱讀編碼MessageToByteEncoder

MessageToByteEncoder的write過程,我們分析以下幾步: 1、匹配物件 2、分配記憶體 3、編碼實現 4、釋放物件 5、傳播資料 6、釋放記憶體 原始碼在這裡: @Override public void write(Cha

netty原始碼閱讀效能優化工具類FastThreadLocal的使用

先說明FastThreadLocal使用的效果。 1、比jdk原生的ThreadLocal的快 2、不同執行緒之間能保證執行緒安全 這是我們的使用者程式碼: public class FastThreadLocalTest { private static F

netty原始碼閱讀效能優化工具類FastThreadLocal的建立

建立的話我們直接從FastThreadLocal的構造方法進入: public FastThreadLocal() { index = InternalThreadLocalMap.nextVariableIndex(); } 可見他是現

JAVA原始碼閱讀java.util—List

List List被宣告為一個介面,程式碼量很少,只聲明瞭方法。 public interface List<E> extends Collection<E> { int size(); boolean isEmpty(); boo

netty原始碼閱讀效能優化工具類Recycler獲取物件

 Recycler獲取物件主要分為以下幾部分: 1、獲取當前執行緒的Stack 2、從Stack裡面彈出物件 3、如果彈出物件為空,那就建立物件並且繫結到Stack裡面 我們從Recycler的get方法進入,就是這個原始碼: @SuppressWarnin

jdk原始碼閱讀Object類

Object的作用 Object是java所有類的基類,定義了所有類的基礎方法 。這個類所定義的方法也不多,大部分是native方法。 什麼是native方法 native關鍵字標識的java方法為本地方法,底層是有c/c++編寫的程式編譯後dll檔案,jav

JDK原始碼閱讀ArrayList

ArrayList簡介 List 介面的大小可變陣列的實現。實現了所有可選列表操作,並允許包括 null 在內的所有元素。除了實現 List 介面外,此類還提供一些方法來操作內部用來儲存列表的陣列的大小。(此類大致上等同於 Vector 類,除了此類是不同步

[kafka掃盲]---(7)kafka原始碼閱讀生產者客戶端緩衝池

Author:趙志乾 Date:2018-10-21 Declaration:All Right Reserved!!! BufferPool.java 1、檔案位置: 該檔案在原始碼中的位置:kafka-2.0.0-src/clients/src/main/java/

JDK原始碼閱讀HashMap

HashMap簡介 基於雜湊表的 Map 介面的實現。此實現提供所有可選的對映操作,並允許使用 null 值和 null 鍵。(除了非同步和允許使用 null 之外,HashMap 類與 Hashtable 大致相同。)此類不保證對映的順序,特別是它不保證該

原始碼閱讀Ordering

注重greatestOf 的演算法實現 舉個栗子: List<Integer> listInt = Lists.newArrayList(4, 2, 0, 1, 3); List<String> listString = Lists.newArrayList("abc