概述
eband_local_planner.h/cpp 解读
- eband_->setplan()
- refineBand()
- removeAndFill()
- interpolateBubbles()
- eband_->optimizeBand()
- calcObstacleKinematicDistance()
- modifyBandArtificialForce()
- calcInternalForces() 计算内力
- calcExternalForces() 计算外力
- suppressTangentialForces() 移除切向力
- applyForces()
- 结语
不知道这一篇还有没有人看…
这两个文件构成了算法的核心, 上一篇文章讲到在 eband_local_planner_ros.cpp 里, EBandPlannerROS::setplan 将全局路径通过之前 conversions_and_types.cpp 讲过的函数 “截断” 到局部, 之后交给 EBandPlanner eband_ 来操作 .
大家有兴趣可以改改代码, 打印打印信息看一下, 上面截断的全局路径在代码中反映的是一个数组, 数组每个元素由点的位置和姿态组成, 点的位置自然就是路径中的每个离散点在 map 下的坐标, 点的姿态只有终点被赋值, 中间的所有点的初始姿态都被赋值为0, 事实上, eband_->optimizeBand() 里会计算优化后的姿态数据(在2D 导航里也就是朝向)
下面我们直接来看 eband_->setplan() 和 eband_->optimizeBand()
eband_->setplan()
setplan 将截断的全局路径赋值给内置变量, 并优化路径里离散点的个数(全局路径点太密集了)
// 函数传入参数: 已经被截断的全局路径,即局部路径, 还未被优化
bool EBandPlanner::setPlan(
const std::vector<geometry_msgs::PoseStamped> &global_plan) {
// check if plugin initialized
if (!initialized_) {
ROS_ERROR("This planner has not been initialized, please call initialize() "
"before using this planner");
return false;
}
// check if plan valid (minimum 2 frames)
if (global_plan.size() < 2) {
ROS_ERROR("Attempt to pass empty path to optimization. Valid path needs to "
"have at least 2 Frames. This one has %d.",
((int)global_plan.size()));
return false;
}
// copy plan to local member variable
// 赋值给内部的成员变量, 下一步送去 optimizeBand() 优化
global_plan_ = global_plan;
// check whether plan and costmap are in the same frame
if (global_plan.front().header.frame_id != costmap_ros_->getGlobalFrameID()) {
ROS_ERROR("Elastic Band expects plan for optimization in the %s frame, the "
"plan was sent in the %s frame.",
costmap_ros_->getGlobalFrameID().c_str(),
global_plan.front().header.frame_id.c_str());
return false;
}
// convert frames in path into bubbles in band -> sets center of bubbles and
// calculates expansion
ROS_DEBUG("Converting Plan to Band");
// 截至到这里,把传入的直接从 global 全局路径粗暴局部截断的路径转化成泡泡路径
if (!convertPlanToBand(global_plan_, elastic_band_)) {
ROS_WARN("Conversion from plan to elastic band failed. Plan probably not "
"collision free. Plan not set for optimization");
// TODO try to do local repairs of band
return false;
}
// close gaps and remove redundant bubbles
ROS_DEBUG("Refining Band");
// 如果路径中点太密集, 将会去除掉冗余点, 如果点太稀疏, 会插值补充
if (!refineBand(elastic_band_)) {
ROS_WARN("Band is broken. Could not close gaps in converted path. Path not "
"set. Global replanning needed");
return false;
}
ROS_DEBUG("Refinement done - Band set.");
return true;
}
setplan 函数最后调用了 refineBand 函数来优化路径中间点个数, 而 refineBand 简单的调用了 removeAndFill
refineBand()
bool EBandPlanner::refineBand(std::vector<Bubble> &band) {
// check if plugin initialized
if (!initialized_) {
ROS_ERROR("This planner has not been initialized, please call initialize() "
"before using this planner");
return false;
}
// check if band valid (minimum 2 bubbles)
if (band.size() < 2) {
ROS_WARN("Attempt to convert empty band to plan. Valid band needs to have "
"at least 2 Frames. This one has %d.",
((int)band.size()));
return false;
}
// instantiate local variables
bool success;
std::vector<Bubble> tmp_band;
std::vector<Bubble>::iterator start_iter, end_iter;
// remove redundant Bubbles and fill gabs recursively
tmp_band = band;
start_iter = tmp_band.begin();
end_iter = (tmp_band.end() - 1); // -1 because .end() points "past the end"!
success = removeAndFill(tmp_band, start_iter, end_iter);
if (!success)
ROS_DEBUG("Band is broken. Could not close gaps.");
else {
#ifdef DEBUG_EBAND_
ROS_DEBUG("Recursive filling and removing DONE");
#endif
band = tmp_band;
}
return success;
}
removeAndFill()
来看看 removeAndFill 在干嘛, 大家可以看看源码里用于 debug 模式的一些调试代码, 里面其实展示了代码中每个步骤的很多信息.
这个函数主要用二分法来处理泡泡路径带, 发现每两个点之间距离太近就移除, 发现隔得太远就插值.
bool EBandPlanner::removeAndFill(std::vector<Bubble> &band,
std::vector<Bubble>::iterator &start_iter,
std::vector<Bubble>::iterator &end_iter) {
// instantiate local variables
bool overlap;
std::vector<Bubble>::iterator tmp_iter;
int mid_int, diff_int;
#ifdef DEBUG_EBAND_
int debug_dist_start, debug_dist_iters;
debug_dist_start = std::distance(band.begin(), start_iter);
debug_dist_iters = std::distance(start_iter, end_iter);
ROS_DEBUG("Refining Recursive - Check if Bubbles %d and %d overlapp. Total "
"size of band %d.",
debug_dist_start, (debug_dist_start + debug_dist_iters),
((int)band.size()));
#endif
// check that iterators are still valid
ROS_ASSERT(start_iter >= band.begin());
ROS_ASSERT(
end_iter <
band.end()); // "<" because .end() points _behind_ last element of vector
ROS_ASSERT(start_iter < end_iter);
// check whether start and end bubbles of this intervall overlap
overlap = checkOverlap(*start_iter, *end_iter);
if (overlap) {
#ifdef DEBUG_EBAND_
ROS_DEBUG("Refining Recursive - Bubbles overlapp, check for redundancies");
#endif
// if there are bubbles between start and end of intervall remove them (they
// are redundant as start and end of intervall do overlap)
if ((start_iter + 1) < end_iter) {
#ifdef DEBUG_EBAND_
ROS_DEBUG(
"Refining Recursive - Bubbles overlapp, removing Bubbles %d to %d.",
(debug_dist_start + 1), (debug_dist_start + debug_dist_iters - 1));
#endif
// erase bubbles between start and end (but not start and end themself)
// and get new iterator to end (old one is invalid)
tmp_iter = band.erase((start_iter + 1), end_iter);
// write back changed iterator pointing to the end of the intervall
end_iter = tmp_iter;
}
#ifdef DEBUG_EBAND_
ROS_DEBUG("Refining Recursive - Bubbles overlapp - DONE");
#endif
// we are done here (leaf of this branch is reached)
return true;
}
// if bubbles do not overlap -> check whether there are still bubbles between
// start and end
if ((start_iter + 1) < end_iter) {
#ifdef DEBUG_EBAND_
ROS_DEBUG("Refining Recursive - Bubbles do not overlapp, go one recursion "
"deeper");
#endif
// split remaining sequence of bubbles
// get distance between start and end iterator for this intervall
mid_int = std::distance(start_iter, end_iter);
mid_int = mid_int / 2; // division by integer implies floor (round down)
// now get iterator pointing to the middle (roughly)
tmp_iter = start_iter + mid_int;
// and realative position of end_iter to tmp_iter
diff_int = (int)std::distance(tmp_iter, end_iter);
// after all this arithmetics - check that iterators are still valid
ROS_ASSERT(start_iter >= band.begin());
ROS_ASSERT(end_iter < band.end()); // "<" because .end() points _behind_
// last element of vector
ROS_ASSERT(start_iter < end_iter);
// and call removeAndFill recursively for the left intervall
if (!removeAndFill(band, start_iter, tmp_iter)) {
// band is broken in this intervall and could not be fixed
return false;
}
// carefull at this point!!! if we filled in or removed bubbles end_iter is
// not valid anymore but the relative position towards tmp_iter is still the
// same and tmp_iter was kept valid in the lower recursion steps
end_iter = tmp_iter + diff_int;
// check that iterators are still valid - one more time
ROS_ASSERT(start_iter >= band.begin());
ROS_ASSERT(end_iter < band.end()); // "<" because .end() points _behind_
// last element of vector
ROS_ASSERT((start_iter < tmp_iter) && (tmp_iter < end_iter));
// o.k. we are done with left hand intervall now do the same for the right
// hand intervall but first get relative position of start and tmp iter
diff_int = (int)std::distance(start_iter, tmp_iter);
if (!removeAndFill(band, tmp_iter, end_iter)) {
// band is broken in this intervall and could not be fixed
return false;
}
// if we filled in bubbles vector might have been reallocated -> start_iter
// might be invalid
start_iter = tmp_iter - diff_int;
// check that iterators are still valid - almost done
ROS_ASSERT(start_iter >= band.begin());
ROS_ASSERT(end_iter < band.end()); // "<" because .end() points _behind_
// last element of vector
ROS_ASSERT((start_iter < tmp_iter) && (tmp_iter < end_iter));
// we reached the leaf but we are not yet done
// -> we know that there are no redundant elements in the left intervall
// taken on its own
// -> and we know the same holds for the right intervall
// but the middle bubble itself might be redundant -> check it
if (checkOverlap(*(tmp_iter - 1), *(tmp_iter + 1))) {
#ifdef DEBUG_EBAND_
ROS_DEBUG("Refining Recursive - Removing middle bubble");
#endif
// again: get distance between (tmp_iter + 1) and end_iter, (+1 because we
// will erase tmp_iter itself)
diff_int = (int)std::distance((tmp_iter + 1), end_iter);
// remove middle bubble and correct end_iter
tmp_iter = band.erase(tmp_iter);
end_iter = tmp_iter + diff_int;
}
// check that iterators are still valid - almost almost
ROS_ASSERT(start_iter >= band.begin());
ROS_ASSERT(end_iter < band.end()); // "<" because .end() points _behind_
// last element of vector
ROS_ASSERT(start_iter < end_iter);
#ifdef DEBUG_EBAND_
ROS_DEBUG("Refining Recursive - Bubbles do not overlapp, go one recursion "
"deeper DONE");
#endif
// now we are done with this case
return true;
}
#ifdef DEBUG_EBAND_
ROS_DEBUG("Refining Recursive - Gap detected, fill recursive");
#endif
// last possible case -> bubbles do not overlap AND there are nor bubbles in
// between -> try to fill gap recursively
if (!fillGap(band, start_iter, end_iter)) {
// band is broken in this intervall and could not be fixed (this should only
// be called on a leaf, so we put a log_out here;)
ROS_DEBUG("Failed to fill gap between bubble %d and %d.",
(int)distance(band.begin(), start_iter),
(int)distance(band.begin(), end_iter));
return false;
}
#ifdef DEBUG_EBAND_
ROS_DEBUG("Refining Recursive - Gap detected, fill recursive DONE");
#endif
// we could fill the gap (reached leaf of this branch)
return true;
}
interpolateBubbles()
插值函数是 interpolateBubbles, 这个函数先调用 fillGap , 再在 fillGap 里调用 interpolateBubbles, interpolateBubbles 直接使用中值插值, 位置和姿态一起插
bool EBandPlanner::interpolateBubbles(
geometry_msgs::PoseStamped start_center,
geometry_msgs::PoseStamped end_center,
geometry_msgs::PoseStamped &interpolated_center) {
// check if plugin initialized
if (!initialized_) {
ROS_ERROR("This planner has not been initialized, please call initialize() "
"before using this planner");
return false;
}
// instantiate local variables
geometry_msgs::Pose2D start_pose2D, end_pose2D, interpolated_pose2D;
double delta_theta;
// copy header
interpolated_center.header = start_center.header;
// interpolate angles
// TODO make this in a better way
// - for instance use "slerp" to interpolate directly between quaternions
// - or work with pose2D right from the beginnning
// convert quaternions to euler angles - at this point this no longer works in
// 3D !!
PoseToPose2D(start_center.pose, start_pose2D);
PoseToPose2D(end_center.pose, end_pose2D);
// calc mean of theta angle
delta_theta = end_pose2D.theta - start_pose2D.theta;
delta_theta = angles::normalize_angle(delta_theta) / 2.0;
interpolated_pose2D.theta = start_pose2D.theta + delta_theta;
interpolated_pose2D.theta =
angles::normalize_angle(interpolated_pose2D.theta);
// convert back to quaternion
interpolated_pose2D.x = 0.0;
interpolated_pose2D.y = 0.0;
Pose2DToPose(interpolated_center.pose, interpolated_pose2D);
// interpolate positions
interpolated_center.pose.position.x =
(end_center.pose.position.x + start_center.pose.position.x) / 2.0;
interpolated_center.pose.position.y =
(end_center.pose.position.y + start_center.pose.position.y) / 2.0;
interpolated_center.pose.position.z =
(end_center.pose.position.z + start_center.pose.position.z) / 2.0;
// TODO ideally this would take into account kinematics of the robot and for
// instance use splines
return true;
}
eband_->optimizeBand()
这个函数实现论文算法, 输入截断的全局路径,输出优化后的路径, 注意这个函数是个重载函数, 在无参数版本里调用有参数版本
无参数版本 :
bool EBandPlanner::optimizeBand() {
// check if plugin initialized
if (!initialized_) {
ROS_ERROR("This planner has not been initialized, please call initialize() "
"before using this planner");
return false;
}
// check if there is a band
if (elastic_band_.empty()) {
ROS_ERROR("Band is empty. Probably Band has not been set yet");
return false;
}
// call optimization with member elastic_band_
ROS_DEBUG("Starting optimization of band");
// 调用有参数版本
if (!optimizeBand(elastic_band_)) {
ROS_DEBUG("Aborting Optimization. Changes discarded.");
return false;
}
ROS_DEBUG("Elastic Band - Optimization successfull!");
return true;
}
有参数版本 :
有参数版本依次调用了如下几个函数:
- calcObstacleKinematicDistance(): 通过 costmap_2d 初始化泡泡的半径
- refineBand(): 在算法优化之前, 先对整条泡泡路径进行一次小优化, 主要包括去除冗余泡泡和泡泡插值
- modifyBandArtificialForce(): 论文算法实现部分, 生成算法优化后的泡泡路径
- refineBand(): 在算法优化之后, 再对整条泡泡路径进行一次小优化
bool EBandPlanner::optimizeBand(std::vector<Bubble> &band) {
// check if plugin initialized
if (!initialized_) {
ROS_ERROR("This planner has not been initialized, please call initialize() "
"before using this planner");
return false;
}
// check whether band and costmap are in the same frame
if (band.front().center.header.frame_id != costmap_ros_->getGlobalFrameID()) {
ROS_ERROR("Elastic Band expects plan for optimization in the %s frame, the "
"plan was sent in the %s frame.",
costmap_ros_->getGlobalFrameID().c_str(),
band.front().center.header.frame_id.c_str());
return false;
}
double distance;
for (int i = 0; i < ((int)band.size()); i++) {
// update Size of Bubbles in band by calculating Dist to nearest Obstacle
// [depends kinematic, environment]
if (!calcObstacleKinematicDistance(band.at(i).center.pose, distance)) {
ROS_DEBUG("Optimization (Elastic Band) - Calculation of Distance failed. "
"Frame %d of %d Probably outside map coordinates.",
i, ((int)band.size()));
return false;
}
if (distance == 0.0) {
// frame must not be immediately in collision -> otherwise calculation of
// gradient will later be invalid
ROS_DEBUG(
"Optimization (Elastic Band) - Calculation of Distance failed. Frame "
"%d of %d in collision. Plan invalid. Trying to refine band.",
i, ((int)band.size()));
// TODO if frame in collision try to repair band instead of aborting
// everything
return false;
}
band.at(i).expansion = distance;
}
// close gaps and remove redundant bubbles
if (!refineBand(band)) {
ROS_DEBUG("Elastic Band is broken. Could not close gaps in band. Global "
"replanning needed.");
return false;
}
// get a copy of current (valid) band
std::vector<Bubble> tmp_band = band;
// now optimize iteratively (for instance by miminizing the energy-function of
// the full system)
for (int i = 0; i < num_optim_iterations_; i++) {
ROS_DEBUG("Inside optimization: Cycle no %d", i);
// ROS_INFO("