膨胀层

InflationLayer的构造及初始化方法和StaticLayer类似。该层是基于已有地图进行膨胀,因此不需要有缺省值。由Layer类单独派生出InflationLayer类. 用于执行每个cell的障碍物膨胀,由于它的父类中不含Costmap2D类,所以其实膨胀层自身没有栅格地图要维护,这一点和另外两层有区别。

  • inflation_radius (double, default: 0.55) 障碍物在地图中向外扩展的膨胀区的半径
  • cost_scaling_factor (double, default: 10.0) 膨胀过程中应用到代价值的比例因子。这个值越大,代价衰减越严重

膨胀层加载过程.png
膨胀层所用的参数:inflation params

可以修改膨胀层代码 inflation_layer.h, 添加函数double getInflationRadius() constdouble getInscribedRadius() const

每个周期传感器数据进来后,都要在代价地图底层占用结构上执行标记和清除障碍操作,并且这种结构会被投影到代价地图附上相应代价值。 这完成之后,对代价赋值为costmap_2d::LETHAL_OBSTACLE的每个cell执行障碍物的膨胀操作,即从每个代价cell向外传播代价值,直到用户定义的膨胀半径为止。这里确实只需要对状态为LETHAL_OBSTACLE的cell进行膨胀操作即可。


ldd, nm, c++filt, ldconfig命令

ldd

命令的作用是打印共享库的依赖关系

  1. ldd不是一个可执行程序,而只是一个shell脚本。ldd能够显示可执行模块的 dependency,其原理是通过设置一系列的环境变量,如下:LD_TRACE_LOADED_OBJECTS、LD_WARN、LD_BIND_NOW、LD_LIBRARY_VERSION、 LD_VERBOSE等。

  2. ldd的工作原理,其实质是通过ld-linux.so(elf动态库的装载器)来实现的。ld-linux.so模块会先于executable模块程序工作,并获得控制权,因此当上述的那些环境变量被设置时,ld-linux.so选择了显示可执行模块的dependency。
    实际上可以直接执行ld-linux.so模块,如:/lib/ld-linux.so.2 --list program(这相当于ldd program)

1
2
/usr/bin/ld: skipping incompatible /home/user/lib/libtinyalsa.so when searching for -ltinyalsa
/usr/bin/ld: cannot find -ltinyalsa: No such file or directory

so库的架构不匹配,可以通过file name.so查看so库的架构,也可以使用readelf命令查看so文件架构信息的示例: readelf -h libexample.so。输出so文件的头部信息,其中包括Machine字段,即架构信息。Machine字段的值对应于不同的架构,比如x86、aarch64、MIPS等。


编译时常常会出错: undefine reference XXX,显然是不识别函数,比如 _ZN9fast_gicp7NDTCudaIN3pcl9PointXYZIES2_EC1Ev,但是这个名称看得不清晰,看不出原来的函数原型。

先执行nm -D libhdl_localization_nodelet.so | grep NDTCuda | grep EC1Ev,输出

1
U  _ZN9fast_gicp7NDTCudaIN3pcl9PointXYZIES2_EC1Ev

再执行命令 c++filt _ZN9fast_gicp7NDTCudaIN3pcl9PointXYZIES2_EC1Ev (去掉前面的 U), 输出函数原型

1
fast_gicp::NDTCuda<pcl::PointXYZI, pcl::PointXYZI>::NDTCuda()

c++filt命令确实太好用了。

ldconfig

有时装完某个库后,需要使用命令sudo ldconfig -v,否则在程序运行后会出现找不到动态库的问题: error while loading shared libraries: liblog4cpp.so.5: cannot open shared object file: No such file or directory

ldconfig是一个动态链接库管理命令,为了让动态链接库为系统所共享,还需运行动态链接库的管理命令

ldconfig通常在系统启动时运行,而当用户安装了一个新的动态链接库时,就需要手工运行这个命令.主要是在默认搜寻目录(/lib/usr/lib)以及动态库配置文件/etc/ld.so.conf内所列的目录下,搜索出可共享的动态链接库(格式如lib*.so*),进而创建出动态装入程序(ld.so)所需的连接和缓存文件.

ldconfig与运行程序时有关,跟编译时一点关系都没有。 不管做了什么动态库的变动后,最好都ldconfig一下

问题 error while loading shared libraries … file too short

