在每一帧轨迹规划的开始,我们需要确定规划的起始点
p
0
=
(
t
,
x
,
y
,
v
,
a
,
h
e
a
d
i
n
g
,
k
a
p
p
a
)
p_0=(t,x,y,v,a,heading,kappa)
p0=(t,x,y,v,a,heading,kappa),起始点
p
0
p_0
p0给定了规划的初始状态,然后结合当前帧的环境信息并调用规划算法规划生成当前帧轨迹。 那么问题来了,每一帧规划的起始点
p
0
p_0
p0是如何确定的呢?在Apollo规划算法中采用了两种方案: 方案1: 根据ADC(Autonomous Driving Car)实际位置状态信息或根据ADC实际位置状态信息,通过车辆运动学推导0.1s后的位置状态信息,作为规划起始点状态,在Apollo中,这一方案叫做重规划(Replan),但是它属于轨迹拼接的一种,只是拼接点集只有一个点
p
0
p_0
p0而已。 方案2: 根据当前帧时间戳以及ADC实际位置信息,在上一帧轨迹中寻找匹配点,将上一帧轨迹中匹配点往后0.1s所对应的轨迹点作为当前帧的规划起始状态点,待当前帧规划生成轨迹后,再和上一帧轨迹中所选择的规划起始点前一段距离的轨迹点进行拼接,形成一条完整的车辆运动轨迹,发送给下游控制模块。 严格来说,以上两种方案都属于轨迹拼接,区别是方案1拼接轨迹只有一个点,而方案2的拼接轨迹是上一帧的部分有序点集,但是在Apollo代码中,称第一种方案叫做重新初始化拼接点(也叫重规划)。
结合图1和TrajectoryStitcher::ComputeStitchingTrajectory这个函数,理解哪些情况下选择方案1、哪些情况选择方案2。 选择方案1的情况有: a.没有使能方案2进行轨迹拼接 b.上一帧不存在轨迹(一般是起步或上一帧规划失败的情况) c.没有处在自动驾驶模式 d.上一帧存在轨迹但是没有轨迹点 e.当前时间相对于上一帧的相对时间戳小于上一帧起点相对时间戳 f.时间匹配点超出上一帧轨迹点的终点实际范围 g.匹配点处不存在匹配的path_point h.ADC实际位置和匹配点横纵向偏差超过给定阈值 方案1生成轨迹拼接点主要有两种情况: 情况1——起步阶段:由于起步阶段属于规划算法执行的第一帧,且速度、加速度值很小,可以直接使用当前状态点作为规划的起始状态点; 情况2——非起步阶段:则根据当前ADC状态点结合运动学模型外推
T
T
T时间之后的状态点作为本帧规划起始点。
[
x
t
+
1
y
t
+
1
ψ
t
+
1
]
=
[
x
t
y
t
ψ
t
]
+
[
c
o
s
(
ψ
)
s
i
n
(
ψ
)
t
a
n
(
δ
f
)
l
]
⋅
v
\begin{bmatrix}x_{t+1}\\y_{t+1}\\\psi_{t+1} \end{bmatrix}= \begin{bmatrix}x_{t}\\y_{t}\\\psi_{t} \end{bmatrix}+\begin{bmatrix}cos(\psi)\\sin(\psi)\\\frac{tan(\delta_f)}{l} \end{bmatrix}\cdot v
xt+1yt+1ψt+1=xtytψt+cos(ψ)sin(ψ)ltan(δf)⋅v
/* Planning from current vehicle state if:
1. the auto-driving mode is off
(or) 2. we don't have the trajectory from last planning cycle
(or) 3. the position deviation from actual and target is too high
*/
std::vector<TrajectoryPoint>TrajectoryStitcher::ComputeStitchingTrajectory(const VehicleState& vehicle_state,constdouble current_timestamp,constdouble planning_cycle_time,const size_t preserved_points_num,constbool replan_by_offset,const PublishableTrajectory* prev_trajectory,
std::string* replan_reason){//a.是否使能轨迹拼接if(!FLAGS_enable_trajectory_stitcher){*replan_reason ="stitch is disabled by gflag.";returnComputeReinitStitchingTrajectory(planning_cycle_time, vehicle_state);}//b.上一帧是否生成轨迹if(!prev_trajectory){*replan_reason ="replan for no previous trajectory.";returnComputeReinitStitchingTrajectory(planning_cycle_time, vehicle_state);}//c.是否处于自动驾驶模式if(vehicle_state.driving_mode()!= canbus::Chassis::COMPLETE_AUTO_DRIVE){*replan_reason ="replan for manual mode.";returnComputeReinitStitchingTrajectory(planning_cycle_time, vehicle_state);}//d.上一帧是否存在轨迹点
size_t prev_trajectory_size = prev_trajectory->NumOfPoints();if(prev_trajectory_size ==0){*replan_reason ="replan for empty previous trajectory.";returnComputeReinitStitchingTrajectory(planning_cycle_time, vehicle_state);}constdouble veh_rel_time = current_timestamp - prev_trajectory->header_time();
size_t time_matched_index = prev_trajectory->QueryLowerBoundPoint(veh_rel_time);//e.判断当前时间相对于上一帧的相对时间戳是否小于上一帧起点相对时间戳if(time_matched_index ==0&&
veh_rel_time < prev_trajectory->StartPoint().relative_time()){*replan_reason ="replan for current time smaller than the previous trajectory's first ""time.";returnComputeReinitStitchingTrajectory(planning_cycle_time, vehicle_state);}//f.判断时间匹配点是否超出上一帧轨迹点范围if(time_matched_index +1>= prev_trajectory_size){*replan_reason ="replan for current time beyond the previous trajectory's last time";returnComputeReinitStitchingTrajectory(planning_cycle_time, vehicle_state);}auto time_matched_point = prev_trajectory->TrajectoryPointAt(static_cast<uint32_t>(time_matched_index));//g.判断时间匹配点处是否存在path_pointif(!time_matched_point.has_path_point()){*replan_reason ="replan for previous trajectory missed path point";returnComputeReinitStitchingTrajectory(planning_cycle_time, vehicle_state);}
size_t position_matched_index = prev_trajectory->QueryNearestPointWithBuffer({vehicle_state.x(), vehicle_state.y()},1.0e-6);//计算实际位置点和匹配点的横纵向偏差auto frenet_sd =ComputePositionProjection(
vehicle_state.x(), vehicle_state.y(),
prev_trajectory->TrajectoryPointAt(static_cast<uint32_t>(position_matched_index)));//h.判断横纵向偏差if(replan_by_offset){auto lon_diff = time_matched_point.path_point().s()- frenet_sd.first;auto lat_diff = frenet_sd.second;//h.1:横向偏差不满足条件if(std::fabs(lat_diff)> FLAGS_replan_lateral_distance_threshold){const std::string msg = absl::StrCat("the distance between matched point and actual position is too ""large. Replan is triggered. lat_diff = ",
lat_diff);*replan_reason = msg;returnComputeReinitStitchingTrajectory(planning_cycle_time,
vehicle_state);}//h.2:纵向偏差不满足条件if(std::fabs(lon_diff)> FLAGS_replan_longitudinal_distance_threshold){const std::string msg = absl::StrCat("the distance between matched point and actual position is too ""large. Replan is triggered. lon_diff = ",
lon_diff);*replan_reason = msg;returnComputeReinitStitchingTrajectory(planning_cycle_time,
vehicle_state);}}else{
ADEBUG <<"replan according to certain amount of lat and lon offset is ""disabled";}//计算当前时刻后T时刻的时间,并计算其在上一帧轨迹中对应的索引值double forward_rel_time = veh_rel_time + planning_cycle_time;
size_t forward_time_index =
prev_trajectory->QueryLowerBoundPoint(forward_rel_time);
ADEBUG <<"Position matched index:\t"<< position_matched_index;
ADEBUG <<"Time matched index:\t"<< time_matched_index;//选择时间匹配索引和位置匹配索引中的较小索引作为匹配点索引auto matched_index = std::min(time_matched_index, position_matched_index);//构建拼接轨迹,<匹配索引点前n个点,当前时刻后的T时刻所对应的匹配点>
std::vector<TrajectoryPoint>stitching_trajectory(
prev_trajectory->begin()+
std::max(0,static_cast<int>(matched_index - preserved_points_num)),
prev_trajectory->begin()+ forward_time_index +1);constdouble zero_s = stitching_trajectory.back().path_point().s();for(auto& tp : stitching_trajectory){if(!tp.has_path_point()){*replan_reason ="replan for previous trajectory missed path point";returnComputeReinitStitchingTrajectory(planning_cycle_time,
vehicle_state);}//适配时间和s值
tp.set_relative_time(tp.relative_time()+ prev_trajectory->header_time()-
current_timestamp);
tp.mutable_path_point()->set_s(tp.path_point().s()- zero_s);}return stitching_trajectory;}