百度 Apollo 10 Planning 运行框架源码解析

发布于 20 天前  223 次阅读


百度 Apollo 10 Planning 运行框架源码解析

入口函数

Planning 中常见的入口函数可以分成几类:

入口函数 所在层级 主要职责
Init() 模块 / 插件 / 任务初始化 加载配置、初始化依赖对象、注册插件
RunOnce() Planning 周期入口 接收当前周期输入,构造 Frame,触发一次规划
Plan() Planner 入口 从通用调度进入具体 Planner 逻辑
ApplyRule() TrafficRule 入口 执行交通规则,向参考线注入约束
IsTransferable() Scenario 入口判断 判断是否可以切换到某个场景
Enter() Scenario 进入入口 写入场景状态,初始化场景上下文
Process() Scenario / Stage / Task 业务入口 执行当前层级的核心逻辑
ExecuteTaskOnReferenceLine() Stage 调度 Task 的入口 按 pipeline 顺序执行 Task 列表
Exit() Scenario 退出入口 清理场景状态

这些函数对应了 Planning 的几个核心层次:

层级 代表模块 关注点
OnLanePlanning Init()RunOnce()Plan() 周期调度、输入组织、输出轨迹
Planner Plan() 具体规划器执行入口
TrafficDecider Execute() 调度交通规则
TrafficRule ApplyRule() 生成交通规则约束
Scenario IsTransferable()Enter()Process()Exit() 场景切换与场景生命周期
Stage Process()ExecuteTaskOnReferenceLine() 场景内部阶段与 Task 调度
Task Execute()Process() 路径、速度、决策等具体规划任务

从入口函数阅读 Planning,可以避免只停留在“有哪些类”的层面,而是进一步理解这些类在运行时如何被调用、如何共享数据、如何影响最终轨迹。

Planning主运行链路

从入口函数的角度,Apollo 10 Planning 的主链路可以压缩为:

PlanningComponent / Cyber RT
        ↓
OnLanePlanning::Init()
        ↓
OnLanePlanning::RunOnce()
        ↓
OnLanePlanning::Plan()
        ↓
Planner::Plan()
        ↓
ScenarioManager 选择 Scenario
        ↓
Scenario::IsTransferable()
        ↓
Scenario::Enter() / Scenario::Process() / Scenario::Exit()
        ↓
Stage::Process()
        ↓
Stage::ExecuteTaskOnReferenceLine()
        ↓
Task::Execute()
        ↓
Task::Process()
        ↓
PathData + SpeedData + Decision
        ↓
ADCTrajectory

交通规则链路则在正式进入 Planner 之前执行:

OnLanePlanning::RunOnce()
        ↓
Frame 初始化完成
        ↓
遍历 ReferenceLineInfo
        ↓
TrafficDecider::Execute()
        ↓
TrafficRule::ApplyRule()
        ↓
向 ReferenceLineInfo 注入停车墙、虚拟障碍物、交通规则约束
        ↓
OnLanePlanning::Plan()
        ↓
Planner / Scenario / Stage / Task

这一点很关键。ApplyRule() 通常不直接生成最终轨迹,而是提前把交通规则转换为 Planning 可处理的约束。后续的路径、速度、决策 Task 会在这些约束的基础上继续计算。

机制 作用
配置驱动 通过 pipeline.pb.txtdefault_conf.pb.txt 等配置组织 Stage、Task、Rule
插件化 Scenario、Task、TrafficRule 可以按包和插件形式扩展
场景状态机 Scenario 内部通过 Stage 表达复杂交通行为
Task 流水线 每个 Stage 按配置顺序执行路径、速度、决策任务
数据容器共享 FrameReferenceLineInfoPlanningContext 串联不同层级

Init初始化链路

Init() 是 Planning 运行前的初始化入口。它不负责生成轨迹,而是把地图、参考线、Planner、TrafficRule、Scenario、Task 等运行依赖准备好。

OnLanePlanning::Init() 主初始化流程

OnLanePlanning::Init() 是车道级 Planning 的初始化入口。其核心流程可以概括为:

OnLanePlanning::Init(config)
        ↓
检查 Planning 配置
        ↓
初始化 PlanningBase
        ↓
清空 History / PlanningStatus
        ↓
加载 HDMap
        ↓
创建 ReferenceLineProvider
        ↓
启动 ReferenceLineProvider
        ↓
LoadPlanner()
        ↓
TrafficDecider::Init()
        ↓
planner_->Init()

其中,地图、参考线和具体 Planner 是三个关键依赖。

初始化对象 作用
HDMap 提供车道、停止线、路口、交通灯、KeepClear 区域等地图元素
ReferenceLineProvider 根据 Routing 和地图生成候选参考线
planner_ 指向具体 Planner,例如公共道路规划器
TrafficDecider 加载并初始化交通规则插件
PlanningContext / History 保存跨周期状态和历史轨迹相关信息