这个错误应该是从别处拷贝so文件过来时出现了软链接错误。删除,重新创建这个软连接即可。
比如A,B,C的链接关系: A --> B, B --> C。先把C重命名,删掉A,B。重新建立软链接

1
2
ln -s C B
ln -s B A


move_base 所有参数
  • base_global_planner: 指定全局规划器

  • base_local_planner: 指定局部规划器

  • recovery_behaviors: true 是否允许恢复行为,恢复行为为原地旋转。 有时候不规则的机器人做恢复行为是比较危险的,容易发生碰撞,圆形机器人不会有这个问题

  • shutdown_costmaps: false 当 move_base进入inactive状态时候,决定是否停用节点的costmap (bool, default: false)

  • controller_frequency: 20.0 控制器频率,即向基座发送速度命令的频率

  • planner_patience: 5.0 在空间清理操作执行前,路径规划器等待多长时间(秒)用来找出一个有效规划 (double, default: 5.0)

  • controller_patience: 20.0 在空间清理操作执行前,控制器会等待多长时间(秒)用来找出一个有效控制 (double, default: 15.0)

  • planner_frequency: 5.0 全局路径规划的更新速率,可以是0. 当机器在避障表现较好时,此参数较小即可。 如果local planner问题很大,再频繁更新全局路径,可能会离原先的最优路径越来越远,直到陷入局部障碍物

  • oscillation_timeout: 10.0 执行修复操作之前,允许的震荡时间是几秒

  • oscillation_distance: 0.5 机器人需要移动多少距离才算作没有震荡

  • conservative_reset_dist: 3.0 当在地图中清理出空间时候,距离机器人几米远的障碍将会从costmap清除。 实验中发现局部地图经常会有移动障碍物遗留,并且不会去除,只有在移动后才能清除,部分只能在恢复行为后清除,所以有一个插件是专门用来做local障碍物清除的
    剩余参数用于无法行动之后的恢复行为,震荡表示机器人在很短的距离反复运动,可以设置距离、时间来判断是否为震荡以及多久后执行恢复操作

move_base有很多unknown type的订阅

1
2
3
4
5
* /footprint [unknown type]
* /footprint_radius [unknown type]
* /move_base/cancel [unknown type]
* /move_base/goal [unknown type]
* /move_base_simple/goal [unknown type]

但是用rostopic info topic_name查看,没有出现 unknown type,怀疑是ROS的bug


move_base分析(五) planService和makePlan函数

move_base源码没有获取机器人当前位姿的地方,这属于局部路径算法的范畴。只能获得全局目标的位姿

bool MoveBase::makePlan(const geometry_msgs::PoseStamped& goal, vector& plan)

此函数只在一个地方被调用,就是planThread()线程被唤醒之后开始规划路径

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
boost::unique_lock<costmap_2d::Costmap2D::mutex_t> lock(*(planner_costmap_ros_->getCostmap()->getMutex() ));
plan.clear();
if(planner_costmap_ros_ == NULL) {
log.error("Planner costmap ROS is NULL, unable to create global plan");
return false;
}
//get the starting pose of the robot
tf::Stamped<tf::Pose> global_pose;
if(!planner_costmap_ros_->getRobotPose(global_pose)) {
log.warn("Unable to get starting pose of robot, unable to create global plan");
return false;
}
geometry_msgs::PoseStamped start;
tf::poseStampedTFToMsg(global_pose, start);

// 这里就是 GlobalPlanner::makePlan 了
if(!planner_->makePlan(start, goal, plan) || plan.empty()){
ROS_WARN("[move_base] Failed to find a plan to point (%.2f, %.2f)",
goal.pose.position.x, goal.pose.position.y);
return false;
}
return true;

代码并不复杂, 最后实际就进入bool GlobalPlanner::makePlan(const geometry_msgs::PoseStamped& start, const geometry_msgs::PoseStamped& goal, double tolerance, std::vector<geometry_msgs::PoseStamped>& plan)

这里要参考全局路径规划(二) makePlan


MoveBase::planService(nav_msgs::GetPlan::Request &req, nav_msgs::GetPlan::Response &resp)

req参数包含了起点和目标信息,这个服务回调函数的的核心是planner_->makePlan

  1. move_base server必须在inactive状态,否则不向下执行

  2. 判断global planner的costmap是否存在;若req给定机器人初始位姿则使用,否则使用getRobotPose获得机器人所在位置做初始位姿

  3. 调用clearCostmapWindows完成对机器人区域的clear, clear_radius由参数设置

  4. 调用 if(!planner_->makePlan(start, req.goal, global_plan) || global_plan.empty()) 这是完成plan计算的核心部分。判断这个调用是否成功,如果失败,则在目标区域附近搜索,多次更改req.goal的值,并重新调用makePlan;新的goal如下图:

