想搞懂Apollo的EM Planner?从Frenet坐标系和DP/QP优化入手就对了

张开发
2026/4/14 9:46:37 15 分钟阅读

分享文章

想搞懂Apollo的EM Planner?从Frenet坐标系和DP/QP优化入手就对了
深入解析Apollo EM PlannerFrenet坐标系与DP/QP优化的实战指南当一辆自动驾驶汽车在城市街道上自如地穿梭时背后是复杂的路径规划算法在实时计算最优行驶轨迹。百度Apollo平台中的EM Planner算法正是这一领域的佼佼者而理解其核心——Frenet坐标系与动态规划(DP)/二次规划(QP)优化方法是掌握现代自动驾驶规划算法的关键。本文将带您从理论到实践深入这两个技术要点的实现细节。1. Frenet坐标系自动驾驶的自然语言在传统的笛卡尔坐标系中我们习惯用(x,y)来描述位置但这对于道路行驶的车辆来说并不直观。想象一下当您开车时您更关心的是我在车道中心线左侧还是右侧而不是我的经纬度坐标是多少——这正是Frenet坐标系的出发点。1.1 坐标系对比与转换让我们通过一个具体例子来理解两种坐标系的差异import numpy as np # 笛卡尔坐标系下的道路中心线 cartesian_centerline np.array([ [0, 0], [10, 0], [20, 5], [30, 10] ]) # 车辆在笛卡尔坐标系中的位置 vehicle_pos [22, 8]要将这个位置转换为Frenet坐标我们需要找到中心线上最近的点计算沿中心线的累积距离(s)计算垂直于中心线的偏移量(l)def cartesian_to_frenet(pos, centerline): # 计算到各中心线点的距离 distances np.linalg.norm(centerline - pos, axis1) closest_idx np.argmin(distances) # 计算s坐标近似为累积弦长 s np.sum(np.linalg.norm(np.diff(centerline[:closest_idx1], axis0), axis1)) # 计算l坐标 if closest_idx len(centerline)-1: tangent centerline[-1] - centerline[-2] else: tangent centerline[closest_idx1] - centerline[closest_idx] tangent tangent / np.linalg.norm(tangent) normal np.array([-tangent[1], tangent[0]]) l np.dot(pos - centerline[closest_idx], normal) return s, l s, l cartesian_to_frenet(vehicle_pos, cartesian_centerline) print(fFrenet坐标: (s{s:.2f}, l{l:.2f}))执行结果可能显示类似Frenet坐标: (s22.36, l2.23)1.2 Frenet坐标系的核心优势为什么EM Planner要采用Frenet坐标系因为它天然适合道路行驶场景特性笛卡尔坐标系Frenet坐标系道路描述复杂需要完整几何定义简单只需中心线约束处理困难需要复杂计算直观横向(l)直接对应车道边界障碍物投影计算量大可直接投影到(s,l)空间路径平滑全局优化复杂可分解为纵向和横向独立优化在EM Planner中Frenet坐标系的使用贯穿整个规划流程SL投影将障碍物映射到(s,l)空间ST图在速度-时间维度分析动态障碍物路径优化分别在s和l方向进行独立优化2. 动态规划(DP)路径搜索的全局视角动态规划是EM Planner中生成初始路径的关键步骤。与常见的A*等搜索算法不同DP在这里的应用有其独特之处。2.1 EM Planner中的DP实现让我们通过一个简化的道路场景来说明假设我们有一条100米长的道路划分为100个s位置每个s位置有5个可能的l位置-2m,-1m,0m,1m,2m。我们需要找到从起点(s0,l0)到终点(s100,l0)的最优路径。代价函数设计是DP的核心通常包括与参考线(l0)的偏离路径曲率影响舒适性与障碍物的距离车道边界约束def dp_path_planning(samples_s, samples_l, obstacles): n_s len(samples_s) n_l len(samples_l) # 初始化代价矩阵和路径记录 cost np.full((n_s, n_l), np.inf) path np.zeros((n_s, n_l), dtypeint) cost[0, n_l//2] 0 # 起点在中间车道 # 动态规划前向传播 for i in range(1, n_s): for j in range(n_l): min_prev_cost np.inf best_k 0 # 查看前一位置的所有可能状态 for k in range(n_l): prev_cost cost[i-1, k] if prev_cost np.inf: continue # 计算转移代价 delta_l samples_l[j] - samples_l[k] ref_cost samples_l[j]**2 # 偏离参考线代价 smooth_cost delta_l**2 # 曲率代价 # 障碍物代价 obs_cost 0 for obs in obstacles: dist np.sqrt((samples_s[i]-obs[0])**2 (samples_l[j]-obs[1])**2) if dist obs[2]: # 安全距离 obs_cost 1/(dist 1e-6) total_cost prev_cost 0.3*ref_cost 0.5*smooth_cost 2.0*obs_cost if total_cost min_prev_cost: min_prev_cost total_cost best_k k if min_prev_cost np.inf: cost[i, j] min_prev_cost path[i, j] best_k # 回溯找到最优路径 optimal_path [] last_l np.argmin(cost[-1]) optimal_path.append((samples_s[-1], samples_l[last_l])) for i in range(n_s-1, 0, -1): last_l path[i, last_l] optimal_path.append((samples_s[i-1], samples_l[last_l])) return optimal_path[::-1]2.2 DP的局限性与改进虽然DP能提供全局视角但在实际应用中存在几个问题离散化误差采样间隔影响结果精度计算复杂度状态空间随维度指数增长局部最优可能错过更好的非单调路径EM Planner通过以下方式缓解这些问题多分辨率搜索先粗后细代价函数精心设计平衡各项因素仅作为QP的初始解提供者3. 二次规划(QP)从粗糙到平滑DP提供了全局的路径框架但通常不够平滑这正是QP发挥作用的地方。QP可以优化路径的连续性和可执行性。3.1 QP问题构建典型的路径平滑QP问题可以表示为最小化 $$ J w_1\sum(\kappa_i)^2 w_2\sum(\frac{d\kappa_i}{ds})^2 w_3\sum(l_i - l_{ref})^2 $$约束条件 $$ |l_i| \leq l_{max} \quad \text{(车道边界)} $$ $$ |\kappa_i| \leq \kappa_{max} \quad \text{(最大曲率)} $$其中$\kappa_i$是路径在点i处的曲率。让我们用Python实现一个简单的QP路径平滑器import cvxpy as cp def qp_smoother(dp_path, lane_width3.5): n len(dp_path) l cp.Variable(n) # 要优化的横向偏移 # 构建参考线DP路径的l坐标 l_ref np.array([p[1] for p in dp_path]) s np.array([p[0] for p in dp_path]) # 目标函数 cost 0 # 平滑项二阶差分近似曲率 for i in range(1, n-1): cost cp.square(l[i1] - 2*l[i] l[i-1]) / ((s[i1]-s[i-1])**2) # 参考线偏离项 cost 0.5 * cp.sum_squares(l - l_ref) # 约束 constraints [ l -lane_width/2, l lane_width/2 ] # 求解 prob cp.Problem(cp.Minimize(cost), constraints) prob.solve() # 返回平滑后的路径 return [(s[i], l.value[i]) for i in range(n)]3.2 QP在实际应用中的技巧在实际的EM Planner实现中QP的应用更加精细样条参数化使用五次样条曲线表示路径优化样条参数而非离散点动态约束根据DP结果生成安全通道QP在该通道内优化实时性优化热启动、问题分解等技术加速求解以下是一个更接近实际应用的样条优化示例from scipy.interpolate import BSpline def spline_smoother(dp_path, degree3): s np.array([p[0] for p in dp_path]) l np.array([p[1] for p in dp_path]) # 均匀参数化 t np.linspace(0, 1, len(s)) # 构造B样条基函数 knots np.linspace(0, 1, len(s)-degree1) knots np.r_[[0]*degree, knots, [1]*degree] basis [BSpline.basis_element(knots[i:idegree2]) for i in range(len(s))] # 设计矩阵 A np.zeros((len(s), len(s))) for i in range(len(s)): for j in range(len(s)): A[i,j] basis[j](t[i]) # 求解最小二乘问题可加入正则化项 coeff np.linalg.lstsq(A, l, rcondNone)[0] # 构造平滑后的样条曲线 def smoothed_l(s_new): s_norm (s_new - s[0]) / (s[-1] - s[0]) return sum(c*b(s_norm) for c,b in zip(coeff, basis)) return smoothed_l4. EM Planner中的协同优化EM Planner的精髓在于路径和速度的迭代优化而Frenet坐标系与DP/QP方法在这一过程中协同工作。4.1 路径-速度迭代流程初始路径生成在Frenet坐标系下用DP获得粗略路径速度规划基于当前路径生成ST图规划速度剖面路径优化用QP考虑动态障碍物和速度约束平滑路径迭代收敛检查路径和速度是否一致否则回到步骤24.2 实现示例简单闭环规划def em_planner_loop(initial_state, obstacles, max_iter5): # 初始化 path initial_guess_path() speed_profile None for _ in range(max_iter): # 速度规划 st_graph build_st_graph(path, obstacles) new_speed_profile optimize_speed(st_graph, speed_profile) # 路径优化 corridor build_corridor(path, new_speed_profile, obstacles) new_path optimize_path(corridor) # 检查收敛 if converged(path, new_path, speed_profile, new_speed_profile): break path, speed_profile new_path, new_speed_profile return path, speed_profile4.3 性能优化技巧在实际工程实现中还需要考虑计算效率DP的剪枝策略QP问题的稀疏性利用并行化处理数值稳定性条件数控制正则化项添加失败恢复机制实时性保证增量更新预测-校正框架最坏情况时限处理5. 前沿发展与实战建议随着自动驾驶技术的发展EM Planner相关技术也在不断演进。以下是一些值得关注的方向学习增强的规划用强化学习优化DP的代价函数神经网络预测QP的初始解和约束多模态规划同时生成多条候选轨迹基于场景选择最优执行方案不确定性处理模糊障碍物预测鲁棒优化方法对于希望在实际项目中应用这些技术的开发者我的建议是从简单场景开始实现核心算法逐步增加复杂度。先确保Frenet坐标系转换和基础DP/QP能正确工作再考虑性能优化和特殊场景处理。一个实用的开发路线可能是实现Frenet-Cartesian坐标转换构建简单的DP路径搜索添加基础的QP平滑引入动态障碍物处理优化计算效率处理边缘案例和故障模式在算法调试过程中可视化工具不可或缺。建议开发能够实时显示以下内容的调试界面Frenet坐标系中的路径和障碍物DP的搜索过程和结果QP优化前后的路径对比ST图和速度规划结果

更多文章