Planning 后续的路径和速度计算并不是直接在整张地图上进行,而是围绕一条或多条 ReferenceLine 展开。因此 ReferenceLineProvider 的初始化非常关键。它为后续 Frame 中的 ReferenceLineInfo 提供基础数据。

LoadPlanner() 与具体 Planner

LoadPlanner() 根据配置创建具体 Planner。OnLanePlanning 本身更偏向周期调度,真正的场景选择、Stage 执行和 Task 流水线会在具体 Planner 内部继续展开。

OnLanePlanning
        ↓
LoadPlanner()
        ↓
PublicRoadPlanner / 其他 Planner
        ↓
ScenarioManager
        ↓
Scenario / Stage / Task

这说明 OnLanePlanning 与具体规划逻辑之间存在一层抽象边界。调度层负责组织输入输出,具体 Planner 负责展开规划策略。

TrafficDecider::Init() 与规则插件加载

TrafficRule 不是写死在主流程中的一组 if-else,而是由 TrafficDecider 按配置加载和初始化。常见规则包括:

TrafficRule 作用
KeepClear 处理禁止滞留区域
StopSign 处理停车标志
TrafficLight 处理红绿灯停车逻辑
Crosswalk 处理人行横道让行
Destination 处理目的地附近停车
ReferenceLineEnd 处理参考线终点停车

TrafficDecider::Init() 完成规则对象创建后,每个规划周期会通过 TrafficDecider::Execute() 逐个调用规则的 ApplyRule()

TrafficRule::Init()ApplyRule() 的分工

KeepClear 为例,初始化与运行执行是分开的:

KeepClear::Init(name, injector)
        ↓
TrafficRule::Init(name, injector)
        ↓
加载 keep_clear 配置
        ↓
保存配置参数
        ↓
等待每个周期由 ApplyRule() 执行

Init() 只加载配置和准备依赖,ApplyRule() 才是真正规则生效的入口。这种设计让规则模块在启动阶段完成配置解析,在周期运行阶段只关注当前 FrameReferenceLineInfo

Task、Scenario、Stage 的初始化

Scenario、Stage、Task 依赖 pipeline.pb.txt 组织。配置文件定义了某个 Scenario 中有哪些 Stage,每个 Stage 中按什么顺序执行哪些 Task。

pipeline.pb.txt
        ↓
Scenario 配置
        ↓
Stage 配置
        ↓
Task 顺序
        ↓
运行时 task_list_

这一步建立了“配置文件中的任务顺序”和“运行时函数调用”的映射关系。后续 Stage::ExecuteTaskOnReferenceLine() 会按这个顺序执行 Task。

RunOnce周期入口

OnLanePlanning::RunOnce() 是 Apollo 10 Planning 最重要的周期入口。每个规划周期都会调用它一次。

函数形式可以概括为:

void OnLanePlanning::RunOnce(
    const LocalView& local_view,
    ADCTrajectory* const ptr_trajectory_pb);

两个核心参数分别代表本周期输入和最终输出:

参数 含义
local_view 当前周期 Planning 接收到的输入视图
ptr_trajectory_pb 当前周期最终填充的轨迹结果

RunOnce() 更像是一个周期调度器,而不是某个路径优化算法。它负责把一次规划周期需要的输入、状态和中间对象组织起来。

OnLanePlanning::RunOnce(local_view, trajectory)
        ↓
保存 local_view
        ↓
检查输入数据是否 ready
        ↓
更新 VehicleState
        ↓
处理新的 PlanningCommand / Routing
        ↓
更新 ReferenceLineProvider
        ↓
计算 stitching trajectory
        ↓
构造 Frame
        ↓
执行 TrafficDecider
        ↓
调用 Plan()
        ↓
填充 ADCTrajectory
        ↓
发布 Planning 结果

LocalView 固定本周期输入

LocalView 是 Planning 当前周期看到的世界。典型输入包括:

输入 作用
localization_estimate 当前车辆位姿
chassis 底盘状态、驾驶模式、速度等
prediction_obstacles 障碍物预测结果
planning_command 导航、靠边、泊车等规划命令
traffic_light 交通灯检测结果
control_interactive_msg Control 与 Planning 的交互信息

RunOnce() 首先要保存这份输入视图,使本周期后续逻辑使用同一份数据。这样可以避免一个周期内上游数据变化导致状态不一致。

车辆状态与规划起点

Planning 会从定位和底盘信息中构造车辆状态。车辆状态是后续几个步骤的基础:

  • 参考线生成;
  • stitching trajectory 计算;
  • Frame 初始化;
  • SL 坐标转换;
  • 路径边界生成;
  • 速度规划。

如果车辆状态无效,Planning 通常无法继续正常规划,系统会进入错误处理或生成停车轨迹。

Routing / PlanningCommand 更新