各个可能位置之间的水平和竖直间距和req.tolerance正相关

如果还是失败,则此次路径规划无解。如果成功,直接break循环,把原来无法达到的目标点插入global_plan容器的最后,local planner一般会从新的目标点导航到原目标点.

  1. global_plan拷贝到resp.plan

MoveBase::clearCostmapWindows(double size_x, double size_y)

planService·用到了clearCostmapWindows函数,它只被调用了这一次,过程如下:

  • 通过planner_costmap_ros_->getRobotPose(global_pose); 获取在全局地图的global_pose
  • 以这个点为中心,找到以size_x和size_y为边长的矩形的四个顶点

  • 调用planner_costmap_ros_->getCostmap()->setConvexPolygonCost(clear_poly, costmap_2d::FREE_SPACE);完成对机器人所在区域的clear工作

  • 以上同样的操作在controller_costmap_ros上也操作一遍,这样globa costmap 和local costmap都已经在机器人所在的区域完成clear工作


障碍层 (一) 配置和常见问题

障碍层地图通过订阅传感器话题,将传感器输出的障碍物信息存进buffer(剔除过高、过远的点),在本层地图上将观测到的点云标记为障碍物,将传感器到点云的连线上的点标记为FREE_SPACE。最后在bound范围内,将本层地图合并到主地图上。

配置参数

通用代价地图中的障碍层常常是这样设置的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
obstacles:
enabled: true
obstacle_range: 2.0
raytrace_range: 5.0
track_unknown_space: true
combination_method: 1
observation_sources: laser_scan_sensor pointcloud2_sensor
laser_scan_sensor:
data_type: LaserScan
topic: /scan
marking: true
clearing: true
max_obstacle_height: 1.0
min_obstacle_height: -1.0

pointcloud2_sensor:
topic: /people_cloud
data_type: PointCloud2
sensor_frame: "/base_link"
obstacle_range: 5.0
raytrace_range: 5.0
observation_persistence: 5.0
marking: true
clearing: true
min_obstacle_height: 0.0
max_obstacle_height: 2.0

  • observation_persistence: 2.0. 如果设置为0,代价地图只考虑最近的观测数据

  • min_obstacle_height: 传感器读数的最小高度(以米为单位)视为有效。通常设置为地面高度。决定是否将地面的部分加入到代价地图,默认0

车周围实际没有障碍物,不同参数的情况如下:
min_obstacle_height=0.png
这种情况下,避障太保守了。
min_obstacle_height=0.1 部分地面加入代价地图.png
min_obstacle_height=0.2 地面未加入代价地图.png

  • max_obstacle_height: 默认2,单位米。插入代价地图的障碍物的最大高度,应当比机器人高度稍微高一点,应该是用于有机械臂的情况。设置为大于全局max_obstacle_height参数的值将会失效,设置为小于全局max_obstacle_height的值将过滤掉传感器上大于该高度以的点。

  • obstacle_range: 设置机器人检测障碍物的最大范围,意思是说超过该范围的障碍物,并不进行检测,只有靠近到该范围内才把该障碍物当作影响路径规划和移动的障碍物。对能否及早发现障碍物至关重要 ,跟车速也有关系。 如果车速0.6,obstacle_range设置为1.5,连正常避障都实现不了,至少也得3

  • raytrace_range: 在机器人移动过程中,实时清除代价地图上的障碍物的最大范围,更新可自由移动的空间数据。假如设置该值为3米,那么就意味着在3米内的障碍物,本来开始时是有的,但是本次检测却没有了,那么就需要在代价地图上来更新,将旧障碍物的空间标记为可以自由移动的空间

增大raytrace_rangeobstacle_range会增大数据量,影响代价地图刷新

  • inf_is_valid: 默认是false,但是对点云障碍无效,仅对雷达scan有效:
1
2
3
4
if (inf_is_valid)
{
ROS_WARN("obstacle_layer: inf_is_valid option is not applicable to PointCloud observations.");
}

障碍层加载过程.png
障碍层所用的参数:obstacles params

不能将近处障碍添加到代价地图


