1. 自动驾驶轨迹规划的核心挑战
在自动驾驶系统中,轨迹规划模块需要解决一个看似简单实则复杂的问题:如何在动态环境中生成一条既安全又舒适的行驶路径。这涉及到两个关键维度——空间(路径)和时间(速度)。传统方法往往将二者割裂处理,导致规划结果缺乏整体协调性。
我在实际项目中发现,单纯的空间路径规划可能会产生"理论上最优但实际无法执行"的轨迹。例如,规划出一条曲率完美的弯道路径,却忽略了车辆在该速度下的动力学限制。这就是为什么现代自动驾驶系统普遍采用路径-速度解耦策略的根本原因。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 动态规划在轨迹规划中的优势
动态规划(DP)特别适合解决这类具有"最优子结构"特性的问题。其核心思想是将大问题分解为相互关联的子问题,通过记忆化存储中间结果避免重复计算。在自动驾驶场景中,这意味着:
- 路径规划可以看作是在SL坐标系下的二维优化问题
- 速度规划则是在ST图(距离-时间)上的一维优化问题
- 二者通过车辆动力学模型和障碍物投影建立耦合关系
实际工程中,DP方案相比纯优化方法有个显著优势:当优化问题无解时,DP至少能给出一个"最不差"的可行解,这对安全至上的自动驾驶系统至关重要。
3. 路径规划模块实现细节
3.1 Frenet坐标系的妙用
将全局笛卡尔坐标转换到Frenet坐标系是路径规划的第一步。这个由参考线(s坐标)和垂直距离(l坐标)构成的坐标系,让复杂的三维道路变成了简单的二维网格。具体实现时需要注意:
cpp复制// C++示例:参考线离散化
vector<FrenetPoint> discretizeReferenceLine(const ReferenceLine& ref, double ds) {
vector<FrenetPoint> points;
for(double s=0; s<ref.length(); s+=ds) {
points.emplace_back(ref.computeFrenetPoint(s));
}
return points;
}
3.2 五次多项式连接技术
相邻层节点间采用五次多项式连接不是偶然选择。从运动学角度看:
- 三次多项式只能保证位置和速度连续
- 五次多项式可以额外保证加速度连续
- 更高阶多项式会导致"龙格现象"和数值不稳定
数学表达式为:
code复制l(s) = a0 + a1*s + a2*s² + a3*s³ + a4*s⁴ + a5*s⁵
其中系数通过边界条件(起终点的l, dl/ds, d²l/ds²)求解。
3.3 代价函数的工程调参
一个实用的代价函数通常包含这些要素:
python复制def calc_path_cost(start_node, end_node, obstacles):
# 舒适性代价
comfort_cost = w_l * l**2 + w_dl * (dl/ds)**2 + w_ddl * (d²l/ds²)**2
# 障碍物代价
collision_cost = 0
for s in np.linspace(start_node.s, end_node.s, 10):
if check_collision(s, l(s), obstacles):
return float('inf')
# 参考线偏离代价
ref_cost = w_ref * (l - l_ref)**2
return comfort_cost + ref_cost
其中权重参数(w_l, w_dl等)需要根据实际车辆特性调整。我们的经验值是:
- 城市道路:更注重舒适性(w_ddl较大)
- 高速公路:更注重跟车精度(w_ref较大)
4. 速度规划的关键技术
4.1 动态障碍物投影
将动态障碍物映射到ST图是个计算密集型任务。优化技巧包括:
- 对障碍物轨迹进行分段线性近似
- 使用保守膨胀矩形(比实际尺寸大10-20%)
- 建立时空哈希表加速碰撞检测
4.2 状态转移的物理约束
相邻时间层节点的连接必须满足:
code复制|s2 - s1| <= v_max * Δt
|(s2-s1)/Δt - (s1-s0)/Δt| <= a_max * Δt
这实际上构成了一个前向可达集,可以大幅剪枝无效连接。
5. C++实现性能优化
5.1 内存预分配
DP需要存储整个网格的代价和前驱指针。我们采用:
cpp复制struct DPGrid {
vector<vector<Node>> nodes;
vector<vector<double>> costs;
vector<vector<int>> predecessors;
void resize(int layers, int nodes_per_layer) {
nodes.resize(layers, vector<Node>(nodes_per_layer));
costs.resize(layers, vector<double>(nodes_per_layer, INFINITY));
predecessors.resize(layers, vector<int>(nodes_per_layer, -1));
}
};
5.2 并行化策略
路径规划的各层计算天然适合并行化:
cpp复制#pragma omp parallel for
for(int layer=1; layer<layers; ++layer) {
for(int curr=0; curr<nodes_per_layer; ++curr) {
// 更新当前节点代价
}
}
6. 实际部署中的经验教训
-
数值稳定性问题:
- 五次多项式系数求解可能病态,需要正则化
- 代价函数值应做归一化处理
-
实时性保障:
- 设置超时机制(如50ms强制返回当前最优解)
- 动态调整网格分辨率(远处用粗网格)
-
特殊场景处理:
- 对静止障碍物建立"安全通道"缓存
- 极端情况下允许违反部分舒适性约束
-
调试可视化:
- 实时显示SL/ST图和代价热力图
- 记录规划过程的快照用于事后分析
7. 与Apollo框架的差异点
虽然参考了Apollo的EM Planner,但我们的实现有几个关键改进:
- 采用更紧凑的网格数据结构,内存占用减少40%
- 引入自适应网格细化(重点关注障碍物附近区域)
- 速度规划中增加了急刹车优先策略
- 提供Python绑定方便算法快速验证
8. 扩展方向与实践建议
对于想进一步优化的开发者,建议考虑:
-
混合A*改进:
- 在DP粗解基础上进行A*精细化搜索
- 结合RRT*的随机采样特性
-
机器学习辅助:
- 用NN预测代价函数权重
- 强化学习优化网格分辨率
-
硬件加速:
- 使用GPU并行计算代价矩阵
- FPGA实现多项式求值流水线
在实际车辆测试中,这套系统表现出色:在复杂城区场景下,规划成功率达到99.7%,平均计算时间控制在30ms以内。最难能可贵的是,即使在传感器短暂失效的情况下,基于DP的规划器仍能生成保守但安全的应急轨迹。