当系统接收到新的 Routing 或 PlanningCommand 时,Planning 需要重置部分跨周期状态,例如历史轨迹、参考线提供器、Planner 状态和 PlanningContext。这个动作说明 Planning 是一个连续运行系统,不是每帧完全独立的规划脚本。

Trajectory Stitching

TrajectoryStitcher 的作用是把上一周期已经发布的轨迹和当前车辆真实状态拼接起来,形成当前周期的规划起点。

上一周期已发布轨迹
        +
当前车辆真实状态
        ↓
当前周期 planning_init_point

如果每个周期都直接从车辆当前位置重新规划,轨迹容易不连续。Stitching 机制能够让输出轨迹在周期之间保持连续性,降低 Control 端跟踪压力。

Frame 初始化

Frame 是一次规划周期的数据容器。RunOnce() 初始化 Frame 后,后续 TrafficRule、Scenario、Stage、Task 基本都围绕这个对象运行。

LocalView + VehicleState + ReferenceLine + Obstacles
        ↓
Frame
        ↓
TrafficDecider / Scenario / Stage / Task

TrafficDecider 执行位置

TrafficDecider 在正式调用 Plan() 前执行:

InitFrame()
        ↓
TrafficDecider::Execute(frame, reference_line_info)
        ↓
OnLanePlanning::Plan()

因此,交通规则是在路径和速度 Task 执行之前注入约束的。这个顺序解释了为什么 KeepClear::ApplyRule() 可以影响后续速度规划在虚拟墙前停车。

Planner 分发

OnLanePlanning::Plan() 是从周期调度进入具体 Planner 的桥梁。

OnLanePlanning::RunOnce()
        ↓
OnLanePlanning::Plan()
        ↓
planner_->Plan(planning_init_point, frame, trajectory)

planner_ 是初始化阶段由 LoadPlanner() 创建出来的具体 Planner 对象。OnLanePlanning::Plan() 自身不展开所有场景和任务,而是把当前规划起点、当前周期 Frame 和轨迹输出指针交给具体 Planner。

在公共道路规划中,Planner 内部会结合当前 Frame、参考线信息和 PlanningContext 选择合适的 Scenario。

Planner::Plan()
        ↓
ScenarioManager 更新当前 Scenario
        ↓
Scenario::Process(planning_init_point, frame)
        ↓
当前 Stage::Process(planning_init_point, frame)
        ↓
Task 流水线

从这一层开始,Planning 的执行逻辑进入 Scenario / Stage / Task 三层框架。

TrafficDecider/ApplyRule

TrafficDecider 是 Apollo Planning 中容易被忽略但非常关键的一层。它位于 RunOnce()Plan() 之间,负责把交通规则转换成后续规划可使用的约束。

TrafficRule 的定位

TrafficRule 不直接代表某个交通场景,也不一定直接生成路径或速度。它主要负责把地图规则、交通规则和停车约束写入 ReferenceLineInfo

TrafficRule 典型处理内容 对后续规划的影响
KeepClear 禁止滞留区域 生成虚拟墙或停车约束
StopSign 停车标志 生成停止线前的 stop decision
TrafficLight 红绿灯状态 红灯或黄灯时生成停车约束
Crosswalk 人行横道 对行人、非机动车进行让行约束
Destination 目的地停车 在目的地附近生成停车点
ReferenceLineEnd 参考线终点 防止车辆驶出参考线末端

TrafficDecider::Execute() 调度规则

TrafficDecider 可以理解为 TrafficRule 的统一调度器。它按配置遍历规则插件,并对每条候选参考线执行规则。

TrafficDecider::Execute(frame, reference_line_info)
        ↓
for rule in rule_list:
        ↓
rule->ApplyRule(frame, reference_line_info)

规则执行时,主要输入是 FrameReferenceLineInfo

输入对象 作用
Frame 提供本周期车辆状态、障碍物、PlanningContext 等信息
ReferenceLineInfo 提供当前参考线、地图 overlap、路径/速度/决策容器
TrafficRule 配置 提供规则阈值、距离参数、开关等

ApplyRule() 的职责

ApplyRule() 是交通规则真正生效的入口。它通常完成以下动作:

  1. ReferenceLineInfo 中读取当前参考线;
  2. 从地图路径中读取 overlap 或特殊区域;
  3. 判断 ego vehicle 与规则区域的相对位置;
  4. 判断当前是否需要停车、避让、限速或生成虚拟障碍物;
  5. 创建虚拟墙、虚拟障碍物或 stop decision;
  6. 写回 ReferenceLineInfo
  7. 供后续 Path / Speed / Decision Task 使用。

数据流可以概括为:

HD Map / ReferenceLine / VehicleState / Obstacles
        ↓
TrafficRule::ApplyRule()
        ↓
Virtual Obstacle / Stop Decision / Constraint
        ↓
ReferenceLineInfo
        ↓
Path Task / Speed Task / Decision Task

