天天看点

Baidu Apollo代码解析之s-t图的创建与采样(path_time_graph)

百度Apollo中使用Frenet坐标系下的s-t图来帮助分析障碍物的状态(静态、动态)对于自车轨迹规划的影响。典型的如Lattice Planner中,在s-t图中绘制出障碍物占据的s-t区间范围,便可以在范围外采样无碰撞的轨迹点(end condition),然后拟合和评估轨迹,选取最优的输出。

在s-t图中,静态障碍物是矩形块表示,因为直观的,随着时间推移,其s轴位置始终不变;动态障碍物是四边形表示(如果是匀速运动,则是平行四边形),因为随着时间推移,障碍物的s轴位置不断朝着s轴正方向移动,且斜率就是移动的速度。

当感知模块输出检测到的障碍物、预测模块输出预测的n秒内的障碍物可能轨迹后,应该如何创建s-t图呢?path_time_graph类就是解决这个问题的。文件路径:apollo\modules\planning\lattice\behavior\path_time_graph.cc。原理比较简单,就直接贴代码和注释了。

PathTimeGraph::PathTimeGraph(...) {
  ...
  //在构造函数中,给S-T graph添加所有的动静态障碍物
  SetupObstacles(obstacles, discretized_ref_points);
}
           
void PathTimeGraph::SetupObstacles(
    const std::vector<const Obstacle*>& obstacles,
    const std::vector<PathPoint>& discretized_ref_points) {
  for (const Obstacle* obstacle : obstacles) {
    if (obstacle->IsVirtual()) {
      continue;
    }
    if (!obstacle->HasTrajectory()) {
      //plot a rectangle on s-t graph, representing the static obstacle
      SetStaticObstacle(obstacle, discretized_ref_points);
    } else {
      //plot a parallelogram on s-t graph, representing a dynamic obstacle
      SetDynamicObstacle(obstacle, discretized_ref_points);
    }
  }

  //对静态障碍物的占据范围,按s坐标升序排列
  std::sort(static_obs_sl_boundaries_.begin(), static_obs_sl_boundaries_.end(),
            [](const SLBoundary& sl0, const SLBoundary& sl1) {
              return sl0.start_s() < sl1.start_s();
            });

  for (auto& path_time_obstacle : path_time_obstacle_map_) {
    path_time_obstacles_.push_back(path_time_obstacle.second);
  }
}
           
/**
 * @brief plot a rectangle on s-t graph, representing the static obstacle
 * @param obstacle
 * @param discretized_ref_points, used to transform the obstacle's polygon(in x-y) to frenet
 */
void PathTimeGraph::SetStaticObstacle(
    const Obstacle* obstacle,
    const std::vector<PathPoint>& discretized_ref_points) {
  const Polygon2d& polygon = obstacle->PerceptionPolygon();

  std::string obstacle_id = obstacle->Id();
  //get the start and end of s&l direction, polygon is in x-y, 
  //so reference line is used to transform the polygon to frenet
  SLBoundary sl_boundary =
      ComputeObstacleBoundary(polygon.GetAllVertices(), discretized_ref_points);

  ...
  //check if the obstacle is in lane, if not in lane, ignore it
  ...

  //plot a static obstacle's occupancy graph on s-t graph, actually a rectangle,
  //so it's ok if the 4 corners are confirmed
  path_time_obstacle_map_[obstacle_id].set_id(obstacle_id);
  path_time_obstacle_map_[obstacle_id].set_bottom_left_point(
      SetPathTimePoint(obstacle_id, sl_boundary.start_s(), 0.0));
  path_time_obstacle_map_[obstacle_id].set_bottom_right_point(SetPathTimePoint(
      obstacle_id, sl_boundary.start_s(), FLAGS_trajectory_time_length));
  path_time_obstacle_map_[obstacle_id].set_upper_left_point(
      SetPathTimePoint(obstacle_id, sl_boundary.end_s(), 0.0));
  path_time_obstacle_map_[obstacle_id].set_upper_right_point(SetPathTimePoint(
      obstacle_id, sl_boundary.end_s(), FLAGS_trajectory_time_length));
  static_obs_sl_boundaries_.push_back(std::move(sl_boundary));
}
           
