1. 自动驾驶路径规划算法概述
路径规划是自动驾驶系统的核心模块之一,它负责在复杂环境中生成一条安全、舒适的行驶轨迹。EMPlanner(Expectation-Maximization Planner)是一种经典的路径规划算法框架,它通过动态规划(DP)和二次规划(QP)两个阶段的协同工作来解决这个问题。
1.1 EMPlanner的基本原理
EMPlanner的核心思想是将路径规划问题分解为两个层次:
-
动态规划阶段:在粗粒度下探索可能的路径空间,生成初始的参考路径。这个阶段主要考虑障碍物避障和全局路径可行性。
-
二次规划阶段:在动态规划结果的基础上进行精细化调整,生成平滑、舒适的最终路径。这个阶段主要考虑车辆动力学约束和乘坐舒适性。
这种两阶段设计既保证了算法的全局搜索能力(通过DP),又确保了局部路径的质量(通过QP),是工程实践中被广泛采用的方案。
1.2 算法实现的技术栈
本实现采用C++作为开发语言,主要依赖以下第三方库:
- Eigen:用于矩阵运算,特别是QP阶段的海森矩阵和约束矩阵的构建
- OSQP:一个高效的二次规划求解器,用于QP阶段的优化计算
- matplotlib-cpp:用于结果可视化,方便调试和算法验证
注意:matplotlib-cpp需要Python 2.7环境支持,主要用于绘制路径和障碍物的可视化结果。在实际车辆部署时,这部分可视化代码可以移除。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 动态规划路径搜索实现
2.1 数据结构设计
首先定义路径和障碍物的基本数据结构:
cpp复制// DP路径结构体
struct DpPath {
std::vector<double> s; // 纵向距离坐标
std::vector<double> l; // 横向偏移坐标
double total_cost = 0.0; // 路径总成本
std::vector<int> parent_index; // 用于路径回溯的父节点索引
};
// 障碍物表示
struct Obstacle {
double s; // 纵向位置(Frenet坐标)
double l; // 横向位置(Frenet坐标)
double radius; // 安全膨胀半径
int id; // 障碍物唯一标识
};
这里采用Frenet坐标系(s,l)表示位置信息,其中s代表沿参考线的纵向距离,l代表横向偏移。这种表示方法比笛卡尔坐标系更适合道路场景的路径规划。
2.2 动态规划核心算法
动态规划的实现主要包括状态转移和代价计算两个部分:
cpp复制DpPath dynamicProgramming(const std::vector<Obstacle>& obstacles,
double delta_s = 1.0, double delta_l = 0.5) {
const int N = 50; // 路径点数量
const int L_GRID_SIZE = 5; // 横向离散化网格数
// 初始化代价矩阵
std::vector<std::vector<double>> cost(N, std::vector<double>(L_GRID_SIZE, INFINITY));
std::vector<std::vector<int>> parent(N, std::vector<int>(L_GRID_SIZE, -1));
// 起点初始化(居中位置)
cost[0][L_GRID_SIZE/2] = 0.0;
// 状态转移
for (int i = 0; i < N-1; ++i) {
for (int dl = 0; dl < L_GRID_SIZE; ++dl) {
if (cost[i][dl] == INFINITY) continue;
double current_l = (dl - L_GRID_SIZE/2) * delta_l;
// 考虑三种可能的横向变化:左移、保持、右移
for (int next_dl_diff : {-1, 0, 1}) {
int next_dl = dl + next_dl_diff;
if (next_dl < 0 || next_dl >= L_GRID_SIZE) continue;
