赞
踩
学习了基础的规划算法后,就立志要学习并移植Baidu Apollo 的路径规划算法,此次从Lattice开始入手。为了完成这次的的Baidu Apollo 的Lattice路径规划算法,花费了不少时间,终于把原理和代码写了出来。
想要可跑工程,看这里:
链接: Apollo6.0规划代码ros移植-路径规划可跑工程分享.
说白了就是参考线离散化为一个一个点。
引用一位大佬的图:
这一步被我省去了, 因为我们的参考线是根据中心线生成的(目前使用Lanelet高精地图是这样处理),本身点是离散的,不用再离散化,但是需要插值然后优化参考线路径点。
就是找距离起点最近的参考线上的点。这里注意Apollo的参考线上的路径点,是一个类封装,里面有位置xy,有朝向,曲率,曲率导数等信息。
可参考:
https://www.jianshu.com/p/630c19f2bb9a
后面会有专门文章。
原理其实不难,说白了就是分为横向规划与纵向规划。
void Trajectory1dGenerator::GenerateTrajectoryBundles(
const PlanningTarget &planning_target,
Trajectory1DBundle *ptr_lon_trajectory_bundle,
Trajectory1DBundle *ptr_lat_trajectory_bundle,
Lattice_InitialConditions Lattice_Initial, std::vector<SL_Result> SL_Static)
{
GenerateLongitudinalTrajectoryBundle(planning_target, ptr_lon_trajectory_bundle);
GenerateLateralTrajectoryBundle(SL_Static, ptr_lat_trajectory_bundle);
return;
}
首先,进行纵向规划,纵向规划为V-T规划,即速度和时间的规划。
void Trajectory1dGenerator::GenerateLongitudinalTrajectoryBundle(
const PlanningTarget &planning_target, Trajectory1DBundle *ptr_lon_trajectory_bundle) const
{
// cruising trajectories are planned regardlessly.~
GenerateSpeedProfilesForCruising(planning_target.cruise_speed(), ptr_lon_trajectory_bundle);
GenerateSpeedProfilesForPathTimeObstacles(ptr_lon_trajectory_bundle);
if (planning_target.has_stop_point())
{
GenerateSpeedProfilesForStopping(planning_target.stop_point(), ptr_lon_trajectory_bundle);
}
}
纵向规划有三种模式:巡航模式,停止模式,根车和超车模式。我在ros下目前只用巡航模式,后面会跟大家仔细分享这些模式的区别和用法。先入门了解一下框架。
其次,进行横向规划,横向规划分为采样点规划和二次规划,Apollo默认进入二次规划,因为速度快。
void Trajectory1dGenerator::GenerateLateralTrajectoryBundle( Trajectory1DBundle *ptr_lat_trajectory_bundle) const { if (!FLAGS_lateral_optimization) { auto end_conditions = end_condition_sampler_.SampleLatEndConditions(); // Use the common function to generate trajectory bundles. GenerateTrajectory1DBundle<5>(init_lat_state_, end_conditions, ptr_lat_trajectory_bundle); } else { double s_min = init_lon_state_[0]; double s_max = s_min + FLAGS_max_s_lateral_optimization; double delta_s = FLAGS_default_delta_s_lateral_optimization; auto lateral_bounds = ptr_path_time_graph_->GetLateralBounds(s_min, s_max, delta_s); // LateralTrajectoryOptimizer lateral_optimizer; std::unique_ptr<LateralQPOptimizer> lateral_optimizer(new LateralOSQPOptimizer); lateral_optimizer->optimize(init_lat_state_, delta_s, lateral_bounds); auto lateral_trajectory = lateral_optimizer->GetOptimalTrajectory(); ptr_lat_trajectory_bundle->push_back( std::make_shared<PiecewiseJerkTrajectory1d>(lateral_trajectory)); } }
轨迹是由多项式曲线生成,就是根据首末点状态,进行多项式曲线的系数求解,这里总结一下:
纵向规划:
巡航模式:四次多项式(起始点s、v、a和末点的v、a)
停止模式:五次多项式(起始点s、v、a和末点的s、v、a)
根车和超车模式:五次多项式(起始点s、v、a和末点的s、v、a)
横向规划:
采样点的规划:五次多项式
二次规划:调用osqp求解器的库
分为纵向轨迹评价和横向轨迹评价。
double TrajectoryEvaluator::Evaluate( const PlanningTarget &planning_target, const PtrTrajectory1d &lon_trajectory, const PtrTrajectory1d &lat_trajectory, std::vector<double> *cost_components) const { // Costs: // 1. Cost of missing the objective, e.g., cruise, stop, etc. // 2. Cost of longitudinal jerk // 3. Cost of longitudinal collision // 4. Cost of lateral offsets // 5. Cost of lateral comfort // Longitudinal costs double lon_objective_cost = LonObjectiveCost(lon_trajectory, planning_target, reference_s_dot_); double lon_jerk_cost = LonComfortCost(lon_trajectory); double lon_collision_cost = LonCollisionCost(lon_trajectory); double centripetal_acc_cost = CentripetalAccelerationCost(lon_trajectory); // decides the longitudinal evaluation horizon for lateral trajectories. double evaluation_horizon = std::min(FLAGS_speed_lon_decision_horizon, lon_trajectory->Evaluate(0,lon_trajectory->ParamLength())); std::vector<double> s_values; for (double s = 0.0; s < evaluation_horizon; s += FLAGS_trajectory_space_resolution) { s_values.emplace_back(s); } // Lateral costs double lat_offset_cost = LatOffsetCost(lat_trajectory, s_values); double lat_comfort_cost = LatComfortCost(lon_trajectory, lat_trajectory); if (cost_components != nullptr) { cost_components->emplace_back(lon_objective_cost); cost_components->emplace_back(lon_jerk_cost); cost_components->emplace_back(lon_collision_cost); cost_components->emplace_back(lat_offset_cost); } return lon_objective_cost * FLAGS_weight_lon_objective + lon_jerk_cost * FLAGS_weight_lon_jerk + lon_collision_cost * FLAGS_weight_lon_collision + centripetal_acc_cost * FLAGS_weight_centripetal_acceleration + lat_offset_cost * FLAGS_weight_lat_offset + lat_comfort_cost * FLAGS_weight_lat_comfort; }
cost的内容其实不难理解:
DiscretizedTrajectory TrajectoryCombiner::Combine( const std::vector<PathPoint> &reference_line, const Curve1d &lon_trajectory, const Curve1d &lat_trajectory, const double init_relative_time) { DiscretizedTrajectory combined_trajectory; double s0 = lon_trajectory.Evaluate(0, 0.0); double s_ref_max = reference_line.back().s(); double accumulated_trajectory_s = 0.0; PathPoint prev_trajectory_point; double last_s = -FLAGS_numerical_epsilon; double t_param = 0.0; while (t_param < FLAGS_trajectory_time_length) { // linear extrapolation is handled internally in LatticeTrajectory1d; // no worry about t_param > lon_trajectory.ParamLength() situation double s = lon_trajectory.Evaluate(0, t_param); if (last_s > 0.0) { s = std::max(last_s, s); } last_s = s; double s_dot = std::max(FLAGS_numerical_epsilon, lon_trajectory.Evaluate(1, t_param)); double s_ddot = lon_trajectory.Evaluate(2, t_param); if (s > s_ref_max) { break; } double relative_s = s - s0; // linear extrapolation is handled internally in LatticeTrajectory1d; // no worry about s_param > lat_trajectory.ParamLength() situation double d = lat_trajectory.Evaluate(0, relative_s); double d_prime = lat_trajectory.Evaluate(1, relative_s); double d_pprime = lat_trajectory.Evaluate(2, relative_s); PathPoint matched_ref_point = PathMatcher::MatchToPath(reference_line, s); double x = 0.0; double y = 0.0; double theta = 0.0; double kappa = 0.0; double v = 0.0; double a = 0.0; const double rs = matched_ref_point.s(); const double rx = matched_ref_point.x(); const double ry = matched_ref_point.y(); const double rtheta = matched_ref_point.theta(); const double rkappa = matched_ref_point.kappa(); const double rdkappa = matched_ref_point.dkappa(); std::array<double, 3> s_conditions = {rs, s_dot, s_ddot}; std::array<double, 3> d_conditions = {d, d_prime, d_pprime}; CartesianFrenetConverter::frenet_to_cartesian(rs, rx, ry, rtheta, rkappa, rdkappa, s_conditions, d_conditions, &x, &y, &theta, &kappa, &v, &a); if (prev_trajectory_point.has_x() && prev_trajectory_point.has_y()) { double delta_x = x - prev_trajectory_point.x(); double delta_y = y - prev_trajectory_point.y(); double delta_s = std::hypot(delta_x, delta_y); accumulated_trajectory_s += delta_s; } TrajectoryPoint trajectory_point; trajectory_point.mutable_path_point()->set_x(x); trajectory_point.mutable_path_point()->set_y(y); trajectory_point.mutable_path_point()->set_s(accumulated_trajectory_s); trajectory_point.mutable_path_point()->set_theta(theta); trajectory_point.mutable_path_point()->set_kappa(kappa); trajectory_point.set_v(v); trajectory_point.set_a(a); trajectory_point.set_relative_time(t_param + init_relative_time); combined_trajectory.AppendTrajectoryPoint(trajectory_point); t_param = t_param + FLAGS_trajectory_time_resolution; prev_trajectory_point = trajectory_point.path_point(); } return combined_trajectory; }
合并的原理也简单,其实就是t采样,得到s,根据s采样,得到L,然后组合得到完整的S、L、t,也没啥好解释~~再从Frenet坐标系转为世界坐标系,这样轨迹就出现了。
请看教程系列:
https://www.bilibili.com/video/BV1zj411Z7VH/?spm_id_from=333.1007.top_right_bar_window_history.content.click&vd_source=be5ccb1d324ad1a880d0e667733d8023
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。