调来调去,发现min_obstacle_height还是要设置为-0.3比较合适,这时因为地面的原因,导致局部代价地图的四角都有了障碍,因此再降低obstacle_range避免这一现象
obstacle_range大于2.5.png

obstacle_range=2.2.png

局部代价地图中无法清除不在视野的障碍

costmap raytraces to clear out obstacles. 障碍物要想从地图上清除,它所占据的空间必须被新的观测看到。但是参数observation_persistence常常无效。

last_updated_ is updated on every new cloud that needs to be added to the buffer. purgeStaleObservations函数 checks the cloud’s time stamp with last_updated_. When we stop publishing a point cloud last_updated_ will never be updated. Hence the observations will not time-out and remain in the observation_list_. This may result in the following warning, when the robot drives away from the clouds in observation_list_: Sensor origin at (x, y) is out of map bounds. The costmap cannot raytrace for it

只适用于ROS2的navigation

将文件observation_buffer.cpp中的函数purgeStaleObservations中的last_updated_改为nh_->now(),会影响共享库liblayers.solibcostmap_2d.so

1
2
nav2_util::LifecycleNode::SharedPtr  nh
last_updated_(nh->now())


各种常用雷达
abstract Welcome to my blog, enter password to read.
Read more
(十三) optimizeTEB 4 图优化的过程

图优化 optimizeGraph

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
// clear_after 调用时写死为false
bool TebOptimalPlanner::optimizeGraph(int no_iterations, bool clear_after)
{
if (!teb_.isInit() || teb_.sizePoses() < cfg_->trajectory.min_samples)
{
ROS_WARN("optimizeGraph(): TEB is empty or has too less elements. Skipping optimization.");
if (clear_after)
clearGraph();
return false;
}
// 头文件定义 boost::shared_ptr<g2o::SparseOptimizer> optimizer_;
// 开始图优化
optimizer_->setVerbose(cfg_->optim.optimization_verbose);
optimizer_->initializeOptimization();
int iter = optimizer_->optimize(no_iterations); // 优化结束

// 可以保存 海森矩阵
/* g2o::OptimizationAlgorithmLevenberg lm =
dynamic_cast<g2o::OptimizationAlgorithmLevenberg*>(optimizer_->solver());
lm->solver()->saveHessian("~/Matlab/Hessian.txt");*/
if (!iter)
{
ROS_ERROR("optimizeGraph(): Optimization failed! iter=%i", iter);
return false;
}
if (clear_after)
clearGraph();

return true;
}

再回到optimizeTEB,剩余部分是这样的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
success = optimizeGraph(iterations_innerloop, false);
if (!success)
{
clearGraph();
return false;
}
optimized_ = true;
// 只在最后一次外循环时 compute cost vec
if (compute_cost_afterwards && i==iterations_outerloop-1)
computeCurrentCost(obst_cost_scale, viapoint_cost_scale, alternative_time_cost);

clearGraph();
weight_multiplier *= cfg_->optim.weight_adapt_factor;
// iterations_outerloop 结束

computeCurrentCost

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
void TebOptimalPlanner::computeCurrentCost( double obst_cost_scale, 
double viapoint_cost_scale, bool alternative_time_cost)
{
// check if graph is empty/exist, important if function is called
// between buildGraph and optimizeGraph/clearGraph
bool graph_exist_flag(false);
if (optimizer_->edges().empty() && optimizer_->vertices().empty())
{
// here the graph is build again, for time efficiency make sure to call this function
// between buildGraph and Optimize (deleted), but it depends on the application
buildGraph();
optimizer_->initializeOptimization();
}
else
graph_exist_flag = true;

optimizer_->computeInitialGuess();
// cost_ 在 TebOptimalPlanner构造函数里 初始化为 HUGE_VAL
cost_ = 0;
if (alternative_time_cost)
{
cost_ += teb_.getSumOfAllTimeDiffs();
/* we use SumOfAllTimeDiffs() here, because edge cost depends on
number of samples, which is not always the same for similar TEBs,
since we are using an AutoResize Function with hysteresis */
}
// now we need pointers to all edges -> calculate error for each edge-type
// since we aren't storing edge pointers, we need to check every edge
for (std::vector<g2o::OptimizableGraph::Edge*>::const_iterator it =
optimizer_->activeEdges().begin(); it!= optimizer_->activeEdges().end(); it++)
{
double cur_cost = (*it)->chi2();

if (dynamic_cast<EdgeObstacle*>(*it) != nullptr
|| dynamic_cast<EdgeInflatedObstacle*>(*it) != nullptr
|| dynamic_cast<EdgeDynamicObstacle*>(*it) != nullptr)
{
cur_cost *= obst_cost_scale;
}
else if (dynamic_cast<EdgeViaPoint*>(*it) != nullptr)
cur_cost *= viapoint_cost_scale;
else if (dynamic_cast<EdgeTimeOptimal*>(*it) != nullptr && alternative_time_cost)
{
// skip these edges if alternative_time_cost is active
continue;
}
cost_ += cur_cost;
}
// delete temporary created graph
if (!graph_exist_flag)
clearGraph();
}