ApplyRule() 本身不是最终规划算法,但它改变了后续算法面对的约束空间。后续 Task 不需要分别理解每一种交通规则,只要读取 ReferenceLineInfo 中的障碍物、决策和约束即可。

TrafficDecider 与 Task 的关系

TrafficDecider 与 Task 并不是并列替代关系,而是前后衔接关系:

TrafficDecider / TrafficRule
        ↓
提前注入规则约束
        ↓
Stage / Task
        ↓
在约束基础上生成路径和速度

例如 KeepClear::ApplyRule() 生成的虚拟墙,会被后续速度边界、速度决策和速度优化任务使用。路径和速度 Task 不需要知道这个虚拟墙来自 KeepClear,只需要按障碍物或 stop decision 处理即可。

Scenario生命周期

Scenario 是 Apollo Planning 中的交通场景层,负责表达当前车辆处于哪类交通情境中。

Scenario 场景含义
lane_follow 正常车道跟随
stop_sign_unprotected 无保护 Stop Sign
traffic_light_protected 受保护红绿灯
traffic_light_unprotected_left_turn 无保护左转
pull_over 靠边停车
valet_parking 代客泊车
park_and_go 泊车后起步

Scenario 的关键入口不是单个函数,而是一组生命周期函数:

函数 作用
IsTransferable() 判断是否可以切换到该场景
Enter() 进入场景时写入场景状态
Process() 执行当前场景逻辑,通常调度当前 Stage
Exit() 离开场景时清理上下文

IsTransferable()

IsTransferable() 判断当前是否可以从已有场景切换到目标场景。以 stop_sign_unprotected 为例,它通常围绕 Stop Sign overlap、车辆位置、距离阈值和当前 PlanningContext 进行判断。

当前 Frame + ReferenceLineInfo + PlanningContext
        ↓
Scenario::IsTransferable()
        ↓
true:可以进入目标 Scenario
false:继续当前 Scenario 或保持 lane_follow

它只负责场景切换判断,不负责停车,也不负责生成轨迹。

Enter()

Enter() 在 Scenario 被选中后执行。它通常把场景相关状态写入 PlanningContext

以 Stop Sign 场景为例,进入场景时需要记录当前处理的 stop_sign_overlap_id。这个 ID 后续会被多个 Stage 读取,用于判断车辆是否已经停车、是否进入 creep、是否完成路口通过。

查找当前参考线上的 stop_sign_overlap
        ↓
确认当前要处理的 stop_sign_id
        ↓
写入 PlanningContext
        ↓
初始化场景状态

Scenario 不是无状态函数。复杂交通场景往往要持续多个规划周期,必须依赖 PlanningContext 保存跨周期状态。

Process()

Scenario 层的 Process() 一般不直接执行所有路径和速度算法,而是调度当前 Stage。

Scenario::Process(planning_init_point, frame)
        ↓
获取当前 Stage
        ↓
current_stage->Process(planning_init_point, frame)
        ↓
根据 StageResult 判断是否切换 Stage
        ↓
如果场景完成则退出

这一层的职责是维护“当前场景进行到哪个阶段”,并将控制权交给 Stage。

Exit()

Exit() 用于清理场景状态。以 Stop Sign 为例,车辆通过路口后要清除当前 stop sign 状态,避免后续周期继续使用旧的 stop_sign_id 或停车状态。

Stage阶段状态机

Stage 是 Scenario 内部的阶段。许多交通场景不是一次函数调用就能完成,而是由多个阶段组成的状态机。

stop_sign_unprotected 为例,阶段可以表示为:

PRE_STOP
        ↓
STOP
        ↓
CREEP
        ↓
INTERSECTION_CRUISE
        ↓
完成场景
Stage 主要职责
PRE_STOP 接近停止线,准备停车
STOP 在停止线前完成停车和观察
CREEP 低速探出,扩大观察视野
INTERSECTION_CRUISE 通过路口并判断场景结束

Stage::Process() 是阶段业务入口。它通常同时承担两个职责:

  1. 调用 Task 流水线生成本周期规划结果;
  2. 判断当前阶段是否完成,并设置下一个 Stage。

典型结构可以抽象为:

StageResult SomeStage::Process(
    const common::TrajectoryPoint& planning_init_point,
    Frame* frame) {
  // 执行当前阶段特有逻辑

  StageResult result = ExecuteTaskOnReferenceLine(planning_init_point, frame);

  // 根据车辆位置、速度、交通状态、PlanningContext 判断阶段状态
  if (stage_done) {
    return FinishStage();
  }

  return result.SetStageStatus(StageStatusType::RUNNING);
}

Stage::Process() 不只是 Task 的包装层。它还会结合当前场景状态判断是否切换阶段。例如在 Stop Sign 场景中,PRE_STOP 阶段需要判断车辆是否到达停车线附近,STOP 阶段需要判断是否已停稳并满足观察条件,CREEP 阶段需要判断是否可以继续通过路口。

