天天看點

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;
  }
}
           

繼續閱讀