task PATH_DECIDER执行之后按照顺序会执行task SPEED_BOUNDS_PRIORI_DECIDER

bool SpeedBoundsDecider::Init(
    const std::string &config_dir, const std::string &name,
    const std::shared_ptr<DependencyInjector> &injector) {
  if (!Decider::Init(config_dir, name, injector)) {
    return false;
  }
  AINFO << "config_dir:" << config_dir << " " << "name:" << name;
  // Load the config this task.
  return Decider::LoadConfig<SpeedBoundsDeciderConfig>(&config_);
}

config_dir:modules/planning/scenarios/lane_follow/conf/lane_follow_stage
name:SPEED_BOUNDS_PRIORI_DECIDER
初始化流程可以回看一下planning模块(9)-路径规划(scenario/stage/task) 初始化部分

default_config_path_:/apollo/modules/planning/tasks/speed_bounds_decider/conf/default_conf.pb.txt config_path_:modules/planning/scenarios/lane_follow/conf/lane_follow_stage/speed_bounds_priori_decider.pb.txt
LoadConfig会加载config_path_路径下指定的配置,如果不存在就使用默认路径下的配置文件

 STBoundaryMapper boundary_mapper(config_, reference_line, path_data,
path_data.discretized_path().Length(),
config_.total_time(), injector_);

config_:在SpeedBoundsDecider::Init函数中加载的speed bounds decider的配置, apollo中加载的是modules/planning/tasks/speed_bounds_decider/conf/default_conf.pb.txt
reference_line:当前参考线
path_data:路径规划规划出的路线
path_data.discretized_path().Length():路径长度
config_.total_time():每次计算未来几秒内的路径
injector_:一个数据缓存中心,管理planning过程中的实时数据和几乎所有的历史数据,用于规划任务的前后帧之间的承接,以及异常处理的回溯.可以回看planning模块(9)-路径规划(scenario/stage/task)

  if (!FLAGS_use_st_drivable_boundary) {
    path_decision->EraseStBoundaries();
  }

重新构建当前参考线上的st边界

ComputeSTBoundary函数

Status STBoundaryMapper::ComputeSTBoundary(PathDecision* path_decision)
    if (!ptr_obstacle->HasLongitudinalDecision()) {
      ComputeSTBoundary(ptr_obstacle);
      continue;
    }

根据planning模块(13) task PATH_DECIDER 这篇介绍,静态障碍物都已经设置了纵向决策,所以上面的逻辑中是在计算动态障碍物的st边界

ComputeSTBoundary函数

FLAGS_use_st_drivable_boundary配置项的作用:

true:规划器使用所有障碍物合并后的统一ST边界
false:规划器会为每一个障碍物单独计算一个ST边界
FLAGS_use_st_drivable_boundary默认是false

GetOverlapBoundaryPoints函数

const auto* planning_status = injector_->planning_context()
                                    ->mutable_planning_status()
                                    ->mutable_change_lane();

  double l_buffer =
      planning_status->status() == ChangeLaneStatus::IN_CHANGE_LANE
          ? speed_bounds_config_.lane_change_obstacle_nudge_l_buffer()
          : FLAGS_nonstatic_obstacle_nudge_l_buffer;

这里我们不考虑换道的情况,所以l_buffer使用配置FLAGS_nonstatic_obstacle_nudge_l_buffer默认是0.4m,它定义了当车辆尝试从横向绕过(nudge)一个动态障碍物时,必须保持的额外最小安全间距.