输出类型 说明
规划中间结果 由 Task 写入 ReferenceLineInfo,包括路径、速度、障碍物决策
阶段状态 RUNNINGFINISHEDERROR
下一阶段 通过 next_stage_FinishStage() 触发切换

Task 流水线

ExecuteTaskOnReferenceLine() 是 Stage 到 Task 的分发入口。它按照当前 Stage 的 pipeline 配置,在当前参考线上依次执行 Task。

ReferenceLineInfo 是 Task 执行对象

公共道路 Planning 基本围绕参考线展开。每条参考线对应一个 ReferenceLineInfo,其中包含:

内容 说明
ReferenceLine 参考线几何信息
Obstacles 当前参考线相关障碍物
PathData 路径规划结果
SpeedData 速度规划结果
Decisions 障碍物决策、停车决策等
Cost / Drivable 参考线代价和可驾驶状态
Debug 信息 规划过程中的调试数据

Stage 执行 Task 时,不是对抽象世界执行,而是对每条候选参考线的 ReferenceLineInfo 执行。

pipeline 配置到 Task 顺序

每个 Scenario 的 conf/pipeline.pb.txt 中会配置 Stage 及其 Task 顺序。以 Stop Sign 相关 Stage 为例,常见任务包括:

LANE_FOLLOW_PATH
LANE_BORROW_PATH
FALLBACK_PATH
PATH_DECIDER
RULE_BASED_STOP_DECIDER
SPEED_BOUNDS_PRIORI_DECIDER
SPEED_HEURISTIC_OPTIMIZER
SPEED_DECIDER
SPEED_BOUNDS_FINAL_DECIDER
PIECEWISE_JERK_SPEED

这些配置在初始化阶段加载为运行时 task_list_,然后由 ExecuteTaskOnReferenceLine() 顺序调用。

pipeline.pb.txt
        ↓
Stage 配置
        ↓
task_list_
        ↓
ExecuteTaskOnReferenceLine()
        ↓
Task::Execute()
        ↓
Task::Process()

抽象执行流程

Stage::ExecuteTaskOnReferenceLine(planning_init_point, frame)
        ↓
for reference_line_info in frame->reference_line_info:
        ↓
  if reference_line_info 不可驾驶:跳过或 fallback
        ↓
  for task in task_list_:
        ↓
      task->Execute(frame, reference_line_info)
        ↓
      task->Process(frame, reference_line_info)
        ↓
      记录 debug 信息和耗时
        ↓
  判断 reference_line_info 是否有效
        ↓
返回 StageResult

这个函数是 Apollo Planning “配置驱动任务流水线”的具体落点。源码阅读时,只看 pipeline.pb.txt 只能知道任务顺序;顺着 ExecuteTaskOnReferenceLine() 才能看到这些配置如何转化为实际函数调用。

Task具体规划算法

Task 是 Apollo Planning 中最接近具体算法的一层。不同 Task 的 Process() 执行不同职责,大体可以分为 Decider 和 Optimizer 两类。

Task 类型 代表模块 职责
Decider PathDeciderSpeedDeciderRuleBasedStopDecider 做决策,标记障碍物、停车、让行、跟车等
Optimizer LaneFollowPathPiecewiseJerkSpeed 做路径或速度优化

很多 Task 通过统一的 Execute() 入口被调用,然后进入子类实现的 Process()

Stage::ExecuteTaskOnReferenceLine()
        ↓
Task::Execute(frame, reference_line_info)
        ↓
具体 Task::Process(frame, reference_line_info)

典型 Process() 的输入输出如下:

项目 说明
输入 FrameReferenceLineInfo、规划起点、配置参数
输出 PathDataSpeedData、障碍物决策、stop decision、debug 信息
写入位置 多数结果写入 ReferenceLineInfo
副作用 修改当前参考线上的规划结果或约束

Task 并不总是返回一个完整轨迹。更多情况下,Task 是逐步修改 ReferenceLineInfo。路径 Task 写入路径,速度 Task 写入速度,决策 Task 修改障碍物决策,最终由 Planner 汇总生成 ADCTrajectory

举例LaneFollowPath解析

LaneFollowPath 是理解 Apollo 路径规划 Task 的默认入口,毕竟基础的规划主要走这里。它位于:

modules/planning/tasks/lane_follow_path/

典型文件结构:

lane_follow_path/
├── conf/
│   └── default_conf.pb.txt
├── proto/
│   └── lane_follow_path.proto
├── lane_follow_path.cc
├── lane_follow_path.h
├── plugins.xml
├── cyberfile.xml
├── BUILD
└── README_cn.md

LaneFollowPath::Process() 是它的核心入口。它基于当前参考线、车辆起点、道路边界和障碍物信息,生成可行路径。

LaneFollowPath::Process(frame, reference_line_info)
        ↓