/**
 * @brief Compute obstacle's start and end coordinate in both s and l direction
 * @param vertices
 * @param discretized_ref_points
 * @return boundary with start and end coordinate in both s and l direction,
 *         without boundary points
 */
SLBoundary PathTimeGraph::ComputeObstacleBoundary(
    const std::vector<common::math::Vec2d>& vertices,
    const std::vector<PathPoint>& discretized_ref_points) const {
  double start_s(std::numeric_limits<double>::max());
  double end_s(std::numeric_limits<double>::lowest());
  double start_l(std::numeric_limits<double>::max());
  double end_l(std::numeric_limits<double>::lowest());

  for (const auto& point : vertices) {
    auto sl_point = PathMatcher::GetPathFrenetCoordinate(discretized_ref_points,
                                                         point.x(), point.y());
    start_s = std::fmin(start_s, sl_point.first);
    end_s = std::fmax(end_s, sl_point.first);
    start_l = std::fmin(start_l, sl_point.second);
    end_l = std::fmax(end_l, sl_point.second);
  }

  SLBoundary sl_boundary;
  sl_boundary.set_start_s(start_s);
  sl_boundary.set_end_s(end_s);
  sl_boundary.set_start_l(start_l);
  sl_boundary.set_end_l(end_l);

  return sl_boundary;
}
           
/**
 * @brief plot a parallelogram on s-t graph, representing a dynamic obstacle
 * @param obstacle
 * @param discretized_ref_points
 */
void PathTimeGraph::SetDynamicObstacle(
    const Obstacle* obstacle,
    const std::vector<PathPoint>& discretized_ref_points) {
  double relative_time = time_range_.first;
  while (relative_time < time_range_.second) {
    TrajectoryPoint point = obstacle->GetPointAtTime(relative_time);
    Box2d box = obstacle->GetBoundingBox(point);
    SLBoundary sl_boundary =
        ComputeObstacleBoundary(box.GetAllCorners(), discretized_ref_points);

    ...
    // The obstacle is not shown on the region to be considered.
    // the obstacle is not in lane, out of the s-t graph region
    if (sl_boundary.start_s() > path_range_.second ||
        sl_boundary.end_s() < path_range_.first ||
        sl_boundary.start_l() > left_width ||
        sl_boundary.end_l() < -right_width) {
      //if the obstacle was in the obstacle_map_ before, now it disappear or go to another lane,
      //break and return, so plotting parallelogram is done 
      if (path_time_obstacle_map_.find(obstacle->Id()) !=
          path_time_obstacle_map_.end()) {
        break;
      }
      //if the obstacle has never been observed in the obstacle_map_ before,
      //time added to see it would appear or not later
      relative_time += FLAGS_trajectory_time_resolution;
      continue;
    }

    //if the obstacle is the first time to be observed, add it to the obstacle_map_,
    //this executes only once
    if (path_time_obstacle_map_.find(obstacle->Id()) ==
        path_time_obstacle_map_.end()) {
      path_time_obstacle_map_[obstacle->Id()].set_id(obstacle->Id());

      path_time_obstacle_map_[obstacle->Id()].set_bottom_left_point(
          SetPathTimePoint(obstacle->Id(), sl_boundary.start_s(),
                           relative_time));
      path_time_obstacle_map_[obstacle->Id()].set_upper_left_point(
          SetPathTimePoint(obstacle->Id(), sl_boundary.end_s(), relative_time));
    }

    //plot a parallelogram on s-t graph, representing a dynamic obstacle
    path_time_obstacle_map_[obstacle->Id()].set_bottom_right_point(
        SetPathTimePoint(obstacle->Id(), sl_boundary.start_s(), relative_time));
    path_time_obstacle_map_[obstacle->Id()].set_upper_right_point(
        SetPathTimePoint(obstacle->Id(), sl_boundary.end_s(), relative_time));
    relative_time += FLAGS_trajectory_time_resolution;
  }
}
           
