百度 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.txt、default_conf.pb.txt 等配置组织 Stage、Task、Rule |
| 插件化 | Scenario、Task、TrafficRule 可以按包和插件形式扩展 |
| 场景状态机 | Scenario 内部通过 Stage 表达复杂交通行为 |
| Task 流水线 | 每个 Stage 按配置顺序执行路径、速度、决策任务 |
| 数据容器共享 | Frame、ReferenceLineInfo、PlanningContext 串联不同层级 |
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() 才是真正规则生效的入口。这种设计让规则模块在启动阶段完成配置解析,在周期运行阶段只关注当前 Frame 和 ReferenceLineInfo。
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)
规则执行时,主要输入是 Frame 和 ReferenceLineInfo:
| 输入对象 | 作用 |
|---|---|
Frame |
提供本周期车辆状态、障碍物、PlanningContext 等信息 |
ReferenceLineInfo |
提供当前参考线、地图 overlap、路径/速度/决策容器 |
| TrafficRule 配置 | 提供规则阈值、距离参数、开关等 |
ApplyRule() 的职责
ApplyRule() 是交通规则真正生效的入口。它通常完成以下动作:
- 从
ReferenceLineInfo中读取当前参考线; - 从地图路径中读取 overlap 或特殊区域;
- 判断 ego vehicle 与规则区域的相对位置;
- 判断当前是否需要停车、避让、限速或生成虚拟障碍物;
- 创建虚拟墙、虚拟障碍物或 stop decision;
- 写回
ReferenceLineInfo; - 供后续 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() 是阶段业务入口。它通常同时承担两个职责:
- 调用 Task 流水线生成本周期规划结果;
- 判断当前阶段是否完成,并设置下一个 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,包括路径、速度、障碍物决策 |
| 阶段状态 | RUNNING、FINISHED、ERROR 等 |
| 下一阶段 | 通过 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 | PathDecider、SpeedDecider、RuleBasedStopDecider |
做决策,标记障碍物、停车、让行、跟车等 |
| Optimizer | LaneFollowPath、PiecewiseJerkSpeed |
做路径或速度优化 |
很多 Task 通过统一的 Execute() 入口被调用,然后进入子类实现的 Process()。
Stage::ExecuteTaskOnReferenceLine()
↓
Task::Execute(frame, reference_line_info)
↓
具体 Task::Process(frame, reference_line_info)
典型 Process() 的输入输出如下:
| 项目 | 说明 |
|---|---|
| 输入 | Frame、ReferenceLineInfo、规划起点、配置参数 |
| 输出 | PathData、SpeedData、障碍物决策、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 停车要求 |
| 冲突对象 | 判断是否存在需要观察或让行的车辆 |
| 下一阶段条件 | 决定进入 CREEP 或 INTERSECTION_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,因此它可以影响后续 LaneFollowPath、SpeedDecider、PiecewiseJerkSpeed 等任务。
数据流转
入口函数只是控制流,真正贯穿整个 Planning 的是数据对象。Frame、ReferenceLineInfo 和 PlanningContext 是阅读源码时最需要跟踪的三个对象。
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() → 路径评估

Comments NOTHING