判断是否需要复用路径
        ↓
GetStartPointSLState()
        ↓
DecidePathBounds()
        ↓
OptimizePath()
        ↓
AssessPath()
        ↓
写入 reference_line_info->path_data()

GetStartPointSLState()

Planning 内部大量使用 SL 坐标系:

坐标 含义
s 沿参考线方向的纵向距离
l 相对于参考线的横向偏移

GetStartPointSLState() 将当前规划起点从笛卡尔坐标转换到参考线坐标系中。后续路径边界和路径优化通常都在 SL 坐标系下展开。

DecidePathBounds()

DecidePathBounds() 负责生成当前车辆可行驶的横向边界。它会综合考虑:

约束来源 作用
当前参考线 提供路径中心和纵向展开方向
道路边界 限制车辆不能驶出道路
车道边界 限制或影响车道内行驶区域
ADC 车辆尺寸 通过车宽、安全余量收缩可行区域
静态障碍物 在路径边界中切除不可通行空间
借道或绕障需求 在特定条件下扩展或调整边界
TrafficRule 注入的虚拟障碍物 将交通规则转化为路径/速度约束

其本质是先构建可行空间:

地图边界 + 车道边界 + 车辆尺寸 + 障碍物 + 规则约束
        ↓
PathBoundary

OptimizePath()

OptimizePath() 在路径边界内求解一条平滑、可行的路径。目标通常包括:

  • 尽量靠近参考线;
  • 横向偏移平滑;
  • 曲率变化平滑;
  • 不碰撞;
  • 不越界;
  • 满足车辆运动学约束。

如果 DecidePathBounds() 解决的是“能走哪里”,OptimizePath() 解决的是“在可行空间中选择哪条路径”。

AssessPath()

AssessPath() 用于筛选和评估候选路径。它会排除不可用路径,例如:

异常类型 说明
与障碍物冲突 路径与静态障碍物或虚拟障碍物冲突
超出边界 路径点超过道路或车道允许范围
路径不连续 几何或曲率变化不合理
横向偏移异常 偏离参考线过大
不满足可驾驶性 不适合作为当前参考线上的可执行路径

最终选出的路径会写入 ReferenceLineInfo,供后续速度规划 Task 使用。

LaneFollowPath 在整体链路中的位置

Stage::Process()
        ↓
ExecuteTaskOnReferenceLine()
        ↓
LaneFollowPath::Process()
        ↓
PathDecider::Process()
        ↓
SpeedBoundsDecider / SpeedOptimizer / SpeedDecider
        ↓
最终轨迹

LaneFollowPath 只解决路径部分,速度和最终轨迹还需要后续 Task 继续处理。

举例StopSignUnprotected解析

stop_sign_unprotected 是理解 Scenario / Stage 状态机的典型案例。目录通常位于:

modules/planning/scenarios/stop_sign_unprotected/

典型结构:

stop_sign_unprotected/
├── conf/
│   ├── pipeline.pb.txt
│   └── scenario_conf.pb.txt
├── proto/
│   └── stop_sign_unprotected.proto
├── stop_sign_unprotected_scenario.cc
├── stop_sign_unprotected_scenario.h
├── stage_pre_stop.cc
├── stage_pre_stop.h
├── stage_stop.cc
├── stage_stop.h
├── stage_creep.cc
├── stage_creep.h
├── stage_intersection_cruise.cc
├── stage_intersection_cruise.h
├── plugins.xml
├── cyberfile.xml
└── README_cn.md

这个场景的主线由 Scenario 生命周期和多个 Stage 组成:

StopSignUnprotectedScenario::IsTransferable()
        ↓
StopSignUnprotectedScenario::Enter()
        ↓
StagePreStop::Process()
        ↓
StageStop::Process()
        ↓
StageCreep::Process()
        ↓
StageIntersectionCruise::Process()
        ↓
StopSignUnprotectedScenario::Exit()

StopSignUnprotectedScenario::IsTransferable()

这是场景切换判断入口。主要判断当前是否应该进入无保护 Stop Sign 场景。

读取当前 Frame
        ↓
读取当前 ReferenceLineInfo
        ↓
查找 stop_sign overlap
        ↓
判断 stop sign 是否在车辆前方
        ↓
计算 ego 到 stop line 的距离
        ↓
检查距离是否满足进入阈值
        ↓
返回 true / false

IsTransferable() 不生成停车轨迹。它只判断是否应该让 ScenarioManager 切换到该场景。

StopSignUnprotectedScenario::Enter()

进入该场景后,Enter() 会把当前要处理的 stop sign 状态写入 PlanningContext

进入 StopSignUnprotectedScenario
        ↓
找到当前要处理的 stop_sign_overlap_id
        ↓
写入 PlanningContext
        ↓
初始化 stop sign 场景状态