//获取所有时刻、所有障碍物的占据范围(即外层vector)
//内层vector是某个时刻、所有障碍物的占据范围
std::vector<std::vector<std::pair<double, double>>>
PathTimeGraph::GetPathBlockingIntervals(const double t_start,
                                        const double t_end,
                                        const double t_resolution) {
  std::vector<std::vector<std::pair<double, double>>> intervals;
  for (double t = t_start; t <= t_end; t += t_resolution) {
    intervals.push_back(GetPathBlockingIntervals(t));
  }
  return intervals;
}
           
/**
 * @brief Given time t, check obstacles' occupied s region
 * @param t
 * @return
 */
std::vector<std::pair<double, double>> PathTimeGraph::GetPathBlockingIntervals(
    const double t) const {
  CHECK(time_range_.first <= t && t <= time_range_.second);
  std::vector<std::pair<double, double>> intervals;
  for (const auto& pt_obstacle : path_time_obstacles_) {
    if (t > pt_obstacle.max_t() || t < pt_obstacle.min_t()) {
      continue;
    }
    //lerp() executing a linear interpolation, reveals that the constant s velocity is assumed
    double s_upper = lerp(pt_obstacle.upper_left_point().s(),
                          pt_obstacle.upper_left_point().t(),
                          pt_obstacle.upper_right_point().s(),
                          pt_obstacle.upper_right_point().t(), t);

    double s_lower = lerp(pt_obstacle.bottom_left_point().s(),
                          pt_obstacle.bottom_left_point().t(),
                          pt_obstacle.bottom_right_point().s(),
                          pt_obstacle.bottom_right_point().t(), t);

    intervals.emplace_back(s_lower, s_upper);
  }
  return intervals;
}
           
/**
 * @brief Sampling expanded boundary points around obstacle, below or above the obstacle
 * @param obstacle_id
 * @param s_dist a very small value, keep the sample points close to obstacle but not overlapped,
 *        also control sampling below or above the obstacle
 * @param t_min_density time sample interval
 * @return sample points upper or lower than obstacle
 */
std::vector<STPoint> PathTimeGraph::GetObstacleSurroundingPoints(
    const std::string& obstacle_id, const double s_dist,
    const double t_min_density) const {
  CHECK(t_min_density > 0.0);
  std::vector<STPoint> pt_pairs;
  //if the obstacle never show up in obstacle_map_, then return null
  if (path_time_obstacle_map_.find(obstacle_id) ==
      path_time_obstacle_map_.end()) {
    return pt_pairs;
  }

  const auto& pt_obstacle = path_time_obstacle_map_.at(obstacle_id);
  ...
  if (s_dist > 0.0) {
    //sampling above the obstacle
    s0 = pt_obstacle.upper_left_point().s();
    s1 = pt_obstacle.upper_right_point().s();
    t0 = pt_obstacle.upper_left_point().t();
    t1 = pt_obstacle.upper_right_point().t();
  } else {
    //sampling below the obstacle
    s0 = pt_obstacle.bottom_left_point().s();
    s1 = pt_obstacle.bottom_right_point().s();
    t0 = pt_obstacle.bottom_left_point().t();
    t1 = pt_obstacle.bottom_right_point().t();
  }
  ...
  for (size_t i = 0; i <= num_sections; ++i) {
    double t = t_interval * static_cast<double>(i) + t0;
    // add s_dist to ensure the sample point don't locate in the obstacle
    double s = lerp(s0, t0, s1, t1, t) + s_dist;
    STPoint ptt;
    ptt.set_t(t);
    ptt.set_s(s);
    pt_pairs.push_back(std::move(ptt));
  }
  return pt_pairs;
}
           
/**
 * @brief get lateral bounds along the static obstacles s range
 * @param s_start static obstacles' start s
 * @param s_end static obstacles' end s
 * @param s_resolution
 * @return
 */
