```cpp
auto indexed_obstacles = reference_line_info_->path_decision()->obstacles();

indexed_obstacles就是在planning模块-路径边界 障碍物 介绍的.通过

Obstacle* ReferenceLineInfo::AddObstacle(const Obstacle* obstacle) {
...
auto* mutable_obstacle = path_decision_.AddObstacle(*obstacle);

添加的障碍物id和障碍物信息.

  for (const auto* obs : indexed_obstacles.Items()) {
    const auto& sl_bound = obs->PerceptionSLBoundary();
    for (int i = 0; i < sl_bound.boundary_point_size(); i++) {
      std::string name = obs->Id() + "_obs_sl_boundary";
      print_curve.AddPoint(name, sl_bound.boundary_point(i).s(),
                           sl_bound.boundary_point(i).l());
    }
  }

obs->PerceptionSLBoundary()也是在planning模块-路径边界 障碍物 介绍的ReferenceLine::GetSLBoundary函数

sl_bound:获取的是障碍物的AABB矩形框(红框),frenet坐标.

54fa86f067644f079ca4637494093081.png

sl_bound.boundary_point_size():是单个障碍物的边界点如(p0, p1, p2, p3, mid1)

GetSLPolygons函数

for (const auto* obstacle : obstacles.Items()) {
    if (!IsWithinPathDeciderScopeObstacle(*obstacle)) {
      continue;
    }

过滤掉虚拟障碍物、动态障碍物以及被忽略的障碍物

if (obstacle->PerceptionSLBoundary().end_s() < adc_back_edge_s) {
      continue;
    }

过滤掉自车后方的障碍物

const auto obstacle_sl = obstacle->PerceptionSLBoundary();
    polygons->emplace_back(obstacle_sl, obstacle->Id(),
                           obstacle->Perception().type());

将该障碍物的 ​SL的AABB矩形框、ID 和类型(如车辆、行人、未知物)存入 polygons列表

  sort(polygons->begin(), polygons->end(),
       [](const SLPolygon& a, const SLPolygon& b) {
         return a.MinS() < b.MinS();
       });

可以理解为按照障碍物纵向位置的最小值从近到远排序
但其实MinS()是每个障碍物bounddary_point的s最小的点的纵向位置

GetBoundaryFromStaticObstacles函数

  if (!PathBoundsDeciderUtil::GetBoundaryFromStaticObstacles(
          *reference_line_info_, &obs_sl_polygons, init_sl_state_, &path_bound,
          &blocking_obstacle_id, &path_narrowest_width)) {
    const std::string msg =
        "Failed to decide fine tune the boundaries after "
        "taking into consideration all static obstacles.";
    AERROR << msg;
    return false;
  }

UpdatePathBoundaryBySLPolygon函数

  if (reference_line_info.IsChangeLanePath() ||
      path_boundary->label().find("regular/left") != std::string::npos ||
      path_boundary->label().find("regular/right") != std::string::npos) {
    center_l.push_back(
        (path_boundary->front().l_upper.l + path_boundary->front().l_lower.l) *
        0.5);
    max_nudge_check_distance = FLAGS_max_nudge_check_distance_in_lk;
  } else {
    center_l.push_back(0.0);
    max_nudge_check_distance = FLAGS_max_nudge_check_distance_in_lc;
  }

根据当前车辆是在“车道保持”还是“换道/绕行”状态,来确定路径规划的“中心参考基准”center_l和“避让检查的纵向距离阈值”max_nudge_check_distance.
lk:表示lane_keep车道保持
lc:表示lane_change换道

path_boundary:是在planning模块-路径边界 障碍物 通过GetBoundaryFromSelfLane函数获取到的,主要包含的是平滑后参考线两侧的可通行空间的路径边界信息.

7b232f30d12d498f88b879fe0562cfa5.png

*narrowest_width =
      path_boundary->front().l_upper.l - path_boundary->front().l_lower.l;
  double mid_l =
      (path_boundary->front().l_upper.l + path_boundary->front().l_lower.l) / 2;

计算车辆当前位置(即规划起始点)的“可用车道宽度”及其“中心位置横向距离”
path_boundary->front().l_upper.l:上图left_bound
path_boundary->front().l_lower.l:上图right_bound
注意:frenet坐标系l轴是左正右负

size_t nudge_check_count =
      size_t(max_nudge_check_distance / FLAGS_path_bounds_decider_resolution);
  double last_max_nudge_l = center_l.front();

确定在纵向(s轴)上需要检查多少个点(4/0.5=8),以判断是否存在障碍物阻挡,并初始化横向偏移(l轴)的起始基准.

auto begin_it =
        center_l.end() - std::min(nudge_check_count, center_l.size());
    last_max_nudge_l = *std::max_element(
        begin_it, center_l.end(),
        [](double a, double b) { return std::fabs(a) < std::fabs(b); });

每次检查center_l中的nudge_check_count个点,并获取nudge_check_count个点中的center_l绝对值最大值存入last_max_nudge_l.

 for (size_t j = 0; j < sl_polygon->size(); j++) {
      if (sl_polygon->at(j).NudgeInfo() == SLPolygon::IGNORE) {
        AINFO << "UpdatePathBoundaryBySLPolygon, ignore obs: "
              << sl_polygon->at(j).id();
        continue;
      }

遍历所有障碍物的sl_polygon,并检查当前障碍物的避让决策(NudgeInfo).如果已经被标记为IGNORE,则直接跳过.

double min_s = sl_polygon->at(j).MinS();
      double max_s =
          sl_polygon->at(j).MaxS() + FLAGS_obstacle_lon_end_buffer_park;

min_s / max_s:可以理解为障碍物在 ​S 轴上的起点和终点
FLAGS_obstacle_lon_end_buffer_park:这是一个纵向安全 Buffer.在障碍物的最远s值后面额外延长一段距离,确保车辆绕行后有足够的空间切回原车道,不至于过早转向导致车尾剐蹭.

if (max_s - min_s < FLAGS_path_bounds_decider_resolution) {
        max_s += FLAGS_path_bounds_decider_resolution;
        min_s -= FLAGS_path_bounds_decider_resolution;
      }

如果障碍物在 ​S 方向非常短(比如一个小路灯杆或交通锥),甚至小于路径规划的分辨率.
放大它的 ​S 范围.为了防止采样遗漏.如果障碍物太小,正好落在两个采样点之间,规划器会“看不见”它.

if (max_s < path_boundary_s) {
        continue;
      }

如果障碍物的最远端已经在当前 path_boundary_s 后面位置了.说明这个障碍物已经“过时”了,继续检查 sl_polygon 列表中的下一个

  if (min_s > path_boundary_s) {
        break;
      }

如果当前这个障碍物的起点 min_s 已经超过了当前path_boundary_s,sl_polygon是按 MinS()从小到大排序的,如果当前sl_polygon的min_s超过了path_boundary_s,那后面的所有sl_polygon的min_s也都会超过path_boundary_s,所以break.

GetBufferBetweenADCCenterAndEdge函数

double PathBoundsDeciderUtil::GetBufferBetweenADCCenterAndEdge() {
  double adc_half_width =
      VehicleConfigHelper::GetConfig().vehicle_param().width() / 2.0;

  return (adc_half_width + FLAGS_obstacle_lat_buffer);
}

自车宽度的一半加上障碍物横向缓冲距离(0.4m)

UpdatePassableInfo函数

void SLPolygon::UpdatePassableInfo(double left_bound, double right_bound,
                                   double buffer) {
  if (!is_passable_[0] && !is_passable_[1]) {
    return;
  }
  is_passable_[0] = left_bound > max_l_point_.l() + buffer;
  is_passable_[1] = right_bound < min_l_point_.l() - buffer;
}

判断障碍物的左侧和右侧是否还存在足够的空间让车辆通过
判断车道左边界是否大于障碍物的最左端点max_l_point_.l()加上安全Buffer
判断车道右边界是否小于障碍物的最右端点min_l_point_.l()减去安全 Buffer

GetRightBoundaryByS/GetLeftBoundaryByS

GetInterpolatedSFromBoundary函数

给定一个纵向距离 ​s,在障碍物的 ​SL 边界点集合中,精确计算出该 ​s 对应位置的横向 ​L

double ret = last_iter->l() + (s - last_iter->s()) *
                                    (iter->l() - last_iter->l()) /
                                    (iter->s() - last_iter->s());

线性插值计算

85cc3794e5234acca1cbd43ba57b8cad.png

根据 ​s 在这段区间内的比例,推算出精确的 ​L

ce11f936f6fc45b395d743d18ee7a203.png

double l_lower = sl_polygon->at(j).GetRightBoundaryByS(path_boundary_s);
      double l_upper = sl_polygon->at(j).GetLeftBoundaryByS(path_boundary_s);

获取当前给定path_boundary_s位置的障碍物左右边界[l_upper, l_lower],这里的左右边界是把障碍物看成一个点插值出来的.

PathBoundPoint obs_left_nudge_bound(
          path_boundary_s, l_upper + adc_obs_edge_buffer, left_bound.l);

PathBoundPoint(double s_init, double l_min, double l_max) {
    s = s_init;
    l_lower.l = l_min;
    l_upper.l = l_max;
  }

path_boundary_s:当前位置
l_upper + adc_obs_edge_buffer:障碍物左边界加上自车半宽加上横向缓冲区间
left_bound.l:自车左边界
为车辆划定了一个向左绕行的横向通行的窗口
左边界:由路径边界决定(left_bound.l)
右边界:由障碍物位置决定(l_upper + buffer)

obs_left_nudge_bound.towing_l = path_boundary->at(i).towing_l;

上面没看到有赋值的地方,先默认为0

PathBoundPoint obs_right_nudge_bound(path_boundary_s, right_bound.l,
                                           l_lower - adc_obs_edge_buffer);

为车辆划定了一个向右绕行的横向通行的窗口
左边界:由障碍物位置决定(l_lower - adc_obs_edge_buffer)
右边界:由路径边界决定(right_bound.l)

obs_right_nudge_bound.towing_l = path_boundary->at(i).towing_l;

上面没看到有赋值的地方,先默认为0

枚举项 含义 场景示例
LEFT_NUDGE 向左微调 / 向左避让 障碍物在道路右侧(如右侧停靠车辆、路边施工锥),或者需要从左侧超越前方慢车,车辆向左小幅偏移以保持安全距离。
RIGHT_NUDGE 向右微调 / 向右避让 障碍物在道路左侧(如左侧占道行人、自行车),车辆向右偏移以保持安全距离。
UNDEFINED 未定义 / 未知 初始规划阶段,尚未对障碍物完成分析,或者避让方向尚未计算完成时的默认状态。
BLOCKED 被阻挡 左右两侧都无法安全避让(例如道路过窄、两侧都有障碍物),车辆只能减速直至停车,等待障碍物清除或重新规划。
IGNORE 忽略 障碍物距离较远、在安全范围之外,或不在当前行驶路径上(如旁路静态物体),对行驶无影响,无需采取避让动作。
if (sl_polygon->at(j).NudgeInfo() == SLPolygon::UNDEFINED)

初始状态SLPolygon::UNDEFINED

double obs_l = (l_lower + l_upper) / 2;

obs_l
障碍物在 Frenet 坐标系中的横向中心
l_lower / l_upper 是障碍物在 l 方向的范围
取中点代表障碍物“整体在左还是右”
sl_polygon->at(j).is_passable()[RIGHT_INDEX]
障碍物右侧可通行
sl_polygon->at(j).is_passable()[LEFT_INDEX]
障碍物左侧可通行

mid_l
当前路径边界的中点 l
init_sl_state.second[0]
规划起点的横向位置l

if (fabs(obs_l - mid_l) < 0.4 &&
    fabs(path_boundary_s - init_sl_state.first[0]) < 5.0)

障碍物在当前路径边界的中点±0.4m
last_max_nudge_l
获取nudge_check_count个点中的center_l绝对值最大值

 if (init_sl_state.second[0] < obs_l) {
   sl_polygon->at(j).SetNudgeInfo(SLPolygon::RIGHT_NUDGE);
   AINFO << sl_polygon->at(j).id()
         << " right nudge with init_sl_state";
 } else {
   sl_polygon->at(j).SetNudgeInfo(SLPolygon::LEFT_NUDGE);
   AINFO << sl_polygon->at(j).id()
         << " left nudge width init_sl_state";
 }

如果自车在障碍物的右侧,则标记为向右绕行,如果自车在障碍物的左侧,则标记为向左绕行.

不符合条件
if (std::fabs(obs_l - mid_l) < 0.4 &&
std::fabs(path_boundary_s - init_sl_state.first[0]) < 5.0)时

else {
 if (last_max_nudge_l < obs_l) {
   sl_polygon->at(j).SetNudgeInfo(SLPolygon::RIGHT_NUDGE);
   AINFO << sl_polygon->at(j).id()
         << " right nudge, according max_nudge_l";
 } else {
   sl_polygon->at(j).SetNudgeInfo(SLPolygon::LEFT_NUDGE);
   AINFO << sl_polygon->at(j).id()
         << " left nudge, according max_nudge_l";
 }
}

(例如障碍物还比较远,或者不在路中心),程序就会执行这个 else.
此时,它不再看车辆现在在哪,而是看 last_max_nudge_l,它代表了车辆之前的“意图”或“趋势”.
向右绕行 (Right Nudge):如果上一帧的最优路径横向位移(last_max_nudge_l)比障碍物的中心线(obs_l)更靠右,那么这一帧继续维持向右绕行.
向左绕行 (Left Nudge):反之,如果之前的路径已经在左侧了,则继续维持向左绕行.

else {
     sl_polygon->at(j).SetNudgeInfo(SLPolygon::RIGHT_NUDGE);
     AINFO << sl_polygon->at(j).id()
           << " right nudge, left is not passable";
   }

当左侧不可通行时,就只能标记向右绕行

else {
          sl_polygon->at(j).SetNudgeInfo(SLPolygon::LEFT_NUDGE);
          AINFO << sl_polygon->at(j).id()
                << " left nudge, right is not passable";
        }

当右侧不可通行时,就只能标记向左绕行

if (sl_polygon->at(j).NudgeInfo() == SLPolygon::RIGHT_NUDGE) {
        // right nudge
        if (obs_right_nudge_bound.l_upper.l < path_boundary->at(i).towing_l) {
          sl_polygon->at(j).SetOverlapeWithReferCenter(true);
          sl_polygon->at(j).SetOverlapeSizeWithReference(
              path_boundary->at(i).towing_l - obs_right_nudge_bound.l_upper.l);
        }

obs_right_nudge_bound.l_upper.l:表示障碍物右侧可通行区域的左边界,把障碍物当成一个点插值出来的障碍物右边界l_lower值减去(自车半宽加上缓冲区)就是障碍物实际的右侧可通行区域左边界l值.
path_boundary->at(i).towing_l:是理想的自车要行驶的l位置,这里一直是0,也就是参考线的位置.
因为此时已经决定向右绕行,说明障碍物在自车的左侧,当obs_right_nudge_bound.l_upper.l < path_boundary->at(i).towing_l时,说明障碍物压上了参考线,为什么单凭obs_right_nudge_bound.l_upper.l < path_boundary->at(i).towing_l时就可以说明障碍物压线了,因为前提已经决定向右绕行,如果整个障碍物在参考线的右侧,那自车肯定要向左绕行,如果选择向右绕行说明障碍物并未完全在参考线右侧.说明障碍物的左边缘在参考线左侧,obs_right_nudge_bound.l_upper.l < path_boundary->at(i).towing_l表示障碍物的右边缘在参考线右侧.
将此障碍物标记为Overlap,并计算偏移的横向距离
标记 Overlap 是为了告诉后续的优化器:自车需要往右偏绕过障碍物

 if (!sl_polygon->at(j).is_passable()[RIGHT_INDEX]) {
   // boundary is blocked
   *blocked_id = sl_polygon->at(j).id();
   AINFO << "blocked at " << *blocked_id << ", s: " << path_boundary_s
         << ", left bound: " << left_bound.l
         << ", right bound: " << right_bound.l;
   sl_polygon->at(j).SetNudgeInfo(SLPolygon::BLOCKED);
   break;
 }

通行检查,决策是向右绕行,但这里会做最后的“物理检查”
如果该障碍物的右侧(RIGHT_INDEX)在地图或静态环境里其实无法行驶
直接判定为BLOCKED(路径阻断)这意味着“右绕”方案不可行
通行性是在上面UpdatePassableInfo函数更新的

if (obs_right_nudge_bound.l_upper.l < left_bound.l) {
          AINFO << "update left_bound: s " << path_boundary_s << ", l "
                << left_bound.l << " -> " << obs_right_nudge_bound.l_upper.l;
          left_bound.l = obs_right_nudge_bound.l_upper.l;
          left_bound.type = BoundType::OBSTACLE;
          left_bound.id = sl_polygon->at(j).id();
          *narrowest_width =
              std::min(*narrowest_width, left_bound.l - right_bound.l);
        }

left_bound.l:路径边界的左边界
obs_right_nudge_bound.l_upper.l:障碍物右侧可通行区域的左边界
因为决策是向右绕行,如果obs_right_nudge_bound.l_upper.l < left_bound.l就需要将自车的左边界收缩为障碍物右侧可通行区域的左边界,并将边界类型标记为 OBSTACLE,并记录障碍物id.
left_bound.l - right_bound.l 计算出当前 ​s 点处,左右边界之间的净距离
通过 std::min 不断迭代,找到整条规划路径上最窄的宽度narrowest_width

else if (sl_polygon->at(j).NudgeInfo() == SLPolygon::LEFT_NUDGE) {
 // left nudge
 if (obs_left_nudge_bound.l_lower.l > path_boundary->at(i).towing_l) {
   sl_polygon->at(j).SetOverlapeWithReferCenter(true);
   sl_polygon->at(j).SetOverlapeSizeWithReference(
       obs_left_nudge_bound.l_lower.l - path_boundary->at(i).towing_l);
 }

obs_left_nudge_bound.l_lower.l:是障碍物左侧通行区间的右边界
因为此时已经决定向左绕行,说明障碍物在自车的右侧,当obs_left_nudge_bound.l_lower.l > path_boundary->at(i).towing_l时,说明障碍物压上了参考线.
将此障碍物标记为Overlap,并计算偏移的横向距离
标记 Overlap 是为了告诉后续的优化器:自车需要往左偏绕过障碍物

 if (!sl_polygon->at(j).is_passable()[LEFT_INDEX]) {
          // boundary is blocked
          *blocked_id = sl_polygon->at(j).id();
          AINFO << "blocked at " << *blocked_id << ", s: " << path_boundary_s
                << ", left bound: " << left_bound.l
                << ", right bound: " << right_bound.l;
          sl_polygon->at(j).SetNudgeInfo(SLPolygon::BLOCKED);
          break;
        }

通行检查,决策是向左绕行,但这里会做最后的“物理检查”
如果该障碍物的左侧(LEFT_INDEX)在地图或静态环境里其实无法行驶
直接判定为BLOCKED(路径阻断)这意味着“左绕”方案不可行

if (obs_left_nudge_bound.l_lower.l > right_bound.l) {
          AINFO << "update right_bound: s " << path_boundary_s << ", l "
                << right_bound.l << " -> " << obs_left_nudge_bound.l_lower.l;
          right_bound.l = obs_left_nudge_bound.l_lower.l;
          right_bound.type = BoundType::OBSTACLE;
          right_bound.id = sl_polygon->at(j).id();
          *narrowest_width =
              std::min(*narrowest_width, left_bound.l - right_bound.l);
        }

因为决策是向左绕行,如果obs_left_nudge_bound.l_lower.l > right_bound.l就需要将自车的右边界收缩为障碍物左侧可通行区域的右边界,并将边界类型标记为 OBSTACLE,并记录障碍物id.
left_bound.l - right_bound.l 计算出当前 ​s 点处,左右边界之间的净距离
通过 std::min 不断迭代,找到整条规划路径上最窄的宽度narrowest_width

last_max_nudge_l = std::fabs((left_bound.l + right_bound.l) / 2.0 -
                    mid_l) > std::fabs(last_max_nudge_l - mid_l)
              ? (left_bound.l + right_bound.l) / 2.0
              : last_max_nudge_l;

在当前的纵向位置(​s 点),更新并记录整条路径中“偏离中心线最远”的那个横向位置

    if (!blocked_id->empty()) {
      TrimPathBounds(i, path_boundary);
      *narrowest_width = default_width;
      return false;
    }

如果有无法通行的位置,,就会把这个路径边界点及之后的路径边界点都裁剪掉

center_l.push_back((left_bound.l + right_bound.l) / 2.0);

更新当前路径边界的center_l

AddExtraPathBound函数

RelaxEgoPathBoundary函数

 double min_radius =
     std::max(veh_param.min_turn_radius(),
              std::tan(veh_param.max_steer_angle() / veh_param.steer_ratio()) /
                  veh_param.wheel_base());

max_steer_angle:方向盘的最大转角(弧度)
steer_ratio:转向传动比
veh_param.max_steer_angle() / veh_param.steer_ratio():前轮偏转的最大角度
在阿克曼转向几何中,车辆的曲率​\kappa与前轮偏转角的关系:
​\delta:前轮偏转角
L:轴距
​\kappa = \frac{\tan \delta }{L}
转弯半径
R = ​\frac{1}{\kappa}
这块有一个问题就是代码是​\frac{tan \delta}{L}这是曲率而不是转弯半径,或许最小转弯半径只想使用配置中的veh_param.min_turn_radius()

double init_frenet_kappa =
  init_sl_state.second[2] /
  std::pow(1 + std::pow(init_sl_state.second[1], 2), 1.5);

second[1]是​{l}'
second[2]是​{l}''
曲线曲率公式:
​\kappa=\frac{{f}''(x)}{(1+{f}'(x)^{2})^{\frac{3}{2} }}
它算出的是当前瞬间,车辆相对于参考线的弯曲程度

if (init_frenet_kappa < 0) {
    init_frenet_kappa = std::min(
        -1.0 / (min_radius + FLAGS_relax_ego_radius_buffer), init_frenet_kappa);
  } else {
    init_frenet_kappa = std::max(
        1.0 / (min_radius + FLAGS_relax_ego_radius_buffer), init_frenet_kappa);
  }

右转时(init_frenet_kappa < 0)
-1.0 / (min_radius + FLAGS_relax_ego_radius_buffer):通过配置中的转弯半径计算的曲率
无论是左转还是右转,计算车辆当前真实的转弯程度(曲率),并强制将其“限制”在车辆物理上能达到的最大极限范围内

for (size_t i = 1; i < path_boundary->size(); ++i) {
    auto& left_bound = path_boundary->at(i).l_upper;
    auto& right_bound = path_boundary->at(i).l_lower;
    double delta_s = path_boundary->at(i).s - init_pt.s;
    if (delta_s > FLAGS_relax_path_s_threshold) {
      left_calculate_success = false;
      right_calculate_success = false;
      break;
    }

只对自车前方FLAGS_relax_path_s_threshold(默认5m)范围的路径边界进行调整

    if (left_calculate_success &&
        (left_bound.type == BoundType::OBSTACLE ||
         path_boundary->at(i).is_nudge_bound[LEFT_INDEX])) {
      left_calculate_success = RelaxBoundaryPoint(
          &path_boundary->at(i), true, init_pt_l, init_frenet_heading, delta_s,
          init_frenet_kappa, min_radius);
    }

这段代码判断是否需要对左边界进行松弛:
BoundType::OBSTACLE: 如果左侧边界是由障碍物(如路边停靠的车辆)限制的
is_nudge_bound: 如果该点是因为避让(Nudge)障碍物而生成的边界

RelaxBoundaryPoint函数

bool PathBoundsDeciderUtil::RelaxBoundaryPoint(
    PathBoundPoint* const path_bound_point, bool is_left, double init_l,
    double heading, double delta_s, double init_frenet_kappa,
    double min_radius)

path_bound_point:当前遍历到的路径边界点
is_left:默认是true
init_l:车辆当前的初始横向位置
heading:是自车车头方向与自车在参考线上的投影点切线方向上的夹角

8aa331a0c73a4319a3c7b9558deb53c0.png

delta_s:当前位置边界点与第一个边界点纵向距离
init_frenet_kappa:车辆相对于参考线的曲率
min_radius:最小转弯半径

if (is_left) {
    if (init_frenet_kappa > 0 && heading < 0) {
      is_success = util::left_arc_bound_with_heading_with_reverse_kappa(
          delta_s, min_radius, heading, init_frenet_kappa,
          &protective_restrict);
    } else {
      is_success = util::left_arc_bound_with_heading(delta_s, radius, heading,
                                                     &protective_restrict);
    }

    relax_constraint =
        std::max(path_bound_point->l_upper.l, init_l + protective_restrict);
    AINFO << "init_pt_l: " << init_l
          << ", left_bound: " << path_bound_point->l_upper.l
          << ",  diff s: " << delta_s << ", radius: " << radius
          << ", protective_restrict: " << protective_restrict
          << ", left_obs_constraint: " << relax_constraint;

    if (path_bound_point->is_nudge_bound[LEFT_INDEX]) {
      old_buffer = std::max(FLAGS_obstacle_lat_buffer,
                            FLAGS_static_obstacle_nudge_l_buffer);
    }

    relax_constraint =
        std::min(path_bound_point->l_upper.l + old_buffer - new_buffer,
                 relax_constraint);
    AINFO << "left_obs_constraint: " << relax_constraint;
    path_bound_point->l_upper.l = relax_constraint;
  }

当前车辆正向左转(kappa > 0),但车头已经朝向右侧(heading < 0)

left_arc_bound_with_heading_with_reverse_kappa函数

bool left_arc_bound_with_heading_with_reverse_kappa(double delta_x, double r,
                                                    double heading,
                                                    double kappa,
                                                    double* result) {
  if (heading > 0 || kappa < 0 ||
      delta_x > r * (1.0 - std::sin(heading)) - 1e-6) {
    *result = std::numeric_limits<double>::lowest();
    return false;
  }
  if (delta_x < -r * std::sin(heading)) {
    *result = r * std::cos(heading) -
              std::sqrt(r * r - std::pow(delta_x - r * std::sin(heading), 2));
  } else {
    *result = std::sqrt(r * r - std::pow(delta_x + r * std::sin(heading), 2)) -
              r * (2 - std::cos(heading));
  }
  return true;
}

当车辆最终是左转(kappa > 0),但由于某种原因(比如之前的避障动作)车头已经向右撇(heading < 0)时,计算车身向左偏移的最大极限距离, 车身不可以超过这个最大偏移.
delta_x:当前位置边界点与第一个边界点纵向距离
r:最小转弯半径
heading:是自车车头方向与自车在参考线上的投影点切线方向上的夹角
init_frenet_kappa:车辆相对于参考线的曲率

这个函数主要是用了两个圆来进行自车左边界的限定,当前自车在圆心.当delta_x小于​-rsin\theta时使用红色圆弧作为限定边界,否则使用绿色圆弧作为限定边界.

ccec9d9f4fa547cca7bf9a9762b6d4ff.png

left_arc_bound_with_heading函数

如果此时自车最终是右转或者此时车头朝向是左转,则使用left_arc_bound_with_heading函数获取限定边界

bool left_arc_bound_with_heading(double delta_x, double r, double heading,
                                 double* result) {
  if (delta_x > r * (1.0 + std::sin(heading)) - 1e-6) {
    *result = std::numeric_limits<double>::lowest();
    return false;
  }

  *result = std::sqrt(r * r - std::pow(delta_x - r * std::sin(heading), 2)) -
            r * std::cos(heading);
  return true;
}

e4224ea98e544e408df8d26488781d9c.jpeg

这个函数主要通过上面的圆来限定左边界,如果自车行驶的纵向距离超出了此圆的范围,则返回false,否则通过圆的方程来获取横向最大偏移.

relax_constraint =
        std::max(path_bound_point->l_upper.l, init_l + protective_restrict);

代码通过 std::max 在两个候选边界值中选择更宽松(​L 值更大)的那一个:
候选 A(物理硬约束):path_bound_point->l_upper.l这是基于地图(如路缘、车道线)和感知到的障碍物计算出的理想最大左边界.
候选 B(状态自适应约束):init_l + protective_restrict这是以车辆当前实际位置(init_l)为基准,向左强制预留的一个最小生存空间(protective_restrict 缓冲区).

relax_constraint =
        std::min(path_bound_point->l_upper.l + old_buffer - new_buffer,
                 relax_constraint);
path_bound_point->l_upper.l = relax_constraint;

还原障碍物边缘位置: path_bound_point->l_upper.l 是已经扣除过 old_buffer (0.4m)的边界.那么l_upper.l + old_buffer 就回到了障碍物真实的物理边缘位置.
应用新的缓冲区: 从障碍物边缘再减去更宽的 new_buffer(max(0.25, 0.4)).
最后,获取一个更小空间,作为左边界.
上面是松弛左边界点的逻辑,松弛右边界与其相似就不详细介绍了.到这RelaxBoundaryPoint函数就介绍完了
到这GetBoundaryFromStaticObstacles函数也介绍完了.

// 4. Append some extra path bound points to avoid zero-length path data.
int counter = 0;
while (!blocking_obstacle_id.empty() &&
       path_bound.size() < temp_path_bound.size() &&
       counter < FLAGS_num_extra_tail_bound_point) {
  path_bound.push_back(temp_path_bound[path_bound.size()]);
  counter++;
}

如果前方有障碍物彻底堵死了道路,计算出的安全边界 path_bound 可能会在障碍物处截断,导致长度极短甚至为零.
上面逻辑的含义是即便被堵住了,也会从原始的、未考虑该障碍物的候选边界 temp_path_bound 中强行取几个点(数量由FLAGS_num_extra_tail_bound_point 决定(默认是20))补在后面.

if (!blocking_obstacle_id.empty()) {
    double current_time = ::apollo::cyber::Clock::NowInSeconds();
    lane_follow_status->set_block_obstacle_id(blocking_obstacle_id);
    if (lane_follow_status->lane_follow_block()) {//上一帧自车已经被障碍物阻塞了
      lane_follow_status->set_block_duration(
          lane_follow_status->block_duration() + current_time -
          lane_follow_status->last_block_timestamp());
    } else {//当前帧自车被障碍物阻塞
      lane_follow_status->set_block_duration(0);
      lane_follow_status->set_lane_follow_block(true);
    }
    lane_follow_status->set_last_block_timestamp(current_time);
  }

lane_follow_status->lane_follow_block()为true:表示自车在上一帧就被障碍物阻塞了
lane_follow_status->set_block_duration(
lane_follow_status->block_duration() + current_time -
lane_follow_status->last_block_timestamp())
:计算阻塞了多久

} else { // 障碍物消失或路通了
    if (lane_follow_status->lane_follow_block()) {
        lane_follow_status->set_block_duration(0); // 清零时长
        lane_follow_status->set_lane_follow_block(false);
    }
}