Stop Sign 场景通常跨多个周期执行。车辆可能需要先接近停止线,再停车,再观察,再低速 creep,最后通过路口。这些状态必须跨周期保存。

StagePreStop::Process()

PRE_STOP 阶段处理车辆到达停止线前的逻辑。

StagePreStop::Process(planning_init_point, frame)
        ↓
ExecuteTaskOnReferenceLine(planning_init_point, frame)
        ↓
获取当前 ReferenceLineInfo
        ↓
读取 PlanningContext 中的 stop_sign_overlap_id
        ↓
判断车辆与停止线的关系
        ↓
判断是否满足进入 STOP 阶段条件
        ↓
未完成则保持 RUNNING
        ↓
完成则设置 next_stage 并 FinishStage()

这一阶段既会执行 Task pipeline,也会判断阶段是否完成。Task 负责生成路径、速度和停车约束,Stage 负责决定是否从 PRE_STOP 切换到 STOP

StageStop::Process()

STOP 阶段关注车辆是否已经在停止线前完成停车,以及是否满足继续前进的条件。

典型判断包括:

判断项 作用
车辆速度 判断是否已经停稳
车辆位置 判断是否位于停止线前合理位置
停车时间 判断是否满足 stop sign 停车要求
冲突对象 判断是否存在需要观察或让行的车辆
下一阶段条件 决定进入 CREEPINTERSECTION_CRUISE

抽象流程:

StageStop::Process()
        ↓
ExecuteTaskOnReferenceLine()
        ↓
检查停车状态
        ↓
检查等待 / 观察条件
        ↓
满足条件则切换到 CREEP 或 INTERSECTION_CRUISE
        ↓
否则保持 RUNNING

StageCreep::Process()

CREEP 阶段用于车辆低速探出,以获得更好的路口视野。无保护 Stop Sign 场景下,车辆停稳后未必立即具备安全通过条件。Creep 允许车辆缓慢前移,同时继续观察冲突对象。

StageCreep::Process()
        ↓
ExecuteTaskOnReferenceLine()
        ↓
检查 creep 距离 / creep 速度
        ↓
检查路口冲突对象
        ↓
满足通过条件则切换到 INTERSECTION_CRUISE
        ↓
否则继续 CREEP

StageIntersectionCruise::Process()

INTERSECTION_CRUISE 阶段表示车辆已经进入或正在通过路口。该阶段继续执行 Task,并判断车辆是否已经通过 stop sign 影响区域。

StageIntersectionCruise::Process()
        ↓
ExecuteTaskOnReferenceLine()
        ↓
检查是否已通过 stop sign 影响区域
        ↓
完成则 FinishScenario
        ↓
否则继续 RUNNING

StopSignUnprotectedScenario::Exit()

场景完成后调用 Exit(),清理 PlanningContext 中与当前 stop sign 相关的状态。这个动作可以防止旧的 stop sign id、停车状态或观察状态影响后续规划周期。

举例KeepClear规则解析

KeepClear 是理解 TrafficRule 的典型案例。它通常位于:

modules/planning/traffic_rules/keepclear/

典型结构:

keepclear/
├── conf/
│   └── default_conf.pb.txt
├── proto/
│   └── keep_clear.proto
├── keep_clear.cc
├── keep_clear.h
├── plugins.xml
├── cyberfile.xml
└── README_cn.md

KeepClear 由 Init()ApplyRule() 串联:

KeepClear::Init()
        ↓
加载规则配置
        ↓
每个规划周期
        ↓
KeepClear::ApplyRule(frame, reference_line_info)
        ↓
向 ReferenceLineInfo 注入约束

KeepClear::Init()

Init() 用于加载 KeepClear 规则配置。

KeepClear::Init(name, injector)
        ↓
TrafficRule::Init(name, injector)
        ↓
LoadConfig<KeepClearConfig>()
        ↓
保存 keepclear_config

初始化阶段不判断当前车辆是否需要停车,也不生成虚拟墙,只准备运行参数。

KeepClear::ApplyRule()

ApplyRule() 是 KeepClear 真正影响 Planning 的入口。

KeepClear::ApplyRule(frame, reference_line_info)
        ↓
从 ReferenceLineInfo 读取当前参考线
        ↓
查找 clear_area / keepclear overlap
        ↓
判断 ego 与 KeepClear 区域的位置关系
        ↓
判断是否需要阻止车辆进入该区域
        ↓
构造虚拟障碍物 / 虚拟墙
        ↓
向 ReferenceLineInfo 写入 stop decision
        ↓
后续 Task 根据该约束规划路径和速度

虚拟墙的作用

KeepClear 区域本身不是一个真实障碍物,但在特定交通状态下,车辆不能停在该区域内。Apollo 会把这类规则转换为虚拟障碍物或虚拟停止墙,使后续规划模块可以用统一方式处理。