std::vector<std::pair<double, double>> PathTimeGraph::GetLateralBounds(
    const double s_start, const double s_end, const double s_resolution) {
  ...
  std::vector<std::pair<double, double>> bounds;
  std::vector<double> discretized_path;
  double s_range = s_end - s_start;
  double s_curr = s_start;
  size_t num_bound = static_cast<size_t>(s_range / s_resolution);
  ...

  // Initialize bounds by reference line width
  for (size_t i = 0; i < num_bound; ++i) {
    double left_width = FLAGS_default_reference_line_width / 2.0;
    double right_width = FLAGS_default_reference_line_width / 2.0;
    ptr_reference_line_info_->reference_line().GetLaneWidth(s_curr, &left_width,
                                                            &right_width);
    double ego_d_lower = init_d_[0] - ego_width / 2.0;
    double ego_d_upper = init_d_[0] + ego_width / 2.0;
    //bounds = [right bound, left bound]
    bounds.emplace_back(
        std::min(-right_width, ego_d_lower - FLAGS_bound_buffer),
        std::max(left_width, ego_d_upper + FLAGS_bound_buffer));
    discretized_path.push_back(s_curr);
    s_curr += s_resolution;
  }

  for (const SLBoundary& static_sl_boundary : static_obs_sl_boundaries_) {
    UpdateLateralBoundsByObstacle(static_sl_boundary, discretized_path, s_start,
                                  s_end, &bounds);
  }

  for (size_t i = 0; i < bounds.size(); ++i) {
    //take the ego width into consideration, the vehiche shouldn't collide with lane edge
    bounds[i].first += ego_width / 2.0;
    bounds[i].second -= ego_width / 2.0;
    //invalid if right bound > left bound
    if (bounds[i].first >= bounds[i].second) {
      bounds[i].first = 0.0;
      bounds[i].second = 0.0;
    }
  }
  return bounds;
}
           
/**
 * @brief adjust lateral bounds based on obstacles' relative position to lane center line
 * @param sl_boundary static obstacles' boundary(start and end of s&l)
 * @param discretized_path path's s coordinate
 * @param s_start
 * @param s_end
 * @param bounds lateral bounds, initialized by lane width(roughly)
 */
void PathTimeGraph::UpdateLateralBoundsByObstacle(
    const SLBoundary& sl_boundary, const std::vector<double>& discretized_path,
    const double s_start, const double s_end,
    std::vector<std::pair<double, double>>* const bounds) {
  if (sl_boundary.start_s() > s_end || sl_boundary.end_s() < s_start) {
    return;
  }
  auto start_iter = std::lower_bound(
      discretized_path.begin(), discretized_path.end(), sl_boundary.start_s());
  auto end_iter = std::upper_bound(
      discretized_path.begin(), discretized_path.end(), sl_boundary.start_s());
  size_t start_index = start_iter - discretized_path.begin();
  size_t end_index = end_iter - discretized_path.begin();
  //obstacle locate at the center of the lane(reference line)
  if (sl_boundary.end_l() > -FLAGS_lattice_epsilon &&
      sl_boundary.start_l() < FLAGS_lattice_epsilon) {
    for (size_t i = start_index; i < end_index; ++i) {
      bounds->operator[](i).first = -FLAGS_lattice_epsilon;
      bounds->operator[](i).second = FLAGS_lattice_epsilon;
    }
    return;
  }
  //obstacle locate at the right(negative) of the lane(reference line)
  if (sl_boundary.end_l() < FLAGS_lattice_epsilon) {
    for (size_t i = start_index; i < std::min(end_index + 1, bounds->size());
         ++i) {
      //adjust right bound to be more left, left nudge, but in the lane, don't change lane
      bounds->operator[](i).first =
          std::max(bounds->operator[](i).first,
                   sl_boundary.end_l() + FLAGS_nudge_buffer);
    }
    return;
  }
  //obstacle locate at the left(positive) of the lane(reference line)
  if (sl_boundary.start_l() > -FLAGS_lattice_epsilon) {
    for (size_t i = start_index; i < std::min(end_index + 1, bounds->size());
         ++i) {
      //adjust left bound to be more right, right nudge, but in the lane, don't change lane
      bounds->operator[](i).second =
          std::min(bounds->operator[](i).second,
                   sl_boundary.start_l() - FLAGS_nudge_buffer);
    }
    return;
  }
}
           

继续阅读