if (trajectory.trajectory_point().empty()) {
    bool box_check_collision = false;
    if (!obstacle.IsStatic()) {
      AWARN << "Non-static obstacle[" << obstacle.Id()
            << "] has NO prediction trajectory."
            << obstacle.Perception().ShortDebugString();
    }

当障碍物没有预测轨迹点的时候会进入上面的case

const Box2d& obs_box = obstacle.PerceptionBoundingBox();
perception_bounding_box_({perception_obstacle_.position().x(),
                                perception_obstacle_.position().y()},
                               perception_obstacle_.theta(),
                               perception_obstacle_.length(),
                               perception_obstacle_.width())

是在CreateObstacles时构造Obstacle时创建的,可以回看planning模块-路径边界 障碍物

    for (const auto& curr_point_on_path : path_points) {
      if (curr_point_on_path.s() > planning_max_distance_) {
        break;
      }
      if (CheckOverlap(curr_point_on_path, obs_box, l_buffer)) {
        box_check_collision = true;
        break;
      }
    }

CheckOverlap函数

Vec2d ego_center_map_frame((vehicle_param_.front_edge_to_center() -
                              vehicle_param_.back_edge_to_center()) *
                                 0.5,
                             (vehicle_param_.left_edge_to_center() -
                              vehicle_param_.right_edge_to_center()) *
                                 0.5);

10250cbd4ef042e3a4d5229efa626930.png

上图坐标是车身坐标系,以后轴中心为原点,ego_center_map_frame就是计算自车中心点相对于后轴中心点的偏移量

SelfRotate函数

void Vec2d::SelfRotate(const double angle) {
  double tmp_x = x_;
  x_ = x_ * cos(angle) - y_ * sin(angle);
  y_ = tmp_x * sin(angle) + y_ * cos(angle);
}

75c01b840ca64eb58ca707b331eb5584.png

将偏移量旋转到路径点方向

ego_center_map_frame.set_x(ego_center_map_frame.x() + path_point.x());
ego_center_map_frame.set_y(ego_center_map_frame.y() + path_point.y());

设置自车中心点在笛卡尔坐标系的坐标
解释一下:
首先,向量和这个向量的起点位置无关,只与这个向量的长度和方向有关.所以上面的偏移我们可以理解为是从自车的后轴中心指向自车中心点的向量,方向是沿着笛卡尔的x轴方向.当车沿着轨迹行驶时,可以理解为把自车后轴中心点当成车,然后沿着路径点移动.调用SelfRotate函数后,相当于车头方向与路径点方向一致,那从自车的后轴中心指向自车中心点的向量的方向也与路径点方向一致,而此时自车后轴中心点的笛卡尔坐标就是当前所在路径点的笛卡尔坐标也就是(path_point.x(), path_point.y()),所以自车中心点的笛卡尔坐标就是(path_point.x(), path_point.y())加上偏移向量坐标(ego_center_map_frame.x(), ego_center_map_frame.y())

Box2d adc_box(ego_center_map_frame, path_point.theta(),
                vehicle_param_.length(), vehicle_param_.width() + l_buffer * 2);

构建自车bounding box
Box2d的构造过程在planning模块-静态障碍物碰撞检测 介绍过

obs_box.HasOverlap(adc_box);

HasOverlap函数的算法流程在planning模块-动态障碍物碰撞检测 介绍过如果忘了回看一下
如果会发生碰撞,则CheckOverlap函数会返回true
当CheckOverlap函数返回true时,box_check_collision变量会被设置为true

if (box_check_collision) {
      // const double backward_distance =
      // -vehicle_param_.front_edge_to_center();
      const double backward_distance = 0;
      const double forward_distance = obs_box.length();

      for (const auto& curr_point_on_path : path_points) {
        if (curr_point_on_path.s() > planning_max_distance_) {
          break;
        }
        const Polygon2d& obs_polygon = obstacle.PerceptionPolygon();

obs_polygon是表示障碍物的二维多边形,上面是先用障碍物和自车的bouding box做了一个粗的碰撞检测,然后再用polygon再进一步判断

Polygon2d ego_collision_polygon;
        if (CheckOverlap(curr_point_on_path, obs_polygon, l_buffer, &ego_collision_polygon))

这个CheckOverlap函数第二个参数使用的obs_polygon,也就意味着这里使用的二维多边形来判断障碍物和自车是否有重叠部分

Polygon2d adc_polygon(adc_box);
  if (obs_polygon.HasOverlap(adc_polygon)) {
    if (collision_ego_polygon != nullptr) {
      *collision_ego_polygon = adc_polygon;
    }
    return true;
  }
Polygon2d::Polygon2d(const Box2d &box) {
  box.GetAllCorners(&points_);
  BuildFromPoints();
}

box.GetAllCorners(&points_);是在获取box的角点,box的角点初始化可以回看planning模块-静态障碍物碰撞检测 Box2d::InitCorners()部分

BuildFromPoints函数

void Polygon2d::BuildFromPoints(bool check_area) {
  num_points_ = static_cast<int>(points_.size());
  CHECK_GE(num_points_, 3);

  // Make sure the points are in ccw order.
  area_ = 0.0;
  for (int i = 1; i < num_points_; ++i) {
    area_ += CrossProd(points_[0], points_[i - 1], points_[i]);
  }

area_:是下图蓝色多边形和绿色多边形面积的总和

dcdaf0e27b464f76b667a7423709f112.png

area_的正负表示遍历的角点顺序是按照顺时针方向遍历的还是按照逆时针方向遍历的,如果area_大于0,表示角点顺序是按照逆时针方向遍历的,如果area_小于0,表示角点顺序是按照顺时针方向遍历的

if (area_ < 0) {
    area_ = -area_;
    std::reverse(points_.begin(), points_.end());
  }

保证角点都是按照逆时针顺序方向遍历的

area_ /= 2.0;

得到的是p0,p1,p2,p3构成的多边形的面积

  line_segments_.reserve(num_points_);
  for (int i = 0; i < num_points_; ++i) {
    line_segments_.emplace_back(points_[i], points_[Next(i)]);
  }

构建多边形线段

is_convex_ = true;
  for (int i = 0; i < num_points_; ++i) {
    if (CrossProd(points_[Prev(i)], points_[i], points_[Next(i)]) <=
        -kMathEpsilon) {
      is_convex_ = false;
      break;
    }
  }

检查多边形线段方向是否保持一致

  min_x_ = points_[0].x();
  max_x_ = points_[0].x();
  min_y_ = points_[0].y();
  max_y_ = points_[0].y();
  for (const auto &point : points_) {
    min_x_ = std::min(min_x_, point.x());
    max_x_ = std::max(max_x_, point.x());
    min_y_ = std::min(min_y_, point.y());
    max_y_ = std::max(max_y_, point.y());
  }

构建AABB box

Polygon2d adc_polygon(adc_box);
  if (obs_polygon.HasOverlap(adc_polygon)) {
    if (collision_ego_polygon != nullptr) {
      *collision_ego_polygon = adc_polygon;
    }
    return true;
  }

HasOverlap函数

  if (polygon.max_x() < min_x() || polygon.min_x() > max_x() ||
      polygon.max_y() < min_y() || polygon.min_y() > max_y()) {
    return false;
  }

先根据AABB box做一个碰撞检测

Polygon2d::IsPointIn函数

Polygon2d::IsPointOnBoundary函数

bool Polygon2d::IsPointOnBoundary(const Vec2d &point) const {
  CHECK_GE(points_.size(), 3U);
  return std::any_of(
      line_segments_.begin(), line_segments_.end(),
      [&](const LineSegment2d &poly_seg) { return poly_seg.IsPointIn(point); });
}

LineSegment2d::IsPointIn函数

  if (length_ <= kMathEpsilon) {
    return std::abs(point.x() - start_.x()) <= kMathEpsilon &&
           std::abs(point.y() - start_.y()) <= kMathEpsilon;
  }

如果线段的长度几乎为0(起点和终点重合),那么线段退化为一个点
此时只需判断输入的point是否与start_ 坐标重合即可

const double prod = CrossProd(point, start_, end_);
  if (std::abs(prod) > kMathEpsilon) {
    return false;
  }

当调用 CrossProd(point, start_, end_) 时,实际上在内部发生了以下转换:
向量 A = start_ - point (从点指向线段起点)
向量 B = end_ - point (从点指向线段终点)
然后计算这两个向量的二维向量叉积​\vec{A} \times \vec{B}
如果叉积为0说明点在线段延长线上,如果叉积不为0说明点不在线段延长线上

  return IsWithin(point.x(), start_.x(), end_.x()) &&
         IsWithin(point.y(), start_.y(), end_.y());
bool IsWithin(double val, double bound1, double bound2) {
  if (bound1 > bound2) {
    std::swap(bound1, bound2);
  }
  return val >= bound1 - kMathEpsilon && val <= bound2 + kMathEpsilon;
}

IsWithin函数主要是判断点是否在线段范围内
到这Polygon2d::IsPointOnBoundary函数就介绍完了

  int j = num_points_ - 1;
  int c = 0;
  for (int i = 0; i < num_points_; ++i) {
    if ((points_[i].y() > point.y()) != (points_[j].y() > point.y())) {
      const double side = CrossProd(point, points_[i], points_[j]);
      if (points_[i].y() < points_[j].y() ? side > 0.0 : side < 0.0) {
        ++c;
      }
    }
    j = i;
  }
  return c & 1;

首先,判断多边形的边与点的水平延长线是否存在交集,存在交集的前提下,才会继续执行后续的逻辑

1f57d35245dc4cae9ff7de021cc2f3f3.png

通过叉积计算出的side值,如果side > 0, 也就是​\vec{pp_{0}} \times \vec{pp_{3}}大于0,表示​\vec{pp_{0}}​\vec{pp_{3}}的夹角是逆时针的角度,沿着​p_{0}​p_{3}的方向p点在​p_{0}p_{3}的左侧.如果side < 0, 也就意味着沿着​p_{0}​p_{3}的方向p点在​p_{0}p_{3}的右侧.

if (points_[i].y() < points_[j].y() ? side > 0.0 : side < 0.0) {
        ++c;
      }

根据射线法通过对某一点向右引出一条水平射线,然后统计与多边形的边的交点个数
如果points_[i].y() < points_[j].y(),说明当前这条边是向上指向,此时side如果大于0,说明点在这条向上边的左侧,所以交点个数需要增加.如果当前这条边是向下指向,此时side如果小于0,说明点在这条向下边的左侧,所以交点个数也需要增加.

return c & 1;

如果c是奇数,表示点在多边形内
如果c是偶数,表示点在多边形外

奇数的最后一位一定是1
偶数的最后一位一定是0
所以如果返回true,表明点在多边形内,否则点在多边形外
到这Polygon2d::IsPointIn函数就介绍完了

  for (int i = 0; i < polygon.num_points(); ++i) {
    if (HasOverlap(polygon.line_segments()[i])) {
      return true;
    }
  }

Polygon2d::HasOverlap函数

  if ((line_segment.start().x() < min_x_ && line_segment.end().x() < min_x_) ||
      (line_segment.start().x() > max_x_ && line_segment.end().x() > max_x_) ||
      (line_segment.start().y() < min_y_ && line_segment.end().y() < min_y_) ||
      (line_segment.start().y() > max_y_ && line_segment.end().y() > max_y_)) {
    return false;
  }

如果一条线段完全在多边形的外接矩形之外,那么它绝对不可能与该多边形相交


  if (std::any_of(line_segments_.begin(), line_segments_.end(),
                  [&](const LineSegment2d &poly_seg) {
                    return poly_seg.HasIntersect(line_segment);
                  })) {
    return true;
  }
bool LineSegment2d::HasIntersect(const LineSegment2d &other_segment) const {
  Vec2d point;
  return GetIntersect(other_segment, &point);
}

LineSegment2d::GetIntersect函数

if (IsPointIn(other_segment.start())) {
    *point = other_segment.start();
    return true;
  }
  if (IsPointIn(other_segment.end())) {
    *point = other_segment.end();
    return true;
  }

判断自车多边形的每一条边的起点和终点,是否落在了障碍物多边形的某一条边上

if (other_segment.IsPointIn(start_)) {
    *point = start_;
    return true;
  }
  if (other_segment.IsPointIn(end_)) {
    *point = end_;
    return true;
  }

判断障碍物多边形的每一条边的起点和终点,是否落在了自车多边形的某一条边上

const double cc1 = CrossProd(start_, end_, other_segment.start());
  const double cc2 = CrossProd(start_, end_, other_segment.end());
  if (cc1 * cc2 >= -kMathEpsilon) {
    return false;
  }

bcd21ffc560e4b5f9282d6abd566b396.png

start-end:是障碍物多边形的某一条边
adc_start-adc_end:是自车多边形的某一条边
当adc_start-adc_end与start-end两条边没有交点时cc1 * cc2 > 0,有交点时cc1 * cc2 < 0

  const double cc3 =
      CrossProd(other_segment.start(), other_segment.end(), start_);
  const double cc4 =
      CrossProd(other_segment.start(), other_segment.end(), end_);
  if (cc3 * cc4 >= -kMathEpsilon) {
    return false;
  }

1bf292ebe81542b7af48c00cfdbe80c3.png

cc3和cc4,cc1和cc2类似只不过是以自车多边形的边为基础边

const double ratio = cc4 / (cc4 - cc3);
  *point = Vec2d(start_.x() * ratio + end_.x() * (1.0 - ratio),
                 start_.y() * ratio + end_.y() * (1.0 - ratio));

eaef39e093e74071a22dfb9ca7d6f148.png

08ae6538cc6f495b9c78f306f029043f.png

到这LineSegment2d::GetIntersect函数就介绍完了
Polygon2d::HasOverlap函数也介绍完了
同样STBoundaryMapper::CheckOverlap也介绍完了
我们再回到当box_check_collision为true时,并且CheckOverlap函数返回true时(自车与障碍物会发生碰撞)

double low_s = std::fmax(0.0, curr_point_on_path.s() + backward_distance);
double high_s = std::fmin(planning_max_distance_, curr_point_on_path.s() + forward_distance);

low_s:碰撞发生时自车后轴中心所在的位置
high_s:后轴中心位置+障碍物的bouding box长度(这里做了简化)

lower_points->emplace_back(low_s - speed_bounds_config_.point_extension(), 0.0);
          lower_points->emplace_back(low_s - speed_bounds_config_.point_extension(), planning_max_time_);
          upper_points->emplace_back(high_s + speed_bounds_config_.point_extension(), 0.0);
          upper_points->emplace_back(high_s + speed_bounds_config_.point_extension(), planning_max_time_);

生成障碍物ST边界点,在ST图上形成一个矩形危险区域
speed_bounds_config_.point_extension()默认配置为0

164a5971c44043a4ba9b3a745adabc02.png

上面是trajectory.trajectory_point().empty()的情况,也就是障碍物没有预测轨迹线的情况,障碍物ST边界点的构建.
接下来是有预测轨迹线的障碍物的ST边界点生成过程

    const int default_num_point = 50;
    DiscretizedPath discretized_path;
    if (path_points.size() > 2 * default_num_point) {
      const auto ratio = path_points.size() / default_num_point;
      std::vector<PathPoint> sampled_path_points;
      for (size_t i = 0; i < path_points.size(); ++i) {
        if (i % ratio == 0) {
          sampled_path_points.push_back(path_points[i]);
        }
      }
      discretized_path = DiscretizedPath(std::move(sampled_path_points));
    } else {
      discretized_path = DiscretizedPath(path_points);
    }

当路径点的个数大于100的情况时,为了计算效率对路径点进行采样,否则不需要进行采样

double trajectory_time_interval =
        obstacle.Trajectory().trajectory_point()[1].relative_time();

获取障碍物轨迹第2个点的相对时间,表示轨迹点之间的时间间隔

int trajectory_step = std::min(FLAGS_trajectory_check_collision_time_step,
                      std::max(vehicle_param_.width() / obstacle.speed() /
                      trajectory_time_interval,
                      1.0));
bool trajectory_point_collision_status = false;
int previous_index = 0;
  • 障碍物横向移动一个自车车宽的距离是一个"显著变化"
  • 在这个距离内至少检查一次碰撞
  • 确保不会漏掉重要的碰撞状态变化

vehicle_param_.width() / obstacle.speed(),表示障碍物移动一个自车的车宽所需的时间
vehicle_param_.width() / obstacle.speed() / trajectory_time_interval,障碍物移动一个自车车宽的距离的轨迹点个数
FLAGS_trajectory_check_collision_time_step:默认是1
trajectory_step:表示每个几个轨迹点进行一次碰撞检测

for (int i = 0; i < trajectory.trajectory_point_size();
         i = std::min(i + trajectory_step,
                      trajectory.trajectory_point_size() - 1)) {
      const auto& trajectory_point = trajectory.trajectory_point(i);
      Polygon2d obstacle_shape =
          obstacle.GetObstacleTrajectoryPolygon(trajectory_point);

每隔trajectory_step步长进行一次判断

GetObstacleTrajectoryPolygon函数

根据轨迹点,计算障碍物在该时刻的多边形

common::math::Polygon2d Obstacle::GetObstacleTrajectoryPolygon(
    const common::TrajectoryPoint& point) const {
  double delta_heading =
      point.path_point().theta() - perception_obstacle_.theta();

point.path_point().theta():轨迹点时刻的方向角
perception_obstacle_.theta():感知时刻的方向角
delta_heading:从感知到当前轨迹点的航向角变化

double cos_delta_heading = cos(delta_heading);
  double sin_delta_heading = sin(delta_heading);
  std::vector<common::math::Vec2d> polygon_point;
  polygon_point.reserve(perception_polygon_.points().size());

  for (auto& iter : perception_polygon_.points()) {
    double relative_x = iter.x() - perception_obstacle_.position().x();
    double relative_y = iter.y() - perception_obstacle_.position().y();

计算障碍物多边形每个顶点相对于障碍物中心点的位置

double x = relative_x * cos_delta_heading - relative_y * sin_delta_heading +
               point.path_point().x();
    double y = relative_x * sin_delta_heading + relative_y * cos_delta_heading +
               point.path_point().y();

delta_heading:是当前预测轨迹点方向角与障碍物方向角的差值,相当于障碍物从开始位置行驶到预测轨迹点位置时方向角变化的值
开始时,障碍物多边形的每个顶点相对于障碍物中心点的位置坐标是(relative_x, relative_y),现在障碍物行驶到预测轨迹点的位置,相当于旋转了delta_heading这么多角度,那么在这个轨迹点的位置时,障碍物多边形的每个顶点相对于这个轨迹点的坐标就需要通过旋转矩阵求得
也就是
relative_x * cos_delta_heading - relative_y * sin_delta_heading
relative_x * sin_delta_heading + relative_y * cos_delta_heading
此时是在这个轨迹点的位置,障碍物多边形的每个顶点相对于障碍物中心点的坐标
再加上point.path_point().x()和point.path_point().y()就是障碍物多边形在这个轨迹点的位置,障碍物多边形每个顶点的全局坐标也就是笛卡尔坐标

cffe467af1df4ff69527370b0e50dbda.png

common::math::Polygon2d trajectory_point_polygon(polygon_point);

然后构建预测点位置的障碍物多边形
这就是common::math::Polygon2d Obstacle::GetObstacleTrajectoryPolygon函数的作用

      bool collision = CheckOverlapWithTrajectoryPoint(
          discretized_path, obstacle_shape, upper_points, lower_points,
          l_buffer, default_num_point, obstacle_length, obstacle_width,
          trajectory_point_time);

discretized_path:可以理解为是路径点信息
obstacle_shape:障碍物多边形信息
upper_points/lower_points:是输出参数
l_buffer:自车对于动态障碍物的横向缓冲距离
default_num_point:目标采样点数
obstacle_length:障碍物bouding box的长度
obstacle_width:障碍物bouding box的宽度
trajectory_point_time:当前预测轨迹点相对于规划起点的时间

CheckOverlapWithTrajectoryPoint函数

const double step_length = vehicle_param_.front_edge_to_center();
  auto path_len = std::min(speed_bounds_config_.max_trajectory_len(),
                           discretized_path.Length());
  // Go through every point of the ADC's path.
  for (double path_s = 0.0; path_s < path_len; path_s += step_length) 

把自车最前边缘到后轴中心的距离作为步长,进行自车与障碍物的碰撞检测

const auto curr_adc_path_point =
        discretized_path.Evaluate(path_s + discretized_path.front().s());
PathPoint DiscretizedPath::Evaluate(const double path_s) const {
  ACHECK(!empty());
  auto it_lower = QueryLowerBound(path_s);
  if (it_lower == begin()) {
    return front();
  }
  if (it_lower == end()) {
    return back();
  }
  return common::math::InterpolateUsingLinearApproximation(*(it_lower - 1),
                                                           *it_lower, path_s);
}

std::vector<PathPoint>::const_iterator DiscretizedPath::QueryLowerBound(
    const double path_s) const {
  auto func = [](const PathPoint &tp, const double path_s) {
    return tp.s() < path_s;
  };
  return std::lower_bound(begin(), end(), path_s, func);
}

QueryLowerBound找到路径点中第一个大于等于path_s位置的点

InterpolateUsingLinearApproximation函数

在两个已知路径点之间进行线性插值,计算指定 s 坐标处的路径点

  double s0 = p0.s();
  double s1 = p1.s();

s0/s1:已知的两个路径点的纵向距离

PathPoint path_point;
  double weight = (s - s0) / (s1 - s0);
  double x = (1 - weight) * p0.x() + weight * p1.x();
  double y = (1 - weight) * p0.y() + weight * p1.y();

使用线性插值计算s位置的坐标(x,y)

792ce62547e44b308b9cc9b2b05deb35.png

double theta = slerp(p0.theta(), p0.s(), p1.theta(), p1.s(), s);

slerp函数

p0.theta()/p1.theta():路径点的方向角
p0.s()/p1.s():路径点的纵向距离
s:当前遍历到的位置
球面线形插值

NormalizeAngle函数

参考NormalizeAngle之前的介绍主要是把任意角度归一化到 [-π, π)
planning模块(1)-参考线的生成

  if (std::abs(t1 - t0) <= kMathEpsilon) {
    ADEBUG << "input time difference is too small";
    return NormalizeAngle(a0);
  }
  • 如果t0和t1太接近(几乎相同)
  • 时间差太小,无法插值
  • 直接返回起始角度a0
const double a0_n = NormalizeAngle(a0);
  const double a1_n = NormalizeAngle(a1);
  double d = a1_n - a0_n;

计算两个路径点间的方向角的差值

  if (d > M_PI) {
    d = d - 2 * M_PI;
  } else if (d < -M_PI) {
    d = d + 2 * M_PI;
  }

67f7636fc3b246fbab1157379ef7f9b3.png

如果从10度到350度,也就是角度差d = 350 - 10 = 340
340 > 180, d = 340 - 2 * 180 =340 - 360 = -20
也就是表示选择从10度顺时针旋转20度到350度的这个旋转,而不是选择从10度逆时针旋转340度的那个旋转
如果从350度到10度,也就是角度差度d = 10 - 350 = -340
-340 < -180, d = -340 + 360 = 20
也就是表示选择从350度逆时针旋转20度的这个旋转,而不是选择从350度顺时针旋转340度的那个旋转
这段代码保证旋转的角度是小角度

const double r = (t - t0) / (t1 - t0);
  const double a = a0_n + d * r;
  return NormalizeAngle(a);

上面是角度插值,和线性插值类似
r:是s位置到第一个路径点的角度差,占两个路径点角角度差的比例
a:通过角度插值得到的s位置的方向角

double theta = slerp(p0.theta(), p0.s(), p1.theta(), p1.s(), s);
  double kappa = (1 - weight) * p0.kappa() + weight * p1.kappa();
  double dkappa = (1 - weight) * p0.dkappa() + weight * p1.dkappa();
  double ddkappa = (1 - weight) * p0.ddkappa() + weight * p1.ddkappa();

曲率,及曲率变化,及曲率变化的变化,都是通过线性插值计算出出来的

  path_point.set_x(x);
  path_point.set_y(y);
  path_point.set_theta(theta);
  path_point.set_kappa(kappa);
  path_point.set_dkappa(dkappa);
  path_point.set_ddkappa(ddkappa);
  path_point.set_s(s);
  return path_point;

最后赋值给path_point
这样就通过插值计算出了采样点位置的curr_adc_path_point路径点信息

if (CheckOverlap(curr_adc_path_point, obstacle_shape, l_buffer))
const double backward_distance = 0.0;
      const double forward_distance = vehicle_param_.length() +
                                      vehicle_param_.width() + obstacle_length +
                                      obstacle_width;
      const double default_min_step = 0.1;  // in meters
      const double fine_tuning_step_length = std::fmin(
          default_min_step, discretized_path.Length() / default_num_point);

      bool find_low = false;
      bool find_high = false;
      double low_s = std::fmax(0.0, path_s + backward_distance);
      double high_s =
          std::fmin(discretized_path.Length(), path_s + forward_distance);

当检测到当前采样路径点位置上的自车多边形与当前障碍物预测轨迹点位置上所构成的多边形发生碰撞时,设置搜索范围的上下界

动态障碍物的特点:

  • 障碍物会移动
  • 需要找到精确的碰撞边界
  • 通过后续的精细搜索逐步收缩

所以初始范围[low_s,high_s]可以设大一点,确保不会漏掉碰撞区域

      while (low_s < high_s) {
        if (find_low && find_high) {
          break;
        }
        if (!find_low) {
          const auto& point_low =
              discretized_path.Evaluate(low_s + discretized_path.front().s());
          if (!CheckOverlap(point_low, obstacle_shape, l_buffer)) {
            low_s += fine_tuning_step_length;
          } else {
            find_low = true;
          }
        }
        if (!find_high) {
          const auto& point_high =
              discretized_path.Evaluate(high_s + discretized_path.front().s());
          if (!CheckOverlap(point_high, obstacle_shape, l_buffer)) {
            high_s -= fine_tuning_step_length;
          } else {
            find_high = true;
          }
        }
      }

查找到碰撞区间[low_s,high_s]

 if (find_high && find_low) {
   lower_points->emplace_back(
       low_s - speed_bounds_config_.point_extension(),
       trajectory_point_time);
   upper_points->emplace_back(
       high_s + speed_bounds_config_.point_extension(),
       trajectory_point_time);
 }

然后生成障碍物ST边界点

      if ((trajectory_point_collision_status ^ collision) && i != 0) {
        // Start retracing track points forward
        int index = i - 1;
        while ((trajectory_point_collision_status ^ collision) &&
               index > previous_index) {
          const auto& point = trajectory.trajectory_point(index);
          trajectory_point_time = point.relative_time();
          obstacle_shape = obstacle.GetObstacleTrajectoryPolygon(point);
          collision = CheckOverlapWithTrajectoryPoint(
              discretized_path, obstacle_shape, upper_points, lower_points,
              l_buffer, default_num_point, obstacle_length, obstacle_width,
              trajectory_point_time);
          index--;
        }
        trajectory_point_collision_status = !trajectory_point_collision_status;
      }

第一次调用CheckOverlapWithTrajectoryPoint时,是按照步长trajectory_step进行采样并做碰撞检测的,这是一次粗检,有些预测轨迹点可能没有检测到比如:

93d2c63938414a3b821f6bbda7d8affd.png

这样像1,2,4,5这些轨迹点就没有做碰撞检测,但有可能在1,2轨迹点的时候就发生了碰撞
所以上面的逻辑又做了一次回溯的碰撞检测,比如当粗检发现在预测轨迹点3的位置会发生碰撞,那么就会回溯到预测轨迹点2,在预测轨迹点2做碰撞检测,如果预测轨迹点2也会发生碰撞,就再继续往1回溯,然后再往0回溯,直到不能回溯位置,这样就会获取到所有会发生碰撞的位置,然后分别存入到lower_points和upper_points中.

  std::sort(lower_points->begin(), lower_points->end(),
            [](const STPoint& a, const STPoint& b) { return a.t() < b.t(); });
  std::sort(upper_points->begin(), upper_points->end(),
            [](const STPoint& a, const STPoint& b) { return a.t() < b.t(); });

对lower_points和upper_points按照相对于规划起点的时间从小到大进行排序
到这,无论是没有预测轨迹线还是有预测轨迹线的障碍物的ST边界点的获取逻辑都介绍完了.
GetOverlapBoundaryPoints函数也就介绍完了

  auto boundary = STBoundary::CreateInstance(lower_points, upper_points);
  • 使用之前计算出的lower_points和upper_points
  • 创建一个STBoundary对象
  • 这个对象表示障碍物在ST图上的边界
boundary.set_id(obstacle->Id());

给 ST 边界设置障碍物的 ID

const auto& prev_st_boundary = obstacle->path_st_boundary();
const auto& ref_line_st_boundary = obstacle->reference_line_st_boundary();
  • prev_st_boundary:障碍物在之前规划周期的ST边界
  • ref_line_st_boundary:障碍物在参考线上的ST边界
if (!prev_st_boundary.IsEmpty()) {
  boundary.SetBoundaryType(prev_st_boundary.boundary_type());
} else if (!ref_line_st_boundary.IsEmpty()) {
  boundary.SetBoundaryType(ref_line_st_boundary.boundary_type());
}
  • 优先使用之前规划周期的边界类型
  • 否则使用参考线上的边界类型
  • 如果都为空 :保持默认类型
obstacle->set_path_st_boundary(boundary);
  • 将创建好的 ST 边界设置到障碍物对象
  • 后续的速度规划会使用这个边界

到这ComputeSTBoundary函数就介绍完了

const auto& decision = ptr_obstacle->LongitudinalDecision();
    if (decision.has_stop()) {
      // 1. Store the closest stop fence info.
      // TODO(all): store ref. s value in stop decision; refine the code then.
      common::SLPoint stop_sl_point;
      reference_line_.XYToSL(decision.stop().stop_point(), &stop_sl_point);
      const double stop_s = stop_sl_point.s();

      if (stop_s < min_stop_s) {
        stop_obstacle = ptr_obstacle;
        min_stop_s = stop_s;
        stop_decision = decision;
      }
    }

当遍历到的障碍物存在纵向停车决策时,首先会获取停止点stop_sl_point的纵向距离,然后找到所有存在纵向停车决策的障碍物中的最小停车距离,并记录障碍物信息和决策信息

else if (decision.has_follow() || decision.has_overtake() ||
               decision.has_yield()) {
      // 2. Depending on the longitudinal overtake/yield decision,
      //    fine-tune the upper/lower st-boundary of related obstacles.
      ComputeSTBoundaryWithDecision(ptr_obstacle, decision);
    }

根据纵向决策(跟随、让行、超车)调整障碍物的 ST 边界

ComputeSTBoundaryWithDecision函数

if (FLAGS_use_st_drivable_boundary &&
      obstacle->is_path_st_boundary_initialized()) {
    const auto& path_st_boundary = obstacle->path_st_boundary();
    lower_points = path_st_boundary.lower_points();
    upper_points = path_st_boundary.upper_points();
  } 

上面逻辑主要是判断是否可以复用之前已经计算好的 ST 边界,避免重复计算

FLAGS_use_st_drivable_boundary:启用了ST可行驶区域边界模式(默认是false)
obstacle->is_path_st_boundary_initialized():这个障碍物的 ST 边界之前已经计算过了
两个条件都满足时,直接用之前算好的,不用重新算

const auto& path_st_boundary = obstacle->path_st_boundary();
lower_points = path_st_boundary.lower_points();
upper_points = path_st_boundary.upper_points();

条件不满足时,使用GetOverlapBoundaryPoints重新计算

else {
   if (!GetOverlapBoundaryPoints(path_data_.discretized_path(), *obstacle,
                                 &upper_points, &lower_points)) 
   {
     return;
   }
  }

上面已经介绍过了GetOverlapBoundaryPoints函数

auto boundary = STBoundary::CreateInstance(lower_points, upper_points);

CreateInstance函数

STBoundary STBoundary::CreateInstance(
    const std::vector<STPoint>& lower_points,
    const std::vector<STPoint>& upper_points) {
  if (lower_points.size() != upper_points.size() || lower_points.size() < 2) {
    return STBoundary();
  }

下边界点的个数要和上边界点相同,也就是要成对出现,并且要保证至少2对,才可以构成一个边界,也就是ST图上不可通行区域.

3441f0ba7f544279aea9262fd0536376.png

std::vector<std::pair<STPoint, STPoint>> point_pairs;
for (size_t i = 0; i < lower_points.size(); ++i) {
  point_pairs.emplace_back(
      STPoint(lower_points.at(i).s(), lower_points.at(i).t()),
      STPoint(upper_points.at(i).s(), upper_points.at(i).t()));
}

将下边界点和上边界点配对

return STBoundary(point_pairs);
STBoundary::STBoundary(
    const std::vector<std::pair<STPoint, STPoint>>& point_pairs,
    bool is_accurate_boundary) {

RemoveRedundantPoints函数

void STBoundary::RemoveRedundantPoints(
    std::vector<std::pair<STPoint, STPoint>>* point_pairs)

移除ST边界点对中的冗余点 ,保留边界形状的同时减少点的数量

 while (i < point_pairs->size() && j + 1 < point_pairs->size()) {

1ea3bf97792747b7b0542ec13cc2276a.png

现在假设point_pairs的size是3,也就意味着有3对{lower_point, upper_point},如上图红色表示lower_point,蓝色表示upper_point

LineSegment2d lower_seg(point_pairs->at(i).first,
                            point_pairs->at(j + 1).first);

初始i = 0,j = 1
point_pairs->at(i).first:表示第一对的lower_point
point_pairs->at(j + 1).first:表示第三对的lower_point

LineSegment2d::LineSegment2d(const Vec2d &start, const Vec2d &end)
    : start_(start), end_(end) {

然后通过第一对的lower_point和第三对的lower_point构造LineSegment2d

const double dx = end_.x() - start_.x();
const double dy = end_.y() - start_.y();

可以理解为下图绿色的向量

b9738494d961474bb2f34a137672b128.png

length_ = hypot(dx, dy);

是上面绿色向量的模长​\sqrt{(dx^2 + dy^2)}

unit_direction_ =
      (length_ <= kMathEpsilon ? Vec2d(0, 0)
                               : Vec2d(dx / length_, dy / length_));

如果length_很小,绿色向量的单位向量就是零向量,否则计算绿色向量的单位向量

heading_ = unit_direction_.Angle();

d4be0adc0e474d8f932acf5ed519e1ad.png

double Vec2d::Angle() const { return std::atan2(y_, x_); }

上面是计算基于笛卡尔坐标系的x轴的夹角

LineSegment2d upper_seg(point_pairs->at(i).second,
                            point_pairs->at(j + 1).second);

同理,通过第一对的upper_point和第三对的upper_point构造LineSegment2d

IsPointNear(lower_seg, point_pairs->at(j).first, kMaxDist)

037b30f8c9534a3d86a04f57f6eaf3e9.png

point_pairs->at(j).first:表示的是第二对的lower_point

bool STBoundary::IsPointNear(const common::math::LineSegment2d& seg,
                             const Vec2d& point, const double max_dist) {
  return seg.DistanceSquareTo(point) < max_dist * max_dist;
}

然后通过调用IsPointNear函数来判断,该点到绿色向量的距离

double LineSegment2d::DistanceSquareTo(const Vec2d &point) const {
  if (length_ <= kMathEpsilon) {
    return point.DistanceSquareTo(start_);
  }

如果绿色向量的模长很小,就用线段的起点到point的距离来表示点到线段的距离,这里的point.DistanceSquareTo的距离是未开平方的

const double x0 = point.x() - start_.x();
const double y0 = point.y() - start_.y();
const double proj = x0 * unit_direction_.x() + y0 * unit_direction_.y();

x0,y0是绿色向量起点指向上面索引为1的lower_point的向量​\vec{A},再乘以绿色向量​\vec{B}的单位向量​\vec{I}​\vec{proj}就表示​\vec{A}​\vec{B}上的带方向的投影

845ac7abe6564b919bb7b761d2b9281d.png

  if (proj <= 0.0) {
    return Square(x0) + Square(y0);
  }

若proj <= 0,则索引1的lower_point到线段的距离就是​\vec{A}的长度的平方,用索引1的lower_point到线段起点的长度的平方表示

  if (proj >= length_) {
    return point.DistanceSquareTo(end_);
  }

若proj >= length_,则索引1的lower_point到线段的距离就用索引1的lower_point到线段终点的长度的平方表示

return Square(x0 * unit_direction_.y() - y0 * unit_direction_.x());

若0 < proj < length_,则用索引1的lower_point与单位向量​I的叉积表示,可以理解为点到线段的距离
point_pairs->at(j).second:表示的是第二对的upper_point

IsPointNear(upper_seg, point_pairs->at(j).second, kMaxDist)

6faf89ea3998436c84903ee47ccab27e.png

同理,是在计算这个点基于紫色线段的距离

    if (!IsPointNear(lower_seg, point_pairs->at(j).first, kMaxDist) ||
        !IsPointNear(upper_seg, point_pairs->at(j).second, kMaxDist)) {
      ++i;
      if (i != j) {
        point_pairs->at(i) = point_pairs->at(j);
      }
    }
    ++j;

若计算出的点到线段的距离不是离的很近,就保留当前j指向的点,i继续向前移动继续遍历检查
若距离离的很近

point_pairs->at(++i) = point_pairs->back();
point_pairs->resize(i + 1);

这里一共是3对,如果第二对的lower_point或upper_point离线段很近很近,那么就会把这一对用第三对的值覆盖掉,此时i是1,size是2.

  for (const auto& item : reduced_pairs) {
    // use same t for both points
    const double t = item.first.t();
    lower_points_.emplace_back(item.first.s(), t);
    upper_points_.emplace_back(item.second.s(), t);
  }

将输入的点对分离成两个独立的数组:

  • lower_points_ :下边界点(靠近时间轴)
  • upper_points_ :上边界点(远离时间轴)
  for (const auto& point : lower_points_) {
    points_.emplace_back(point.t(), point.s());
  }

将下边界点按时间顺序添加到points_数组中

  for (auto rit = upper_points_.rbegin(); rit != upper_points_.rend(); ++rit) {
    points_.emplace_back(rit->t(), rit->s());
  }

将上边界点按时间递减的逆序添加到 points_ 数组中
BuildFromPoints()函数已经介绍过了,主要是根据points_构建多边形

  for (const auto& point : lower_points_) {
    min_s_ = std::fmin(min_s_, point.s());
  }

遍历所有下边界点,找到最小的 s 坐标

for (const auto& point : upper_points_) {
  max_s_ = std::fmax(max_s_, point.s());
}

遍历所有上边界点,找到最大的 s 坐标

min_t_ = lower_points_.front().t();
max_t_ = lower_points_.back().t();

计算时间范围


下面逻辑是根据纵向决策类型(跟车/让行/超车)设置ST边界的边界类型和特征长度(上下边界向外扩展的长度)
跟车决策/让行决策/超车决策
首先都会获取各自的跟车距离/让行距离/超车距离(距离前车多少米开始超车动作的距离)

STBoundary::CreateInstance(lower_points, upper_points)
                   .ExpandByS(characteristic_length)

然后分别根据各自的characteristic_length向外扩展自己的边界范围,形成新的集合边界
b_type:设置边界类型

  boundary.SetBoundaryType(b_type);
  boundary.set_id(obstacle->Id());
  boundary.SetCharacteristicLength(characteristic_length);
  obstacle->set_path_st_boundary(boundary);

然后设置最终的边界类型,关联的障碍物id,边界向外扩展的长度,将边界信息设置给对应的障碍物
到这ComputeSTBoundaryWithDecision函数就介绍完了

  if (stop_obstacle) {
    bool success = MapStopDecision(stop_obstacle, stop_decision);
    if (!success) {
      const std::string msg = "Fail to MapStopDecision.";
      AERROR << msg;
      return Status(ErrorCode::PLANNING_ERROR, msg);
    }
  }

为最近的停车障碍物创建停车边界

common::SLPoint stop_sl_point;
reference_line_.XYToSL(stop_decision.stop().stop_point(), &stop_sl_point);

获取停车点的纵向距离,XYToSL函数介绍可以参考planning模块-笛卡尔坐标转Frenet坐标

const double stop_ref_s =
      stop_sl_point.s() - vehicle_param_.front_edge_to_center();

e5e89d8a292445b4909e205c2b1b6f9c.png

stop_sl_point.s():是绿色横线的位置,而实际自车在行驶时,是以后轴中心作为车的表示,可以理解为把车看作了一个点,而那个点就是车辆的后轴中心。而stop_sl_point.s()表示的位置是车停止时,车头所在的位置,而后轴中心所在的位置是上图停止点的位置,所以stop_ref_s这个停止点的位置需要用stop_sl_point.s()减去车辆最前边缘到后轴中心的距离(vehicle_param_.front_edge_to_center()).

97ef96ae79cb46fb89d889f715f137ab.png

if (stop_ref_s > path_data_.frenet_frame_path().back().s()) {
    st_stop_s = path_data_.discretized_path().back().s() +
                (stop_ref_s - path_data_.frenet_frame_path().back().s());
  }

如果停车位置超出了规划路径的终点,那么会使用离散化路径终点+超出距离,来估算出来一个停车位置
如果停车位置没有超出规划路径的终点,则使用GetPathPointWithRefS函数获取停止点位置

GetPathPointWithRefS函数

  for (uint32_t i = 0; i + 1 < frenet_path_.size(); ++i) {
    if (fabs(ref_s - frenet_path_.at(i).s()) < kDistanceEpsilon) {
      path_point->CopyFrom(discretized_path_.at(i));
      return true;
    }
    if (frenet_path_.at(i).s() < ref_s && ref_s <= frenet_path_.at(i + 1).s()) {
      index = i;
      break;
    }
  }

如果ref_s的位置和路径规划的当前路径点离的很近,就把当前的路径点作为停车点

double r = (ref_s - frenet_path_.at(index).s()) /
             (frenet_path_.at(index + 1).s() - frenet_path_.at(index).s());
 const double discretized_path_s = discretized_path_.at(index).s() +
                                    r * (discretized_path_.at(index + 1).s() -
                                         discretized_path_.at(index).s());
path_point->CopyFrom(discretized_path_.Evaluate(discretized_path_s));

如果不是离的很近就去查找,找到对应的index,然后基于离散化的路径进行一次线性插值获取到discretized_path_s()(这个值只是纵向距离),然后还需要通过Evaluate函数线性插值出其他路径点的信息.Evaluate函数在上面已经介绍过了.
到这GetPathPointWithRefS函数就介绍完了

st_stop_s = stop_point.s();

获取到停车点后,通过停车点获取到停车距离

 const double s_min = std::fmax(0.0, st_stop_s);
  const double s_max = std::fmax(
      s_min, std::fmax(planning_max_distance_, reference_line_.Length()));

s_min:是停车区域的起点
s_max:是停车区域的终点
表示在纵向范围内[s_min, s_max]车辆无法通行

  point_pairs.emplace_back(STPoint(s_min, 0.0), STPoint(s_max, 0.0));
  point_pairs.emplace_back(
      STPoint(s_min, planning_max_time_),
      STPoint(s_max + speed_bounds_config_.boundary_buffer(),
              planning_max_time_));
  auto boundary = STBoundary(point_pairs);

创建ST边界

  boundary.SetBoundaryType(STBoundary::BoundaryType::STOP);
  boundary.SetCharacteristicLength(speed_bounds_config_.boundary_buffer());
  boundary.set_id(stop_obstacle->Id());
  stop_obstacle->set_path_st_boundary(boundary);

同理,设置其它边界的信息
到这MapStopDecision函数就介绍完了
到这ComputeSTBoundary函数也介绍完了

  std::vector<const STBoundary *> boundaries;
  for (auto *obstacle : path_decision->obstacles().Items()) {
    const auto &id = obstacle->Id();
    const auto &st_boundary = obstacle->path_st_boundary();
    if (!st_boundary.IsEmpty()) {
      if (st_boundary.boundary_type() == STBoundary::BoundaryType::KEEP_CLEAR) {
        path_decision->Find(id)->SetBlockingObstacle(false);
      } else {
        path_decision->Find(id)->SetBlockingObstacle(true);
      }
      st_boundary.PrintDebug("_obs_st_bounds");
      boundaries.push_back(&st_boundary);
    }
  }

遍历所有障碍物,当障碍物的ST边界不为空时,并且当边界类型是禁停区时,设置该障碍物为非阻塞障碍物
否则设置为阻塞障碍物

const double min_s_on_st_boundaries = SetSpeedFallbackDistance(path_decision);

SetSpeedFallbackDistance函数

计算速度规划的回退距离,当正常速度规划失败时,提供一个保守的安全停车位置

double SpeedBoundsDecider::SetSpeedFallbackDistance(
    PathDecision *const path_decision) {
for (auto *obstacle : path_decision->obstacles().Items()) {
    const auto &st_boundary = obstacle->path_st_boundary();

    if (st_boundary.IsEmpty()) {
      continue;
    }

遍历所有障碍物,只处理存在ST边界的

const auto left_bottom_point_s = st_boundary.bottom_left_point().s();
const auto right_bottom_point_s = st_boundary.bottom_right_point().s();
const auto lowest_s = std::min(left_bottom_point_s, right_bottom_point_s);

获取边界区域的左下的纵向距离,和边界区域右下的纵向距离,获取两者中的最小lowest_s

if (left_bottom_point_s - right_bottom_point_s > kEpsilon) {
      if (min_s_reverse > lowest_s) {
        min_s_reverse = lowest_s;
      }
    }

fd5f6b42f5624e13910ca9f0c4b448a7.png

获取所有障碍物中反向边界下边界的最小s值

else if (min_s_non_reverse > lowest_s) {
      min_s_non_reverse = lowest_s;
    }

获取所有障碍物中正向边界下边界的最小s值

6d22f23ba6b04705a09dc6582903fa33-zkRx.png

in_s_reverse = std::max(min_s_reverse, 0.0);
min_s_non_reverse = std::max(min_s_non_reverse, 0.0);
return min_s_non_reverse > min_s_reverse ? 0.0 : min_s_non_reverse;
SpeedLimitDecider speed_limit_decider(config_, reference_line, path_data);
SpeedLimit speed_limit;
  if (!speed_limit_decider
      .GetSpeedLimits(path_decision->obstacles(), &speed_limit)
      .ok())

获取速度限制

Status SpeedLimitDecider::GetSpeedLimits(
    const IndexedList<std::string, Obstacle>& obstacles,
    SpeedLimit* const speed_limit_data) const {
  CHECK_NOTNULL(speed_limit_data);

  const auto& discretized_path = path_data_.discretized_path();
  const auto& frenet_path = path_data_.frenet_frame_path();

discretized_path:是路径规划结果的笛卡尔坐标系表示
frenet_path:是路径规划结果的frenet坐标系表示

for (uint32_t i = 0; i < discretized_path.size(); ++i) {
    const double path_s = discretized_path.at(i).s();
    const double reference_line_s = frenet_path.at(i).s();

遍历路径点

double speed_limit_from_reference_line =
        reference_line_.GetSpeedLimitFromS(reference_line_s);

根据当前位置,在参考线上的纵向距离获取速度限制

double ReferenceLine::GetSpeedLimitFromS(const double s) const {
  for (const auto& speed_limit : speed_limit_) {
    if (s >= speed_limit.start_s && s <= speed_limit.end_s) {
      return speed_limit.speed_limit;
    }
  }

遍历参考线上的所有速度限制,根据当前的s值,如果在某个限速范围内,就返回当前的速度限制

const auto& map_path_point = GetReferencePoint(s);

GetReferencePoint函数在planning模块(5)-参考线的平滑 介绍过,可以回头再看一遍
主要是

  • 根据 s 坐标获取参考线上的参考点
  • 这个点包含了 车道信息(lane_waypoints)
  • 用于查询车道的默认限速
  double speed_limit = FLAGS_planning_upper_speed_limit;
  bool speed_limit_found = false;
  for (const auto& lane_waypoint : map_path_point.lane_waypoints()) {
    if (lane_waypoint.lane == nullptr) {
      AWARN << "lane_waypoint.lane is nullptr.";
      continue;
    }
    speed_limit_found = true;
    speed_limit =
        std::fmin(lane_waypoint.lane->lane().speed_limit(), speed_limit);
  }

从参考点关联的所有车道中,找出最小的限速值

FLAGS_planning_upper_speed_limit:规划允许的最大速度,默认是20m/s,配置在下面位置

fbabd050bfe245ad819909843a4809e4.png

  if (!speed_limit_found) {
    // use default speed limit based on road_type
    speed_limit = FLAGS_default_city_road_speed_limit;
    hdmap::Road::Type road_type = GetRoadType(s);
    if (road_type == hdmap::Road::HIGHWAY) {
      speed_limit = FLAGS_default_highway_speed_limit;
    }
  }

如果在前面的循环中没有找到有效的车道速度限制信息,将限速设置为城市道路速度限制
然后从地图数据中获取一下道路类型,如果是高速类型使用FLAGS_default_highway_speed_limit限速配置
两个配置位于下面,单位都是m/s

0967aa4bfdef402ba27d70261f4c3494.png

const double speed_limit_from_centripetal_acc =
 std::sqrt(speed_bounds_config_.max_centric_acceleration_limit() /
           std::fmax(std::fabs(discretized_path.at(i).kappa()),
                     speed_bounds_config_.minimal_kappa()));

根据路径曲率计算向心加速度限制下的最大速度
向心加速度公式:​a = \frac{v^{2}}{r}

第一层: std::fabs(discretized_path.at(i).kappa())

  • 取路径点 i 的 曲率绝对值
  • kappa (曲率)可以是正数(左转)或负数(右转)
  • 向心加速度只关心曲率的 大小 ,不关心方向
    第二层: std::fmax(..., speed_bounds_config_.minimal_kappa())
  • 取曲率和 最小曲率阈值 的较大值
  • 目的 :防止除以零或极小值

曲率:​kappa = \frac{1}{r}
​r = \frac{1}{kappa}
​v =\sqrt{ a \times r} = ​\sqrt{\frac{a}{kappa}}

下面逻辑是根据附近障碍物的横向避让决策来计算速度限制
当车辆接近障碍物时,计算相应的速度限制
核心思想是:当车辆接近障碍物时,应该降低速度以确保安全

double speed_limit_from_nearby_obstacles =
        std::numeric_limits<double>::max();

接近障碍物时速度限制speed_limit_from_nearby_obstacles,先初始为一个最大值

const double collision_safety_range =
        speed_bounds_config_.collision_safety_range();

距离障碍物的安全距离,默认是1m

for (const auto* ptr_obstacle : obstacles.Items()) {
      if (ptr_obstacle->IsVirtual()) {
        continue;
      }
      if (!ptr_obstacle->LateralDecision().has_nudge()) {
        continue;
      }

遍历所有的障碍物,跳过虚拟障碍物和不需要nudge的障碍物

const double vehicle_front_s =
          reference_line_s + vehicle_param_.front_edge_to_center();
      const double vehicle_back_s =
          reference_line_s - vehicle_param_.back_edge_to_center();

vehicle_front_s:自车最前边缘的纵向距离
vehicle_back_s:自车最后边缘的纵向距离

const double obstacle_front_s =
          ptr_obstacle->PerceptionSLBoundary().end_s();
      const double obstacle_back_s =
          ptr_obstacle->PerceptionSLBoundary().start_s();

obstacle_front_s:障碍物SL矩形框最前边缘纵向距离
obstacle_back_s:障碍物SL矩形框最后边缘纵向距离

if (vehicle_front_s < obstacle_back_s ||
          vehicle_back_s > obstacle_front_s) {
        continue;
      }

如果自车的最前边缘纵向距离小于障碍物的最后边缘纵向距离或者,自车的最后边缘纵向距离大于障碍物的最前边缘纵向距离,则跳过

bool is_close_on_left =
    (nudge_decision.type() == ObjectNudge::LEFT_NUDGE) &&
    (frenet_point_l - vehicle_param_.right_edge_to_center() -
         collision_safety_range <
     ptr_obstacle->PerceptionSLBoundary().end_l());

当决策类型是从障碍物左侧绕行时
frenet_point_l:绿色轨迹的横向距离(左正右负)
vehicle_param_.right_edge_to_center():是车宽的一半
frenet_point_l - vehicle_param_.right_edge_to_center():是车辆右边界的横向距离
collision_safety_range:是距离障碍物的缓冲距离
ptr_obstacle->PerceptionSLBoundary().end_l():因为l轴是左正右负,所以end_l是障碍物的左边界的纵向距离
当frenet_point_l - vehicle_param_.right_edge_to_center() -
collision_safety_range < ptr_obstacle->PerceptionSLBoundary().end_l()说明存在碰撞风险

d93fae39402247cfb480b113be6a90fe.png

bool is_close_on_right =
          (nudge_decision.type() == ObjectNudge::RIGHT_NUDGE) &&
          (ptr_obstacle->PerceptionSLBoundary().start_l() -
               collision_safety_range <
           frenet_point_l + vehicle_param_.left_edge_to_center());

当决策类型是从障碍物右侧绕行时
ptr_obstacle->PerceptionSLBoundary().start_l():是障碍物的右边界
collision_safety_range:缓冲距离
frenet_point_l:绿色轨迹的横向距离
vehicle_param_.left_edge_to_center():车宽的一半
frenet_point_l + vehicle_param_.left_edge_to_center():自车左边界距离
ptr_obstacle->PerceptionSLBoundary().start_l() -
collision_safety_range <
frenet_point_l + vehicle_param_.left_edge_to_center():当障碍物右边界扩展一个缓冲距离后的横向距离依旧小于自车的左边界时,说明存在碰撞风险

6c2cb0fecfbe4a4eb73a16a0ca4386d9.png

 if (is_close_on_left || is_close_on_right) {
   double nudge_speed_ratio = 1.0;
   if (ptr_obstacle->IsStatic()) {
     nudge_speed_ratio =
         speed_bounds_config_.static_obs_nudge_speed_ratio();
   } else {
     nudge_speed_ratio =
         speed_bounds_config_.dynamic_obs_nudge_speed_ratio();
   }
   speed_limit_from_nearby_obstacles =
       nudge_speed_ratio * speed_limit_from_reference_line;
   break;
 }

当近距离绕过障碍物时计算减速后的速度上限
如果发现旁边有障碍物距离太近:

看障碍物类型
├─ 静态障碍物 → nudge_speed_ratio:0.6
└─ 动态障碍物 → nudge_speed_ratio:0.8

计算打折后的速度上限

停止检查其他障碍物

if (speed_bounds_config_.enable_nudge_slowdown()) {
  curr_speed_limit =
      std::fmax(speed_bounds_config_.lowest_speed(),
                std::min({speed_limit_from_reference_line,
                          speed_limit_from_centripetal_acc,
                          speed_limit_from_nearby_obstacles}));
}

启用 nudge 减速

  • 取三种限速的最小值 :
  • speed_limit_from_reference_line :地图限速
  • speed_limit_from_centripetal_acc :曲率限速
  • speed_limit_from_nearby_obstacles :障碍物限速
  • 与最低速度限制比较 :
  • 使用 std::fmax 确保不低于 lowest_speed
  • 防止速度过低导致车辆停滞

不启用 nudge 减速

else {
curr_speed_limit =
std::fmax(speed_bounds_config_.lowest_speed(),
std::min({speed_limit_from_reference_line,
speed_limit_from_centripetal_acc}));
}
  • 不考虑障碍物限速
  • 只取地图限速和曲率限速的最小值
  • 适用于调试或特殊场景

enable_nudge_slowdown默认是开启状态

speed_limit_data->AppendSpeedLimit(path_s, curr_speed_limit);

向速度限制列表中添加一个新的速度限制点

const double path_data_length = path_data.discretized_path().Length();

获取离散化路径的总长度
这个长度将作为ST图中s轴(纵向距离)的搜索上界

const double total_time_by_conf = config_.total_time();

从配置中获取规划总时间,单位s
这个时间将作为ST图中t轴(时间)的搜索上界

StGraphData *st_graph_data = reference_line_info_->mutable_st_graph_data();

从 reference_line_info_ 获取ST图数据对象的指针
这个对象用于存储 ST 图的所有数据

  auto *debug = reference_line_info_->mutable_debug();
  STGraphDebug *st_graph_debug = debug->mutable_planning_data()->add_st_graph();
  • 创建ST图调试信息对象
  • 用于记录调试数据,方便后续分析和可视化
st_graph_data->LoadData(boundaries, min_s_on_st_boundaries, init_point,
                          speed_limit, reference_line_info->GetCruiseSpeed(),
                          path_data_length, total_time_by_conf, st_graph_debug);
void StGraphData::LoadData(const std::vector<const STBoundary*>& st_boundaries,
                           const double min_s_on_st_boundaries,
                           const apollo::common::TrajectoryPoint& init_point,
                           const SpeedLimit& speed_limit,
                           const double cruise_speed,
                           const double path_data_length,
                           const double total_time_by_conf,
                           planning_internal::STGraphDebug* st_graph_debug) {
  init_ = true;
  st_boundaries_ = st_boundaries;
  min_s_on_st_boundaries_ = min_s_on_st_boundaries;
  init_point_ = init_point;
  speed_limit_ = speed_limit;
  cruise_speed_ = cruise_speed;
  path_data_length_ = path_data_length;
  total_time_by_conf_ = total_time_by_conf;
  st_graph_debug_ = st_graph_debug;
}

st_boundaries:所有障碍物的 ST 边界集合, 定义ST图中的 不可行区域(障碍物占据的时空区域)
min_s_on_st_boundaries:当正常速度规划失败时,提供一个保守的安全停车位置
init_point:当前帧的规划起点
speed_limit:当前路径点位置的速度限制
reference_line_info->GetCruiseSpeed():巡航速度
path_data_length:离散化路径的总长度,ST图中s轴(纵向距离)的搜索上界
total_time_by_conf:规划总时间,作为ST图中t轴(时间)的搜索上界
st_graph_debug:调试信息对象指针
到这SPEED_BOUNDS_PRIORI_DECIDER task就介绍完了.