1. 项目概述
最近完成了一个基于二次规划(Quadratic Programming, QP)的路径规划与速度规划项目,从MATLAB原型开发到C++工程实现,再到QT可视化界面的完整流程。这个项目最初是为了解决自动驾驶系统中的轨迹优化问题,但其中的方法同样适用于机器人导航、工业自动化等需要运动规划的领域。
在项目初期,我面临的主要挑战是如何将理论上的QP算法转化为实际可用的代码实现。虽然QP在理论上很优美,但真正落地时会遇到各种工程细节问题,比如约束条件的数学表达、求解器的选择与调优、计算效率的优化等。经过两个月的迭代开发,最终实现了从算法验证到工程部署的完整闭环。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 核心算法原理
2.1 二次规划基础
二次规划是优化问题的一个重要子类,其标准形式为:
minimize (1/2)xᵀQx + cᵀx
subject to Ax ≤ b
Ex = d
其中:
- x是决策变量(在路径规划中代表路径点位置或速度)
- Q是正定矩阵,决定了目标函数的二次项
- c是线性项系数向量
- A和E定义了不等式和等式约束矩阵
- b和d是对应的约束边界值
在运动规划中,QP特别适合解决以下两类问题:
- 路径平滑:通过最小化曲率变化实现舒适性
- 动态约束:处理速度、加速度等物理限制
2.2 分段加加速度(Piecewise Jerk)模型
分段加加速度模型是本项目采用的核心方法,它将运动轨迹划分为若干小段,在每段内保持加加速度(jerk)恒定。这种方法相比传统的多项式插值具有以下优势:
- 计算效率高:每段只需存储少量参数
- 约束表达简单:加速度变化率直接对应jerk值
- 物理意义明确:符合车辆动力学特性
数学表达上,对于第i个路径点:
- 位置:sᵢ
- 速度:vᵢ = (sᵢ - sᵢ₋₁)/Δt
- 加速度:aᵢ = (vᵢ - vᵢ₋₁)/Δt
- 加加速度:jᵢ = (aᵢ - aᵢ₋₁)/Δt
通过约束jᵢ的大小,可以间接控制运动的平滑性。
3. MATLAB实现详解
3.1 开发环境配置
推荐使用MATLAB R2020b及以上版本,主要依赖以下工具箱:
- Optimization Toolbox(提供quadprog求解器)
- Robotics System Toolbox(可选,用于高级可视化)
matlab复制% 检查工具箱是否安装
if ~license('test', 'Optimization_Toolbox')
error('需要安装Optimization Toolbox');
end
3.2 路径规划实现
3.2.1 问题建模
考虑n个路径点的规划问题,定义决策变量x ∈ ℝⁿ表示各点位置。目标函数包含:
-
平滑性项:最小化相邻点位置差
J_smooth = Σ(xᵢ - xᵢ₋₁)² -
参考线跟踪:最小化与参考路径的偏差
J_ref = Σ(xᵢ - x_refᵢ)²
组合后的目标函数:
min w₁J_smooth + w₂J_ref
对应的QP形式:
Q = w₁L + w₂I
c = -2w₂x_ref
其中L是离散Laplacian矩阵。
3.2.2 约束条件处理
常见的路径约束包括:
- 边界约束:x_min ≤ xᵢ ≤ x_max
- 障碍物约束:|xᵢ - oⱼ| ≥ rⱼ (oⱼ为障碍物位置,rⱼ为安全半径)
在MATLAB中可通过A、b矩阵表达:
matlab复制% 边界约束
A_bound = [eye(n); -eye(n)];
b_bound = [x_max*ones(n,1); -x_min*ones(n,1)];
% 障碍物约束(以单个圆形障碍物为例)
A_obs = zeros(2n, n);
for i = 1:n
A_obs(i,i) = 1;
A_obs(n+i,i) = -1;
b_obs(i) = o_x - r;
b_obs(n+i) = -(o_x + r);
end
% 合并约束
A = [A_bound; A_obs];
b = [b_bound; b_obs];
3.2.3 求解与验证
使用quadprog求解:
matlab复制options = optimoptions('quadprog', 'Algorithm', 'interior-point-convex',...
'Display', 'iter');
[x_opt, fval, exitflag] = quadprog(Q, c, A, b, [], [], [], [], [], options);
if exitflag == 1
disp('优化成功');
plot(x_opt, 'LineWidth', 2);
else
error('优化失败');
end
3.3 速度规划实现
速度规划采用类似方法,但决策变量变为速度v ∈ ℝⁿ。关键区别在于:
- 目标函数需考虑加速度和jerk限制
- 约束条件包含车辆动力学限制(最大加速度、制动距离等)
matlab复制% 构建速度平滑项(最小化加速度变化)
Q_v = zeros(n);
for i = 2:n-1
Q_v(i,i-1:i+1) = [1 -2 1];
end
Q_v = Q_v' * Q_v;
% 添加终点速度约束
Aeq = zeros(1,n);
Aeq(1,end) = 1;
beq = v_target;
v_opt = quadprog(Q_v, [], [], [], Aeq, beq, v_min, v_max);
4. C++工程化实现
4.1 技术选型考量
从MATLAB迁移到C++主要基于以下考虑:
- 实时性要求:MATLAB解释执行效率不足
- 部署需求:需要独立可执行文件
- 内存控制:精细化管理计算资源
选用的核心库:
- Eigen 3.4:线性代数运算
- OSQP 0.6.2:专为QP优化的求解器
- Qt 5.15:可视化界面
4.2 核心算法移植
4.2.1 问题构建
使用Eigen表达QP参数:
cpp复制#include <Eigen/Sparse>
// 构建稀疏Q矩阵
Eigen::SparseMatrix<double> Q(n, n);
std::vector<Eigen::Triplet<double>> triplets;
for(int i=1; i<n-1; ++i) {
triplets.emplace_back(i,i, 2.0);
triplets.emplace_back(i,i-1, -1.0);
triplets.emplace_back(i,i+1, -1.0);
}
Q.setFromTriplets(triplets.begin(), triplets.end());
// 约束条件
Eigen::SparseMatrix<double> A(2*n, n);
// ...类似MATLAB的逻辑填充A...
Eigen::VectorXd lb = /* 下界 */, ub = /* 上界 */;
4.2.2 OSQP求解器集成
cpp复制#include <osqp/osqp.h>
// 转换为OSQP格式
OSQPData* data = (OSQPData*)c_malloc(sizeof(OSQPData));
data->n = n;
data->m = 2*n;
data->P = /* Q矩阵的CSC格式 */;
data->A = /* A矩阵的CSC格式 */;
// ...其他参数设置...
OSQPSettings* settings = (OSQPSettings*)c_malloc(sizeof(OSQPSettings));
osqp_set_default_settings(settings);
settings->eps_abs = 1e-5;
settings->eps_rel = 1e-5;
OSQPWorkspace* work = osqp_setup(data, settings);
osqp_solve(work);
// 提取结果
Eigen::VectorXd solution = Eigen::Map<Eigen::VectorXd>(work->solution->x, n);
4.3 性能优化技巧
-
稀疏矩阵优化:
- 使用Eigen::SparseMatrix存储Q、A
- 预先分配非零元素数量
cpp复制Q.reserve(Eigen::VectorXi::Constant(n, 3)); // 每行约3个非零元 -
热启动:
cpp复制if(previous_solution.size() == n) { osqp_warm_start(work, previous_solution.data(), nullptr); } -
并行计算:
- 使用OpenMP加速矩阵构建
cpp复制#pragma omp parallel for for(int i=0; i<n; ++i) { // 并行填充矩阵元素 }
5. Qt可视化实现
5.1 界面架构设计
采用Model-View-Controller模式:
- Model:存储路径/速度数据
- View:QCustomPlot实现的绘图区域
- Controller:处理用户交互
code复制MainWindow
├── PathPlotWidget : QCustomPlot
├── SpeedPlotWidget : QCustomPlot
└── ControlPanel : QWidget
├── QSlider // 参数调节
└── QPushButton // 重新计算
5.2 关键功能实现
5.2.1 实时绘图
cpp复制// 在QCustomPlot子类中
void PathPlotWidget::updatePath(const QVector<double>& x,
const QVector<double>& y) {
graph(0)->setData(x, y);
rescaleAxes();
replot();
}
5.2.2 交互功能
cpp复制// 鼠标拖动障碍物
void PathPlotWidget::mouseMoveEvent(QMouseEvent* event) {
if(dragging_obstacle) {
obstacle_pos = event->pos();
emit obstacleMoved(obstacle_pos);
}
}
// 连接信号槽
connect(this, &PathPlotWidget::obstacleMoved,
controller, &Controller::recalculatePath);
5.3 性能优化
-
双缓冲绘图:
cpp复制QCustomPlot::setOpenGl(true); // 启用GPU加速 -
数据异步更新:
cpp复制// 在工作线程计算 void WorkerThread::run() { while(!stopped) { auto result = calculate(); emit resultReady(result); // 信号触发UI更新 } }
6. 工程实践中的挑战与解决方案
6.1 数值稳定性问题
现象:当路径点超过100个时,OSQP偶尔会返回非正定错误。
解决方案:
- 添加正则化项:
cpp复制Q += 1e-8 * Eigen::MatrixXd::Identity(n,n); - 改用更稳定的求解器(如QPALM)
- 实施标度变换(Scaling):
cpp复制Eigen::VectorXd scale = /* 计算各变量的量级 */; Q = Q.cwiseProduct(scale * scale.transpose());
6.2 实时性保障
测试数据:在Intel i7-1185G7上:
- 50个路径点:~2ms
- 200个路径点:~15ms
优化手段:
- 固定维度分配:预分配最大可能的问题尺寸
cpp复制#define MAX_N 500 Eigen::Matrix<double, MAX_N, 1> x; - 使用内存池管理临时变量
- 启用AVX2指令集:
cmake复制add_compile_options(-mavx2)
6.3 多坐标系处理
实际车辆需要同时处理:
- 世界坐标系(全局路径)
- 车身坐标系(局部避障)
实现方案:
cpp复制struct TrajectoryPoint {
Eigen::Vector2d global_pos;
double heading; // 航向角
double curvature; // 曲率
Eigen::Vector2d toLocal(const Eigen::Vector2d& global) const {
Eigen::Matrix2d R;
R << cos(heading), sin(heading),
-sin(heading), cos(heading);
return R * (global - global_pos);
}
};
7. 扩展应用与未来改进
7.1 典型应用场景
-
自动泊车:
- 多段路径拼接
- 考虑转向角约束
-
高速公路巡航:
- 长距离规划(>1km)
- 交通流预测集成
-
越野路径规划:
- 地形高度约束
- 附着系数估计
7.2 待优化方向
-
混合整数QP:
- 处理离散决策(如换道)
- 使用SCIP或Gurobi求解器
-
机器学习集成:
python复制# 示例:使用神经网络预测QP权重 model = tf.keras.Sequential([ layers.Dense(64, activation='relu'), layers.Dense(3) # 输出w_smooth, w_ref, w_obstacle ]) -
不确定性处理:
- 鲁棒QP公式
math复制min max_{Δ∈U} xᵀ(Q+Δ)x + cᵀx
8. 项目部署建议
8.1 跨平台构建
使用CMake管理依赖:
cmake复制find_package(Eigen3 REQUIRED)
find_package(OSQP REQUIRED)
find_package(Qt5 COMPONENTS Core Widgets REQUIRED)
add_executable(qp_planner
src/main.cpp
src/planner.cpp)
target_link_libraries(qp_planner
Eigen3::Eigen
OSQP::OSQP
Qt5::Core Qt5::Widgets)
8.2 性能监控
集成实时指标上报:
cpp复制class PerformanceMonitor {
public:
void startFrame() {
start_time = std::chrono::high_resolution_clock::now();
}
void endFrame() {
auto end = std::chrono::high_resolution_clock::now();
auto duration = std::chrono::duration_cast<std::chrono::microseconds>(end - start_time);
emit frameTimeUpdated(duration.count());
}
private:
std::chrono::time_point start_time;
};
8.3 安全考量
-
输入校验:
cpp复制if(path.size() != speed.size()) { throw std::invalid_argument("路径与速度维度不匹配"); } -
故障恢复:
- 备用求解器(如迭代法)
- 最后有效路径缓存
在实际部署中,我们还需要考虑:
- 内存安全(避免动态分配)
- 实时保障(限制最大计算时间)
- 数值边界检查(预防溢出)
