該部分是teb進行自動調整,根據de_ref參考兩個位姿之間的時間差,dt_hysteresis爲滯後時間,也就是爲時間差加一個誤差限,一般爲dt_ref的10%。
經過調整滯後想要達到的目的是,每個時間差都在dt_ref左右。
void TimedElasticBand::autoResize(double dt_ref, double dt_hysteresis, int min_samples, int max_samples, bool fast_mode)
{
/// iterate through all TEB states and add/remove states!
bool modified = true;
for (int rep = 0; rep < 100 && modified; ++rep) // actually it should be while(), but we want to make sure to not get stuck in some oscillation, hence max 100 repitions.
正如註釋中所說本來需要不斷的對teb路徑進行autoresize的,但是爲了避免陷入死循環這裏只進行100次的調整。
整體思路:當兩個位姿之間的時間差大於dt_ref時就在兩個位姿之間插入一個點,插入一個timediff,此時兩個點之間的
時間差肯定比dt_ref要小了,第二次進行調整的時候就會在此之間刪除一個點,這時兩個點之間的時間差可能就會變大,
第三次進來再插入一個點,兩個點之間的距離可能就滿足要求了。
如此往復的增加和刪除,能夠保證最終的整個teb路徑兩個點之間的時間差都在指定的dt_ref範圍內。
{
modified = false;
for(int i=0; i < sizeTimeDiffs(); ++i) // TimeDiff connects Point(i) with Point(i+1)
{
if(TimeDiff(i) > dt_ref + dt_hysteresis && sizeTimeDiffs()<max_samples)
{
//ROS_DEBUG("teb_local_planner: autoResize() inserting new bandpoint i=%u, #TimeDiffs=%lu",i,sizeTimeDiffs());
double newtime = 0.5*TimeDiff(i);
TimeDiff(i) = newtime;
insertPose(i+1, PoseSE2::average(Pose(i),Pose(i+1)) );
insertTimeDiff(i+1,newtime);
modified = true;
}
else if(TimeDiff(i) < dt_ref - dt_hysteresis && sizeTimeDiffs()>min_samples) // only remove samples if size is larger than min_samples.
{
//ROS_DEBUG("teb_local_planner: autoResize() deleting bandpoint i=%u, #TimeDiffs=%lu",i,sizeTimeDiffs());
if(i < ((int)sizeTimeDiffs()-1))
{
TimeDiff(i+1) = TimeDiff(i+1) + TimeDiff(i);
deleteTimeDiff(i);
deletePose(i+1);
}
modified = true;
}
}
if (fast_mode) break;
}
}
該部分是根據全局的路徑A*或者給定起點終點,生成最初的teb路徑。然後再進入優化進行autoresize自動調整
bool TimedElasticBand::initTrajectoryToGoal(const std::vector<geometry_msgs::PoseStamped>& plan, double max_vel_x, bool estimate_orient, int min_samples, bool guess_backwards_motion)
{
if (!isInit())
{
PoseSE2 start(plan.front().pose);
PoseSE2 goal(plan.back().pose);
double dt = 0.1;
addPose(start); // add starting point with given orientation
setPoseVertexFixed(0,true); // StartConf is a fixed constraint during optimization
//這裏起點是固定的所以要設置爲true。
bool backwards = false;
if (guess_backwards_motion && (goal.position()-start.position()).dot(start.orientationUnitVec()) < 0) // check if the goal is behind the start pose (w.r.t. start orientation)
backwards = true;
// TODO: dt ~ max_vel_x_backwards for backwards motions
for (int i=1; i<(int)plan.size()-1; ++i)
{
double yaw;
if (estimate_orient)
{
// get yaw from the orientation of the distance vector between pose_{i+1} and pose_{i}
double dx = plan[i+1].pose.position.x - plan[i].pose.position.x;
double dy = plan[i+1].pose.position.y - plan[i].pose.position.y;
yaw = std::atan2(dy,dx);
if (backwards)
yaw = g2o::normalize_theta(yaw+M_PI);
}
else
{
yaw = tf::getYaw(plan[i].pose.orientation);
}
PoseSE2 intermediate_pose(plan[i].pose.position.x, plan[i].pose.position.y, yaw);
if (max_vel_x > 0) dt = (intermediate_pose.position()-BackPose().position()).norm()/max_vel_x;
//這裏dt的計算是當前位置和最後一個點之間的距離的時間差,這裏感覺有點疑惑,待後續繼續思考!!!!!
addPoseAndTimeDiff(intermediate_pose, dt);//將位置和時間差放入相應的vector中
}
// if number of samples is not larger than min_samples, insert manually
if ( sizePoses() < min_samples-1 )
{
ROS_DEBUG("initTEBtoGoal(): number of generated samples is less than specified by min_samples. Forcing the insertion of more samples...");
while (sizePoses() < min_samples-1) // subtract goal point that will be added later
{
// simple strategy: interpolate between the current pose and the goal
PoseSE2 intermediate_pose = PoseSE2::average(BackPose(), goal);
if (max_vel_x > 0) dt = (intermediate_pose.position()-BackPose().position()).norm()/max_vel_x;
addPoseAndTimeDiff( intermediate_pose, dt ); // let the optimier correct the timestep (TODO: better initialization
}
}
// Now add final state with given orientation
if (max_vel_x > 0) dt = (goal.position()-BackPose().position()).norm()/max_vel_x;
addPoseAndTimeDiff(goal, dt);
setPoseVertexFixed(sizePoses()-1,true); // GoalConf is a fixed constraint during optimization
//最後一個點也是固定的
}
else // size!=0
{
ROS_WARN("Cannot init TEB between given configuration and goal, because TEB vectors are not empty or TEB is already initialized (call this function before adding states yourself)!");
ROS_WARN("Number of TEB configurations: %d, Number of TEB timediffs: %d", sizePoses(), sizeTimeDiffs());
return false;
}
return true;
}
該部分是最關鍵的規劃部分,根據全局路徑initial_plan進行路徑的初始化。
這裏的initial_plan是經過處理的,在move_base中該部分是根據全局路徑A*進行截取得到的,有一個前視距離,A*可能規劃了12米但前視距離可能只看6米,這時候的goal_就是這個6米出的位置。
bool TebOptimalPlanner::plan(const std::vector<geometry_msgs::PoseStamped>& initial_plan, const geometry_msgs::Twist* start_vel, bool free_goal_vel)
{
ROS_ASSERT_MSG(initialized_, "Call initialize() first.");
if (!teb_.isInit())
{
// init trajectory最開始的第一次使用A*的路徑對teb進行初始化,給teb賦值。
teb_.initTrajectoryToGoal(initial_plan, cfg_->robot.max_vel_x, cfg_->trajectory.global_plan_overwrite_orientation, cfg_->trajectory.min_samples, cfg_->trajectory.allow_init_with_backwards_motion);
}
else // warm start
{
PoseSE2 start_(initial_plan.front().pose);
PoseSE2 goal_(initial_plan.back().pose);
if (teb_.sizePoses()>0 && (goal_.position() - teb_.BackPose().position()).norm() < cfg_->trajectory.force_reinit_new_goal_dist) // actual warm start!之後先檢查goal_是否變化很大,如果變化不大則使用上一次teb的路徑作爲初值。
teb_.updateAndPruneTEB(start_, goal_, cfg_->trajectory.min_samples); // update TEB進行簡單的裁剪作爲新的初值
else // goal too far away -> reinit
*****該部分比較重要是因爲路徑被帶走的問題,當有動態障礙物穿越teb時,A*會發生變化,在根據前視距離對A*的全局路徑進行截取時,使用的是兩個點之間的距離的累加。當A*不更新時會導致終點始終不變化,導致使用上次的teb進行更新,始終向一個方向更新,導致路徑被帶走。
{如果goal變化很大則使用A*的路徑進行作爲初值。
ROS_DEBUG("New goal: distance to existing goal is higher than the specified threshold. Reinitalizing trajectories.");
teb_.clearTimedElasticBand();
teb_.initTrajectoryToGoal(initial_plan, cfg_->robot.max_vel_x, true, cfg_->trajectory.min_samples, cfg_->trajectory.allow_init_with_backwards_motion);
}
}
if (start_vel)
setVelocityStart(*start_vel);
if (free_goal_vel)
setVelocityGoalFree();
else
vel_goal_.first = true; // we just reactivate and use the previously set velocity (should be zero if nothing was modified)
// now optimize
return optimizeTEB(cfg_->optim.no_inner_iterations, cfg_->optim.no_outer_iterations);
}
該部分是對A*路徑的處理,包括使用tf對座標系進行轉換,使用前視距離對路徑進行截取
bool TebLocalPlannerROS::transformGlobalPlan(const tf::TransformListener& tf, const std::vector<geometry_msgs::PoseStamped>& global_plan,
const tf::Stamped<tf::Pose>& global_pose, const costmap_2d::Costmap2D& costmap, const std::string& global_frame, double max_plan_length,
std::vector<geometry_msgs::PoseStamped>& transformed_plan, int* current_goal_idx, tf::StampedTransform* tf_plan_to_global) const
{
// this method is a slightly modified version of base_local_planner/goal_functions.h
const geometry_msgs::PoseStamped& plan_pose = global_plan[0];
transformed_plan.clear();
try
{
if (global_plan.empty())
{
ROS_ERROR("Received plan with zero length");
*current_goal_idx = 0;
return false;
}
// get plan_to_global_transform from plan frame to global_frame
tf::StampedTransform plan_to_global_transform;
tf.waitForTransform(global_frame, ros::Time::now(),
plan_pose.header.frame_id, plan_pose.header.stamp,
plan_pose.header.frame_id, ros::Duration(0.5));
tf.lookupTransform(global_frame, ros::Time(),
plan_pose.header.frame_id, plan_pose.header.stamp,
plan_pose.header.frame_id, plan_to_global_transform);
//let's get the pose of the robot in the frame of the plan
tf::Stamped<tf::Pose> robot_pose;
tf.transformPose(plan_pose.header.frame_id, global_pose, robot_pose);
//we'll discard points on the plan that are outside the local costmap
double dist_threshold = std::max(costmap.getSizeInCellsX() * costmap.getResolution() / 2.0,
costmap.getSizeInCellsY() * costmap.getResolution() / 2.0);
dist_threshold *= 0.85; // just consider 85% of the costmap size to better incorporate point obstacle that are
// located on the border of the local costmap
int i = 0;
double sq_dist_threshold = dist_threshold * dist_threshold;
double sq_dist = 1e10;
//we need to loop to a point on the plan that is within a certain distance of the robot
for(int j=0; j < (int)global_plan.size(); ++j)
{
double x_diff = robot_pose.getOrigin().x() - global_plan[j].pose.position.x;
double y_diff = robot_pose.getOrigin().y() - global_plan[j].pose.position.y;
double new_sq_dist = x_diff * x_diff + y_diff * y_diff;
if (new_sq_dist > sq_dist_threshold)
break; // force stop if we have reached the costmap border
if (new_sq_dist < sq_dist) // find closest distance
{
sq_dist = new_sq_dist;
i = j;
}
}
tf::Stamped<tf::Pose> tf_pose;
geometry_msgs::PoseStamped newer_pose;
double plan_length = 0; // check cumulative Euclidean distance along the plan
//now we'll transform until points are outside of our distance threshold
while(i < (int)global_plan.size() && sq_dist <= sq_dist_threshold && (max_plan_length<=0 || plan_length <= max_plan_length))
{
const geometry_msgs::PoseStamped& pose = global_plan[i];
tf::poseStampedMsgToTF(pose, tf_pose);
tf_pose.setData(plan_to_global_transform * tf_pose);
tf_pose.stamp_ = plan_to_global_transform.stamp_;
tf_pose.frame_id_ = global_frame;
tf::poseStampedTFToMsg(tf_pose, newer_pose);
transformed_plan.push_back(newer_pose);
double x_diff = robot_pose.getOrigin().x() - global_plan[i].pose.position.x;
double y_diff = robot_pose.getOrigin().y() - global_plan[i].pose.position.y;
sq_dist = x_diff * x_diff + y_diff * y_diff;
// caclulate distance to previous pose
if (i>0 && max_plan_length>0)
plan_length += distance_points2d(global_plan[i-1].pose.position, global_plan[i].pose.position);
++i;
}
// if we are really close to the goal (<sq_dist_threshold) and the goal is not yet reached (e.g. orientation error >>0)
// the resulting transformed plan can be empty. In that case we explicitly inject the global goal.
if (transformed_plan.empty())
{
tf::poseStampedMsgToTF(global_plan.back(), tf_pose);
tf_pose.setData(plan_to_global_transform * tf_pose);
tf_pose.stamp_ = plan_to_global_transform.stamp_;
tf_pose.frame_id_ = global_frame;
tf::poseStampedTFToMsg(tf_pose, newer_pose);
transformed_plan.push_back(newer_pose);
// Return the index of the current goal point (inside the distance threshold)
if (current_goal_idx) *current_goal_idx = int(global_plan.size())-1;
}
else
{
// Return the index of the current goal point (inside the distance threshold)
if (current_goal_idx) *current_goal_idx = i-1; // subtract 1, since i was increased once before leaving the loop
}
// Return the transformation from the global plan to the global planning frame if desired
if (tf_plan_to_global) *tf_plan_to_global = plan_to_global_transform;
}
catch(tf::LookupException& ex)
{
ROS_ERROR("No Transform available Error: %s\n", ex.what());
return false;
}
catch(tf::ConnectivityException& ex)
{
ROS_ERROR("Connectivity Error: %s\n", ex.what());
return false;
}
catch(tf::ExtrapolationException& ex)
{
ROS_ERROR("Extrapolation Error: %s\n", ex.what());
if (global_plan.size() > 0)
ROS_ERROR("Global Frame: %s Plan Frame size %d: %s\n", global_frame.c_str(), (unsigned int)global_plan.size(), global_plan[0].header.frame_id.c_str());
return false;
}
return true;
}