[pilot智驾系统] 纵向规划器(LongitudinalPlanner) | 模型预测控制(MPC)
第6章:纵向规划器(LongitudinalPlanner)
在上一章中,我们了解到模型守护进程(modeld)是sunnypilot
的"视觉先知",能够预测前方道路、车道线和其他车辆。
modeld
告诉我们即将发生的情况,并给出建议的期望路径和加速度。但这些仅仅是预测。
sunnypilot
如何将这些原始预测转化为车辆速度与加速度的平稳、舒适且最重要的是安全的规划?
它如何精确决定何时踩油门或刹车以跟随前车、在信号灯前停下或达到巡航速度?
这就是**纵向规划器(LongitudinalPlanner)**的职责所在
将纵向规划器视为sunnypilot
的战略速度管理者。它就像大脑中持续计算最佳加速和刹车策略的部分,综合当前车速、期望速度(来自巡航控制或导航)、其他车辆距离以及modeld
对道路的预测等信息,为未来几秒制定详细的加速度"计划"。
其主要职责是生成平稳安全的加速度曲线,确保保持舒适跟车距离、平顺停车并高效达到目标速度,同时遵守交通规则。
为什么需要纵向规划器?
没有纵向规划器,sunnypilot
虽然能接收modeld
的预测(如"此处需要轻微加速"),但无法智能地将其转化为随时间推移的系列油门刹车动作。可能导致刹车过猛、加速突兀或跟车过近。
纵向规划器采用**模型预测控制(MPC)**技术解决这个问题。MPC如同拥有一个超级智能助手,持续展望未来(约10秒),尝试不同油门刹车组合,最终选择最优动作序列。
这个"最优"序列需满足:保证安全(避免碰撞)、确保舒适(无突然颠簸)并实现目标(达到期望速度/距离)。
它确保sunnypilot
不仅对当前时刻做出反应,更能提前规划以实现平顺最优的行驶体验。
用例:保持安全跟车距离
假设我们在高速公路上开启sunnypilot
跟随前车,前车突然开始减速。
纵向规划器如何确保我们的车辆也平稳减速,保持安全舒适的跟车距离,避免刹车过猛或距离过近?
它通过持续执行以下步骤实现:
- 观察:通过雷达获取当前车速及前车距离和速度
- 预测:使用
modeld
的输出确认前车行为和道路曲率 - 规划:运行优化算法,找到未来几秒最平顺安全的加速度(或减速度)曲线,完美匹配前车减速,同时遵循设定的跟车距离和舒适性参数
- 输出:向控制守护进程(controlsd)提供具体的期望加速度(
aTarget
)和是否完全停车(shouldStop
)指令
纵向规划器工作原理
纵向规划器持续运行,每秒多次(20Hz频率,即每0.05秒与modeld
输出同步)。以下是其核心任务的简化流程:
- 收集信息:获取所有必要输入:
- 当前车辆状态:通过车辆接口(CarInterface)获取车速和加速度
- 雷达信息:雷达系统提供前车距离和速度
- 模型预测:模型守护进程(modeld)提供的未来路径、速度和加速度预测
- 巡航速度:设定的巡航控制速度
- 系统状态:自动驾驶守护进程(selfdrived)提供的
sunnypilot
激活状态和驾驶"个性"
- 运行优化:将所有信息输入核心组件
LongitudinalMpc
(模型预测控制求解器),计算未来约10秒车辆的最优加速度和速度轨迹(速度随时间变化的计划) - 提取关键指令:从详细轨迹中提取最重要的即时指令:下一时刻的目标加速度(
aTarget
)和停车标志(shouldStop
) - 发布输出:将这些指令与完整预测的速度/加速度轨迹打包成
longitudinalPlan
消息,发送给控制守护进程(controlsd)执行
流程可视化:
如何使用纵向规划器(与其输出交互)
作为初学者,我们不会直接"调用"纵向规划器。
其他sunnypilot
组件(特别是控制守护进程(controlsd))会监听其发布的longitudinalPlan
消息。
该消息包含controlsd
用于控制车辆油门刹车的精确aTarget
(期望加速度)和shouldStop
标志。
以下是控制守护进程(controlsd)读取该信息的简化示例:
import cereal.messaging as messaging
from cereal import log # 访问LongitudinalPlan消息结构# 创建SubMaster监听消息
sm = messaging.SubMaster(["longitudinalPlan"])# 在控制守护进程主循环中:
while True:sm.update(0) # 检查新消息if sm.updated["longitudinalPlan"]:plan = sm["longitudinalPlan"]# 从规划器获取期望加速度desired_accel = plan.aTargetprint(f"纵向规划器期望加速度: {desired_accel:.2f} m/s^2")# 检查规划器是否认为应该停车if plan.shouldStop:print("纵向规划器指令: 准备停车!")else:print("纵向规划器指令: 继续行驶")# 也可查看预测的未来速度轨迹:# print(f"未来速度(前5个值): {plan.speeds[:5]}")# ... controlsd其余逻辑 ...
这段代码展示controlsd
如何将aTarget
作为油门/刹车的主要指令,shouldStop
用于特定停车操作。
纵向规划器的详细速度计划正是通过这些指令直接影响车辆加速度。
底层原理:LongitudinalPlanner
核心循环
LongitudinalPlanner
主要在selfdrive/controls/lib/longitudinal_planner.py
实现,其update
方法被重复调用以生成新计划。
以下是update
方法中关键步骤的简化版本:
# 摘自selfdrive/controls/lib/longitudinal_planner.py(简化版)
import numpy as np
from openpilot.selfdrive.controls.lib.longitudinal_mpc_lib.long_mpc import LongitudinalMpcclass LongitudinalPlanner:def __init__(self, CP, init_v=0.0, init_a=0.0, dt=0.05):self.mpc = LongitudinalMpc(dt=dt) # 核心优化求解器self.output_a_target = 0.0self.output_should_stop = False# ... 其他初始化 ...def update(self, sm):v_ego = sm['carState'].vEgo # 当前速度v_cruise = sm['carState'].vCruise * CV.KPH_TO_MS # 设定巡航速度# 1. 解析模型守护进程的预测x, v, a, j, throttle_prob = self.parse_model(sm['modelV2'])# x, v, a, j是自车的预测轨迹# 2. 设置MPC求解器当前状态和参数# (如根据激活状态启用/禁用先前加速度约束)prev_accel_constraint = not sm['carState'].standstill # 简化检查self.mpc.set_weights(prev_accel_constraint, personality=sm['selfdriveState'].personality)self.mpc.set_cur_state(self.v_desired_filter.x, self.a_desired) # 设置MPC初始速度和加速度# 3. 运行核心MPC优化!self.mpc.update(sm['radarState'], v_cruise, x, v, a, j, personality=sm['selfdriveState'].personality)# 4. 从MPC解中提取期望轨迹self.v_desired_trajectory = np.interp(CONTROL_N_T_IDX, T_IDXS_MPC, self.mpc.v_solution)self.a_desired_trajectory = np.interp(CONTROL_N_T_IDX, T_IDXS_MPC, self.mpc.a_solution)# 5. 确定即时输出指令(aTarget和shouldStop)action_t = self.CP.longitudinalActuatorDelay + DT_MDL # 考虑延迟self.output_a_target, self.output_should_stop = get_accel_from_plan(self.v_desired_trajectory, self.a_desired_trajectory, CONTROL_N_T_IDX, action_t=action_t)# 6. 对输出加速度应用最终裁剪/平滑self.output_a_target = np.clip(self.output_a_target, ACCEL_MIN, ACCEL_MAX) # 简化裁剪def publish(self, sm, pm):plan_send = messaging.new_message('longitudinalPlan')longitudinalPlan = plan_send.longitudinalPlanlongitudinalPlan.aTarget = float(self.output_a_target)longitudinalPlan.shouldStop = bool(self.output_should_stop)# ... 填充其他轨迹数据 ...pm.send('longitudinalPlan', plan_send)
这个简化的update
方法展示了self.mpc.update()
的核心作用,正是这里完成了计算最优加速度曲线的繁重工作。
规划器核心:LongitudinalMpc
LongitudinalMpc
类(位于selfdrive/controls/lib/longitudinal_mpc_lib/long_mpc.py
)是驱动规划过程的引擎,实现了**模型预测控制(MPC)**求解器。
在此上下文中MPC的含义:
- 模型:具有车辆速度加速度(
x_ego
,v_ego
,a_ego
)随加加速度(j_ego
,即加速度变化率)变化的简化数学模型 - 预测:使用该模型预测未来"视野"(约10秒,分为
N=12
个时间步长)内车辆速度加速度的变化 - 控制:计算该时间范围内最佳的加加速度指令序列以实现特定目标:
- 跟车距离:保持与前车的安全距离
- 巡航速度:达到并维持巡航控制设定速度
- 舒适性:
最小化加速度突变
(加加速度) - 安全性:避免碰撞障碍物或前车
- 限制:遵守车辆物理加速度/减速度极限
LongitudinalMpc
通过gen_long_model()
和gen_long_ocp()
(脱机构建求解器代码)设置。这些函数定义了车辆物理模型(“模型”)、“成本”(需最小化或最大化的目标,如舒适性、与障碍物距离)和"约束"(必须遵守的规则,如不超过最大加速度或避免碰撞)。
LongitudinalMpc
的update
方法运行实际求解器:
# 摘自selfdrive/controls/lib/longitudinal_mpc_lib/long_mpc.py(简化版)
class LongitudinalMpc:# ... (初始化方法) ...def update(self, radarstate, v_cruise, x, v, a, j, personality=log.LongitudinalPersonality.standard):# 1. 根据驾驶个性计算期望跟车距离t_follow = get_T_FOLLOW(personality) # 如标准个性为1.45秒# 2. 处理前车数据(推算其未来位置)lead_xv_0 = self.process_lead(radarstate.leadOne) # 前车0的未来x, vlead_xv_1 = self.process_lead(radarstate.leadTwo) # 前车1的未来x, v# 3. 为MPC创建"障碍物":# - 实际前车(推算位置+停车等效距离)# - 代表巡航设定速度的"虚拟"障碍物lead_0_obstacle = lead_xv_0[:,0] + get_stopped_equivalence_factor(lead_xv_0[:,1])# ... 计算其他障碍物 ...cruise_obstacle = np.cumsum(T_DIFFS * v_cruise_clipped) + get_safe_obstacle_distance(v_cruise_clipped, t_follow)# 4. 确定未来每个时间步最近的障碍物self.params[:,2] = np.min(x_obstacles, axis=1) # MPC的'x_obstacle'参数# 5. 设置其他MPC参数(加速度最小/最大值、先前加速度等)self.params[:,0] = ACCEL_MINself.params[:,1] = ACCEL_MAXself.params[:,3] = np.copy(self.prev_a) # 用于平滑self.params[:,4] = t_followself.params[:,5] = LEAD_DANGER_FACTOR # 使跟车成为软约束# 6. 设置MPC参考轨迹(期望实现的目标)# 这些是模型的期望路径/速度或调整后的巡航目标self.yref[:,1] = x # 期望位置self.yref[:,2] = v # 期望速度self.yref[:,3] = a # 期望加速度self.yref[:,5] = j # 期望加加速度# 7. 运行Acados求解器(实际优化过程)self.run()# 8. 存储最优解(预测速度、加速度、加加速度)self.v_solution = self.x_sol[:,1] # 最优速度轨迹self.a_solution = self.x_sol[:,2] # 最优加速度轨迹self.j_solution = self.u_sol[:,0] # 最优加加速度轨迹
LongitudinalMpc.update
方法通过定义当前状态、未来障碍物(前车、巡航速度)和参考轨迹来构建优化问题
随后调用self.run()
触发高效的C语言生成ACADOS求解器,找到未来10秒的最优v_solution
、a_solution
和j_solution
。
个性化与跟车距离
sunnypilot
允许选择不同的"纵向驾驶个性"(轻松、标准、激进)。这些个性直接影响纵向规划器的参数,特别是t_follow
(跟车时距)和jerk_factor
(加加速度系数)。
t_follow
:定义跟随前车的秒数。数值越大跟车距离越远,越小则越近jerk_factor
:影响MPC对加速度突变的惩罚程度。数值越高驾驶越平顺(减少颠簸)
这让我们可以自定义sunnypilot
的加速刹车行为以适应个人偏好。
总结
**纵向规划器(LongitudinalPlanner)**是sunnypilot
精密的速度与加速度管理者。
它综合车辆状态、前车信息、道路条件和用户偏好,采用高级优化算法(模型预测控制)生成未来几秒平稳安全的加速度计划。
通过输出精确的aTarget
和shouldStop
标志,直接控制车辆油门刹车,提供精致智能的驾驶体验。
接下来我们将聚焦驾驶安全,探索监控驾驶员注意力的驾驶员监控守护进程(dmonitoringd)
下一章:驾驶员监控守护进程(dmonitoringd)