相应的有个TebOptimalPlanner::getCurrentCost()返回每次图优化计算的代价,而它又用于HomotopyClassPlanner::computeCurrentCostHomotopyClassPlanner::selectBestTeb()

  • obst_cost_scale Specify extra scaling for obstacle costs (only used if compute_cost_afterwards is true)

  • viapoint_cost_scale Specify extra scaling for via-point costs (only used if compute_cost_afterwards is true)

  • alternative_time_cost Replace the cost for the time optimal objective by the actual (weighted) transition time (only used if compute_cost_afterwards is true)

总结

optimizeTEB包括两个循环:

  1. 通过调用TimedElasticBand::autoResize(),外循环resizes the trajectory

  2. optimizeGraph()是内循环部分,调用solver(使用g2o的sparse Levenberg-Marquardt方法) 遍历一定次数的optimization calls

  3. 内外循环次数的比例defines the contraction behavior and convergence rate of the trajectory optimization. 2~6个内循环足够了

  • TebOptPlannerContainer tebs_; : Container that stores multiple local teb planners (for alternative equivalence classes) and their corresponding costs

The number of outer loop iterations should be determined by considering the maximum CPU time required to match the control rate.

Optionally, the cost vector can be calculated by specifyingcompute_cost_afterwards, the cost vector can be accessed afterwards using getCurrentCost(). 目前使用TebOptimalPlanner不会计算代价,使用HomotopyClassPlanner会,第一次计算时,代价是一个很大很大的数,第二次减小很多,之后逐渐减小到1左右或者更小


代价地图的层(一) 概述

所有的层都是继承Layer类,这个类本身比较简单,是一个虚基类,定义了两个重要接口:
updateBoundsupdateCosts

1
2
3
4
5
6
7
8
9
10
11
12
13
// 每层的初始化
void Layer::initialize(LayeredCostmap* parent, std::string name,
tf::TransformListener *tf)
{
layered_costmap_ = parent;
name_ = name;
tf_ = tf;
onInitialize(); //虚函数,空
}
const std::vector<geometry_msgs::Point>& Layer::getFootprint() const
{
return layered_costmap_->getFootprint();
}

调用在Costmap2DROS的构造函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
if (!private_nh.hasParam("plugins") )
resetOldParameters(private_nh);

if (private_nh.hasParam("plugins") )
{
XmlRpc::XmlRpcValue my_list;
private_nh.getParam("plugins", my_list);
for (int32_t i = 0; i < my_list.size(); ++i)
{
std::string pname = static_cast<std::string>(my_list[i]["name"]);
std::string type = static_cast<std::string>(my_list[i]["type"]);
ROS_INFO("Using plugin \"%s\"", pname.c_str() );

// plugin_loader_ 类型是 pluginlib::ClassLoader<Layer>,ROS插件机制
boost::shared_ptr<Layer> plugin = plugin_loader_.createInstance(type);
// 添加到容器 plugins_
layered_costmap_->addPlugin(plugin);
// 执行 Layer::initialize,它向各层地图通知主地图的存在,并调用
// oninitialize方法(Layer类中的虚函数,被定义在各层地图中)
plugin->initialize(layered_costmap_, name + "/" + pname, &tf_);
}
}

这里得到的插件即为各层子地图。如果是新定义的层,至少需要对虚函数onInitialize覆盖

Layer的所有虚函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/* called by the LayeredCostmap to poll this plugin as to how
much of the costmap it needs to update.
Each layer can increase the size of this bounds.
*/
virtual void updateBounds(double robot_x, double robot_y, double robot_yaw,
double* min_x, double* min_y, double* max_x, double* max_y) {}

/** Actually update the underlying costmap, only within the bounds
* calculated during UpdateBounds(). */
virtual void updateCosts(Costmap2D& master_grid, int min_i, int min_j, int max_i, int max_j) {}