好处 说明
统一障碍物模型 后续 Task 不需要专门理解每一种交通规则
便于路径/速度约束 虚拟墙可以参与 stop decision 或速度边界计算
规则与算法解耦 TrafficRule 负责规则判断,Task 负责轨迹生成

KeepClear 的完整链路如下:

OnLanePlanning::RunOnce()
        ↓
InitFrame()
        ↓
TrafficDecider::Execute()
        ↓
KeepClear::ApplyRule()
        ↓
ReferenceLineInfo 中出现虚拟障碍物 / stop decision
        ↓
Stage::Process()
        ↓
Task::Process()
        ↓
速度规划在虚拟墙前停车或减速

这说明 ApplyRule() 的执行早于 Task pipeline,因此它可以影响后续 LaneFollowPathSpeedDeciderPiecewiseJerkSpeed 等任务。

数据流转

入口函数只是控制流,真正贯穿整个 Planning 的是数据对象。FrameReferenceLineInfoPlanningContext 是阅读源码时最需要跟踪的三个对象。

Frame:一次规划周期的数据容器

Frame 贯穿整个周期:

RunOnce()
        ↓
InitFrame()
        ↓
TrafficDecider::Execute(frame, reference_line_info)
        ↓
Scenario::Process(planning_init_point, frame)
        ↓
Stage::Process(planning_init_point, frame)
        ↓
Task::Process(frame, reference_line_info)
特征 说明
生命周期 单个规划周期
内容 当前输入、车辆状态、参考线、障碍物、规划上下文入口
使用者 TrafficRule、Scenario、Stage、Task
作用 将本周期规划所需信息集中管理

ReferenceLineInfo:围绕参考线组织的规划结果

每条参考线对应一个 ReferenceLineInfo。规划过程中,它会不断被 TrafficRule 和 Task 修改。

ReferenceLineProvider 生成 ReferenceLine
        ↓
Frame 构造 ReferenceLineInfo
        ↓
TrafficRule::ApplyRule() 注入规则约束
        ↓
Path Task 写入 PathData
        ↓
Speed Task 写入 SpeedData
        ↓
Decision Task 写入障碍物决策
        ↓
最终合成轨迹
写入者 写入内容
TrafficRule 虚拟障碍物、stop decision、规则约束
Path Task PathData、路径边界、路径评估结果
Speed Task SpeedData、速度边界、速度曲线
Decision Task 障碍物横纵向决策
Planner 选择最终可驾驶参考线并合成轨迹

ReferenceLineInfo 是 TrafficRule、Task 和最终轨迹之间的重要中介。

PlanningContext:跨周期状态容器

Frame 管当前周期,PlanningContext 管跨周期状态。

Scenario::Enter()
        ↓
写入当前 stop_sign_overlap_id
        ↓
Stage::Process()
        ↓
读取 stop sign 状态
        ↓
状态持续多个周期
        ↓
Scenario::Exit()
        ↓
清理状态
使用场景 示例
Stop Sign 当前处理的 stop sign、是否已停车、是否正在 creep
Traffic Light 当前信号灯状态相关的规划状态
Pull Over 靠边停车阶段状态
Park and Go 泊车后起步状态

没有 PlanningContext,Scenario 很难表达“已经停过车”“正在 creep”“当前处理的是哪个 stop sign”这类状态。

完整调用链

保留最关键的入口函数后,Apollo 10 Planning 的运行链路可以写成:

OnLanePlanning::Init()
        ↓
TrafficDecider::Init()
        ↓
planner_->Init()
        ↓
OnLanePlanning::RunOnce()
        ↓
InitFrame()
        ↓
TrafficDecider::Execute()
        ↓
TrafficRule::ApplyRule()
        ↓
OnLanePlanning::Plan()
        ↓
planner_->Plan()
        ↓
Scenario::IsTransferable()
        ↓
Scenario::Enter()
        ↓
Scenario::Process()
        ↓
Stage::Process()
        ↓
Stage::ExecuteTaskOnReferenceLine()
        ↓
Task::Execute()
        ↓
Task::Process()
        ↓
Scenario::Exit()
        ↓
ADCTrajectory

对应到重点模块:

KeepClear
  Init()       → 加载规则配置
  ApplyRule()  → 生成虚拟墙 / stop decision

StopSignUnprotected
  IsTransferable() → 判断是否进入 Stop Sign 场景
  Enter()          → 写入 stop_sign_overlap_id
  Process()        → 调度当前 Stage
  Exit()           → 清理 stop sign 状态

StagePreStop / StageStop / StageCreep / StageIntersectionCruise
  Process()        → 执行阶段逻辑 + 调用 Task pipeline

LaneFollowPath
  Process()              → 路径生成主入口
  GetStartPointSLState() → 规划起点 SL 转换
  DecidePathBounds()     → 生成路径边界
  OptimizePath()         → 路径优化
  AssessPath()           → 路径评估
QQ:2219349024
最后更新于 2026-04-28