/* Stop publishers. */
virtual void deactivate() {}
/* Restart publishers if they've been stopped. */
virtual void activate() {}
virtual void reset() {}
/* make this layer match the size of the parent costmap. */
virtual void matchSize() {}
/** LayeredCostmap calls this whenever the footprint there
* changes (via LayeredCostmap::setFootprint()).
Override to be notified of changes to the robot's footprint. */
virtual void onFootprintChanged() {}
virtual void onInitialize() {}

  • 聚合layer的功能:LayeredCostmap* layered_costmap_
  • 此layer是否被使能,是否当前(更新信息):bool enabled_,current
  • layer名:std::string name_,
  • 小车: std::vector< geometry_msgs::Point> footprint_spec_,

三层的加载过程

Costmap2DROS::mapUpdateLoop —— Costmap2DROS::updateMap —— LayeredCostmap::updateMap —— 每一层调用updateBounds —— 每一层调用updateCosts(核心函数)


常用日志的宏

总共有二十多个宏,我只列出有价值而且容易用的

1
2
3
4
5
6
7
#define ROS_INFO(...)
#define ROS_INFO_COND(cond, ...)
#define ROS_INFO_ONCE(...)
#define ROS_INFO_STREAM(args)
#define ROS_INFO_NAMED(name, ...)
#define ROS_INFO_THROTTLE(rate, ...)
#define ROS_INFO_FILTER(filter, ...)

ROS_INFO_ONCE

适用于循环内,防止不断产生日志

1
2
for(int i=0; i<3; i++)
ROS_INFO_ONCE("1111111");

结果只有一个1111111

ROS_INFO_COND

ROS_INFO_COND(2>1, "log here");

ROS_INFO_STREAM

其实就是C++风格的ROS_INFO:
ROS_INFO_STREAM("velocity command: " << " linear=" << msg. linear . x << " angular=" << msg. angular . z)


全局路径规划(二) makePlan

makePlan

1
2
3
bool GlobalPlanner::makePlan(const geometry_msgs::PoseStamped& start,
const geometry_msgs::PoseStamped& goal,
std::vector<geometry_msgs::PoseStamped>& plan)

planService中计算导航路径用到makePlan函数,但planner_boost::shared_ptr<nav_core::BaseGlobalPlanner>,也就是抽象类,实际调用的函数是派生类的override函数bool GlobalPlanner::makePlan(start, goal, plan),内容相当复杂。

实际调用是重载函数makePlan(start, goal, default_tolerance_, plan);, 参数defaulttoleranceGlobalPlanner::initialize中由参数服务器的参数default_tolerance决定. 但是GlobalPlanner::makePlan实际没有处理default_tolerance参数,是Navfn::makePlan处理了。也就是说终点不合适时,GlobalPlanner不会改变全局路径终点

GlobalPlanner::makePlan 重载函数如下:

1
2
3
4
5
6
7
boost::mutex::scoped_lock lock(mutex_);
if (!initialized_) {
ROS_ERROR( "This planner has not been initialized yet, but it is being used,
please call initialize() before use");
return false;
}
plan.clear();

initialized_GlobalPlanner::initialize赋值true, frame_id_实际是GlobalPlanner::initialize函数中的costmap_ros->getGlobalFrameID()

继承关系 2.png

最重要的函数是 calculatePotentials 和 getPlanFromPotential

calculatePotentials

计算所有的可行点

1
2
3
bool found_legal = planner_->calculatePotentials(costmap_->getCharMap(), 
start_x, start_y, goal_x, goal_y,
nx*ny*2, potential_array_ );

实现方法显然是DijkstraExpansion::calculatePotentialsQuadraticCalculator::calculatePotential

getPlanFromPotential 和 getPath

getPlanFromPotential涉及到Traceback::virtual bool getPath,这是一个纯虚函数,实现有两种: class GradientPath : public Traceback(梯度路径), class GridPath : public Traceback(栅格路径)

GradientPath::getPath // 从可行点中计算路径path

OrientationFilter

核心是processPath函数,可以理解为给生成的 path 顺毛,因为A*算法规划的path只能保证路线的连续性,还要保证拐弯的角度别变得太快。 但是setMode设定模式只有在GlobalPlanner::reconfigureCB里使用

参考:
为ROS navigation功能包添加自定义的全局路径规划器
官方wiki教程-使用自己的路径规划算法
ROS插件机制