自动泊车这个方向,我前前后后折腾了不少时间。最开始是帮某位做车辆控制的朋友啃论文,他给了一篇基于EKF和MPC的自动泊车文献,让我用MATLAB把整套逻辑复现出来。当时觉得不就是倒车入库嘛,真做起来才发现里面的坑一个接一个:EKF的雅可比矩阵手推错一位,MPC求解器在低速大转角下直接失控,还有仿真动画里车辆姿态配不上车位坐标……好在最后都逐个解决了。这篇就当作一份工程笔记,把EKF状态估计、MPC轨迹跟踪、MATLAB场景建模这几块完整拆开讲清楚,特别是那些论文里不会写、但实际跑仿真一定会遇到的细节。
如果有人正准备做自动泊车方向的项目,或者想复现某个横向控制论文里的EKF+MPC架构,这篇文章应该能帮你省下不少试错时间。我会按项目拆解的顺序来讲:先说明为什么选这对组合,再讲车辆模型和场景怎么建,接着是EKF的推导细节和MPC的求解实现,最后给出完整的MATLAB工程结构和排查手册。
1. 项目背景与整体框架拆解
1.1 项目要解决什么,适合哪些人
自动泊车本质上是低速下的小范围路径规划与跟踪控制问题。车辆要从一个起始位置,在有限的车位空间里,经过一系列前进和倒车操作,最终把车身停在目标车位内,并且姿态角要满足要求。听起来简单,但有两个硬约束:一是泊车空间通常很窄,控制误差稍大就可能剐蹭;二是低速倒车时车辆运动学表现出强烈的非线性和非完整约束特征,不是你给它一个转角它就会乖乖转过去的。
这个项目里,MPC负责解决“怎么打方向盘”的问题,EKF负责解决“车辆到底在哪里”的问题。前者是控制核心,后者是感知层的关键。整个MATLAB工程的目标就是:建立一个可以反复修改场景参数、传感器噪声等级和控制权重的仿真环境,把论文中的算法跑通,并输出可量化的跟踪误差和泊车结果。
这个项目非常适合三类人:第一类是刚进入智能车辆控制领域的研究生,想通过一个完整例子把EKF和MPC串起来;第二类是工作中需要做泊车辅助功能开发的工程师,想先做算法验证再去碰实车;第三类是自学控制理论但感觉知识碎片化的同学,这个项目能帮你把状态估计、运动学模型、优化控制、传感器噪声这些概念揉进一个具体场景里。
1.2 为什么是EKF和MPC这对组合
先看控制端。泊车场景下车辆速度低、转角大、轨迹多变,PID这种线性控制器很难兼顾不同曲率段的跟踪精度。而MPC的核心优势是带约束的预测优化——它会把未来一段时域内的控制量全部算出来,并且把方向盘转角限制、转角变化率限制、车位边界约束都放进优化问题里。这非常贴合泊车的实际需求:你不可能为了让车快点到位就让方向盘瞬间打满,也不希望车辆顶着车位边界修正。
再看估计端。EKF用于融合轮速、前轮转角、车身坐标等传感器信息,并输出一个平滑的车辆状态估计值。之所以用EKF而不是经典KF,是因为车辆运动学方程里包含正弦余弦项,是明显的非线性系统。KF要求系统是线性高斯模型,硬套的话精度会崩。EKF的做法是把非线性函数在当前状态附近做一阶泰勒展开,得到线性化模型后再走KF的预测更新流程。
EKF和MPC组合起来,实际上形成了“感知—规划—控制”闭环:EKF把有噪声的传感器数据变成相对干净的状态流,MPC基于这个状态流计算方向盘转角指令,车辆执行后又产生新的观测数据进入EKF。整个工程就是一个完整的自动泊车算法原型。
1.3 论文复现的一般套路和本项目的复现策略
论文复现不是照着公式敲代码那么简单。我通常按四步走:第一步,读清楚论文的系统结构图和数学模型,列出所有状态量、输入量、参数和约束;第二步,先在理想环境下实现MPC控制,把车辆模型、参考轨迹和控制算法跑通;第三步,引入传感器噪声,加入EKF进行状态估计替换理想状态;第四步,调参数、加不同场景验证泛化能力。
这个项目按同样的思路组织。先建立一个纯几何的参考泊车路径,再实现MPC跟踪这条路径,然后再加EKF处理带噪声的观测。每一层都是在前一层基础上叠加,出问题的时候定位范围会小很多。如果直接一步到位写一个超大脚本,出了bug根本不知道是模型问题、估计问题还是优化问题。
2. 车辆运动学模型与场景搭建
2.1 自行车模型推导与低速泊车适配
自动泊车项目中,最常用的是自行车模型,也叫单轨模型。它把车辆前后轴各看成一条“腿”,左右轮合并成一个虚拟轮,用几何关系描述车辆的位置、航向角、前轮转角之间的运动约束。
对于低速泊车,忽略轮胎侧偏和纵向加速度的影响,运动学模型可以写成:
dx/dt = v * cos(theta + beta) dy/dt = v * sin(theta + beta) d(theta)/dt = v / L * (cos(beta) * tan(delta))其中x, y是后轴中心坐标,theta是航向角,delta是前轮转角,L是轴距,beta是质心侧偏角。如果以后轴中心作为参考点,且认为质心不在前后轴中间的特殊场景下,beta可以忽略,模型简化为:
dx/dt = v * cos(theta) dy/dt = v * sin(theta) d(theta)/dt = v / L * tan(delta)这里必须注意:控制量是前轮转角delta和车速v,但对于自动泊车,速度通常由一个低层速度控制器执行,我们的MPC只输出转角指令,速度按预设的低速曲线给定。这样能降低MPC问题的复杂度,也更符合实际泊车系统的分层架构——上层规划路径,中层控制转角,底层调速。
2.2 泊车场景坐标设定与车位建模
我在MATLAB里建的场景是一个垂直车位。定义车位大小为宽度2.4米、长度5.2米,车道宽度5.5米。这是比较标准的轿车垂直泊车尺寸。坐标原点设在地图左上角,y轴向下或者向上都可以,但建议保持车辆运动学推导的坐标系一致性,即x向右、y向上,航向角从x轴正方向逆时针计。
车位由四个角点定义:
车位_far_left = [-5.0, 1.2] 车位_far_right = [-2.6, 1.2] 车位_near_left = [-5.0, -1.2] 车位_near_right = [-2.6, -1.2]这样设置之后,目标停车位中心大约在(-3.8, 0),车辆最终应以后轴中心靠近车位中心偏前一点,车身完全落在车位矩形内。参考路径设计为一条多段曲线,包含直线段、圆弧段和反向圆弧段,模拟一次先向右打、再向左回的倒车入库轨迹。
生成参考路径时我常用圆-直线-圆组合,再通过几何计算求出每段圆弧的圆心和半径。这里有一个实用技巧:为了MPC跟踪稳定,参考路径应该提供每个点的位置(x_ref, y_ref)和航向角theta_ref。航向角不能只靠相邻点差分求,而要根据圆弧几何严格计算,否则MPC在圆弧段会出现跟踪滞后。
2.3 传感器观测建模与噪声注入
EKF需要观测数据,在实际系统中可能来自毫米波雷达、超声波传感器、视觉SLAM,但在仿真里我直接假设可以观测车辆的位置和航向角,并叠加高斯白噪声。观测模型写为:
z_x = x + w_x z_y = y + w_y z_theta = theta + w_theta噪声标准差设置为别太大也别太小。位置噪声标准差给0.05米,航向噪声标准差给0.02弧度。这个量级接近比较好的传感器融合结果。如果噪声设置过大,EKF也很难把状态滤干净;如果过小,则体现不出EKF的作用。仿真中最好做成可配置参数,方便对比不同噪声水平下MPC的跟踪表现。
3. EKF状态估计核心细节
3.1 为什么用EKF而不是KF,线性化到底做了什么
经典KF的适用条件要求系统模型和观测模型都是线性的,也就是状态转移可以写成x_k = A x_{k-1} + B u的形式。但车辆运动学模型里有cos(theta)和sin(theta),还有tan(delta)这种非线性项,硬要线性化到全局是不现实的。EKF的思路是:在当前估计状态附近做局部线性化,拿到一个随时间变化的线性近似,然后套用KF的更新公式。
换句话说,每一时间步,EKF计算雅可比矩阵F和H,用它们替代KF中的常值矩阵A和H。这个操作相当于把非线性函数在当前点“掰直”了再做高斯传播,虽然会引入一定线性化误差,但对于泊车这种低速场景,采样时间短、模型变化平缓,误差完全可以接受。
3.2 状态转移与雅可比矩阵推导完整过程
状态向量定义为[x, y, theta],控制输入为[v, delta],离散化使用欧拉法,采样间隔dt = 0.1秒。
x_k = x_{k-1} + v * dt * cos(theta_{k-1}) y_k = y_{k-1} + v * dt * sin(theta_{k-1}) theta_k = theta_{k-1} + v * dt / L * tan(delta)对状态向量求偏导得到雅可比矩阵F:
F = [1, 0, -v*dt*sin(theta); 0, 1, v*dt*cos(theta); 0, 0, 1]注意这里把theta视作上一步已知值代入。控制输入对状态的影响矩阵G:
G = [dt*cos(theta), 0; dt*sin(theta), 0; v*dt/(L*cos(delta)^2), dt*tan(delta)/L]不过在标准EKF流程中,预测协方差时实际上用到的是F和过程噪声矩阵Q,控制输入的雅可比G主要用于当噪声耦合到控制输入时的交叉协方差项。我通常把控制噪声并入过程噪声中,这样简化后预测协方差为:
P_pred = F * P * F' + QQ的取值根据经验来,位置过程噪声方差设0.01^2,航向过程噪声方差设0.005^2。这一步很多人会忽略F里的第1行第3列、第2行第3列这两个非零项。如果把它当成单位矩阵,EKF就退化成了“只靠观测更新,完全没有运动模型传播”的状态,估计结果会非常抖。
观测矩阵H因为是直接观测[x, y, theta],所以是 3x3 单位矩阵。观测噪声协方差矩阵R由传感器噪声决定:
R = diag([0.05^2, 0.05^2, (0.02)^2])3.3 协方差调参与状态发散处理
协方差调参是EKF落地中最不优雅但最现实的部分。一个容易被忽视的陷阱:过程噪声Q设得太大,滤波器会更信任观测,但状态估计噪声大;设得太小,滤波器会“迷信”模型,当模型误差或未建模扰动存在时,估计值会长时间偏离真实值,甚至发散。
我调试时,先固定Q和R,跑一个理想仿真看估计误差的稳态行为。如果误差均值不为零,说明模型偏置未消除,需要调初始协方差P0或检查模型离散化是否有误。如果误差振荡很大,说明Q/R比值过小,应该增大Q或者减小R。
实践经验:P0可以设成diag([1, 1, 0.1]),表示初始置信度较低,滤波器会更快接受观测。后面追踪稳定后P会收敛到某个值。我在实际调试中还遇到过theta估计误差在正负180度跳变的问题,这是因为航向角周期性和线性化导致的。解决办法是把航向角差值映射到[-pi, pi]范围,在做EKF更新前先计算残差并归一化。
4. MPC控制器设计与实现
4.1 MPC在泊车场景中的角色和优势
MPC的全称是模型预测控制,核心含义就是“想三步再走一步”。每一控制周期,MPC求解器根据当前车辆状态和参考轨迹,预测未来N步的系统响应,并找出一组最优控制序列,使得预测轨迹和参考轨迹误差最小,同时满足约束。求解后只执行第一步控制量,下个周期重新滚动优化。
泊车场景里MPC的优势特别明显。泊车路径曲率变化剧烈,方向盘需要频繁正打和回打。MPC可以在优化问题里加入对前轮转角delta及其变化率d_delta的约束,避免出现方向盘猛打猛回的情况。同时车位边界约束也能写成状态约束,让车辆在优化的每一步都不会超出安全区域。
4.2 预测模型离散化与增量式控制
我使用的是增量式MPC,也就是把控制增量delta_u = u_k - u_{k-1}作为决策变量。这样做的好处是天然可以把控制量的变化率约束放进去,优化出的转角曲线更平滑,也更容易实现零稳态误差。
先将连续运动学模型在每一工作点线性化,得到误差模型:
state = [x, y, theta, delta] control_increment = [v_inc, delta_inc]实际工程中我常把车速视为恒定测量值,只把转角增量作为唯一的决策量,这样预测模型进一步简化,但依然能满足泊车场景的要求。预测模型写成:
x(k+1) = x(k) + v * dt * cos(theta(k)) y(k+1) = y(k) + v * dt * sin(theta(k)) theta(k+1) = theta(k) + v * dt * tan(delta(k)) / L delta(k+1) = delta(k) + delta_inc(k)这是一个以delta_inc为输入、以[x, y, theta, delta]为状态的预测模型。MPC在每个时间步内,基于当前测量状态和参考序列,通过数值积分递推预测未来状态。因为泊车速度很低,我直接用欧拉离散就够用,不需要更复杂的离散化方法。
4.3 约束设计与安全边界的转化
约束分三类:控制量约束、控制增量约束、状态输出约束。
控制量约束就是方向盘转角限制。乘用车前轮转角一般限制在[-0.5, 0.5]弧度,大约正负28度。控制增量约束限制每个控制周期内转角变化幅度,我会设delta_inc_max = 0.05弧度,这样方向盘不会瞬间打满。
状态约束中,最重要的是位置约束。车位边界在MPC预测时域内都需要纳进来。比如车辆后轴中心位置应该保持在后轴边界包络内,但更实用的是把整个车身简化成矩形并计算四个顶点的坐标,约束顶点位置在车位边界内。不过对于MPC优化求解来说,直接约束矩形顶点会引入非线性,不是特别好处理。一个折中方案是约束后轴中心点位置在车位中线的安全走廊内,同时保证航向角落在合理范围。这样做虽然牺牲了部分精度,但MPC求解速度和稳定性大幅提升。
4.4 权重矩阵与预测时域参数选择
MPC成本函数通常包括四部分:输出误差、控制量惩罚、控制增量惩罚、松弛变量惩罚。写成矩阵形式:
J = sum_{i=1}^{Np} |y_ref(k+i) - y_pred(k+i)|^2_Q + sum_{i=0}^{Nc-1} |delta_inc(k+i)|^2_R_inc + sum_{i=0}^{Nc-1} |delta(k+i)|^2_R + rho * epsilon^2Q是对输出误差的权重,我设diag([20, 20, 10])分别对应x、y、theta。因为泊车对位置误差更敏感,航向角误差可以通过后续倒车修正,所以位置权重更高。R_inc是控制增量权重,设0.1,避免转角序列过激。R是控制量权重,设0.05。松弛变量权重rho设1000,这样约束在正常情况下不会被违反,只在极端情况下松弛以保证优化问题有解。
预测时域Np设30,控制时域Nc设10,采样周期0.1秒,预测总长3秒。对泊车来说,车辆速度0.5米每秒左右,3秒内车辆最多走1.5米,足够覆盖一整个倒车修正过程的曲率变化。如果Np太小,MPC看不到远处的弯道,会反应迟钝;如果太大,计算代价增加,而且对于非线性系统数值误差可能累积。我实际测试下来30步是个不错的平衡点。
5. MATLAB工程实现全过程
5.1 工程目录结构与文件职责
项目代码我从不写成一个大脚本,而是按模块拆分。一个可复现的MATLAB工程目录大致如下:
parking_ekf_mpc/ ├── main_script.m ├── init_params.m ├── generate_reference_path.m ├── vehicle_model.m ├── ekf_predict_update.m ├── mpc_controller.m ├── simulate_one_step.m └── plot_results.minit_params.m集中定义所有参数,包括车位尺寸、车辆尺寸、采样时间、噪声强度、MPC权重等。generate_reference_path.m根据车位尺寸和起始位置生成参考轨迹点序列。vehicle_model.m是车辆运动学的单纯形函数,接收状态和控制量,返回下一时刻状态。ekf_predict_update.m执行EKF预测和更新。mpc_controller.m构建并求解MPC优化问题。simulate_one_step.m把上述模块串成一整个闭环。plot_results.m负责可视化。
5.2 核心函数实现要点
先看EKF函数。函数签名可以这样写:
function [x_est, P_next] = ekf_predict_update(x_prev, P_prev, u, z, dt, params) % 预测 theta = x_prev(3); v = u(1); delta = u(2); L = params.L; x_pred = x_prev + [v*dt*cos(theta); v*dt*sin(theta); v*dt*tan(delta)/L]; F = [1, 0, -v*dt*sin(theta); 0, 1, v*dt*cos(theta); 0, 0, 1]; Q = params.Q; P_pred = F * P_prev * F' + Q; % 更新 H = eye(3); R = params.R; y_res = z - x_pred; y_res(3) = wrapToPi(y_res(3)); S = H * P_pred * H' + R; K = P_pred * H' / S; x_est = x_pred + K * y_res; P_next = (eye(3) - K * H) * P_pred; end这里注意航向角残差用了wrapToPi,这是EKF航向角收敛的关键。如果不归一化,当真实theta接近pi、估计theta接近-pi时,残差会错误地变成约2pi,更新方向完全错误。
MPC控制器部分,我用MATLAB的优化工具箱,通过fmincon求解非线性MPC。虽然非线性MPC理论上成本更高,但对于只有几个状态和几十个时域的泊车问题,fmincon完全跑得动。核心设置如下:
function u_opt = mpc_controller(x_measure, ref_traj, u_prev, params) Np = params.Np; Nc = params.Nc; state_dim = 4; U0 = zeros(Nc, 1); % 定义优化变量为控制增量序列 prob = optimproblem; dU = optimvar('dU', Nc); % 递推预测函数 ... % 成本与约束 ... sol = solve(prob, x0); u_opt = u_prev + sol.dU(1); end实际编程时,我不建议在优化函数内部用for循环递推整个预测时域,那样非常慢且无法用自动微分做梯度。更好的做法是把未来状态递推写成for循环但约束和成本都用符号变量表示。MATLABoptimproblem对非线性目标函数的支持还可以,虽然比不过专用MPC工具箱,但胜在代码透明、易修改。
5.3 仿真循环与参数配置实例
主仿真循环大概如下:
for k = 1:sim_steps % EKF估计 z = x_true + noise; [x_est, P] = ekf_predict_update(x_est, P, u_opt, z, dt, params); % MPC控制 u_opt = mpc_controller(x_est, ref_traj(k:k+Np), u_prev, params); % 车辆运动学更新,使用真实状态加控制误差 x_true = vehicle_model(x_true, u_opt + control_noise, dt); u_prev = u_opt; % 记录数据 ... end一个关键细节:MPC使用的是EKF后的估计状态,而不是真实状态。这是闭环仿真的重点。如果直接用真实状态给MPC,等于跳过了EKF,那EKF就白做了。实际噪声注入要在观测z上做,控制误差在控制量上做,这样才能模拟传感器噪声和执行器误差对系统的影响。
参数配置我贴一个实际运行稳定的组合:
| 参数 | 值 | 说明 |
|---|---|---|
| 采样时间 dt | 0.1s | 控制周期 |
| 预测时域 Np | 30 | 3秒预测 |
| 控制时域 Nc | 10 | 1秒控制序列 |
| 车速 v | 0.5 m/s | 倒车速度恒定 |
| 轴距 L | 2.8 m | 中型车 |
| 前轮转角范围 | ±0.5 rad | 物理限制 |
| 转角增量限制 | ±0.05 rad | 舵机响应限制 |
| 位置噪声标准差 | 0.05 m | 传感器噪声 |
| 航向噪声标准差 | 0.02 rad | 传感器噪声 |
5.4 可视化与动画调试的小技巧
泊车仿真最直观的验证方式是动画。用plot画车位矩形、参考路径、车辆矩形轮廓和航向箭头,每一帧更新。车辆轮廓要描述为一个矩形,根据航向角旋转四个顶点。我习惯把车身长度按轴距加前后悬来画,这样车身比只画后轴点更真实。
调试时多亏了动画,我才发现一个隐蔽问题:车辆在倒车时,后轴中心轨迹虽然基本贴合参考路径,但车头甩出车位边界,就是因为MPC只约束了后轴中心点。后来我把车身四个顶点实时绘制出来,才意识到需要额外约束车辆轮廓投影。解决方式是在成本函数中加入车辆顶点偏离安全区域的惩罚项,或直接在位置约束中预留车头车尾的偏移量。
6. 常见问题与排查实录
6.1 问题速查表
| 现象 | 可能原因 | 解决思路 |
|---|---|---|
| EKF估计结果震荡 | Q太小或R太大 | 适当增大Q,减小R |
| EKF完全跟随测量噪声 | 运动模型失效或F错误 | 检查F中关于theta的偏导项,确认速度方向 |
| 航向角在±180度跳变 | 残差未归一化 | 对航向残差调用wrapToPi |
| MPC跟踪直线段正常,圆弧段大幅偏差 | 参考航向角生成有误 | 检查圆弧上航向角是否按切线方向设置 |
| MPC控制量一直在边界上抖动 | 控制增量权重过小 | 增大R_inc,减小最大增量 |
| 求解时间过长,实时性不足 | 预测时域过大或非线性过强 | 减小Np,或把车辆模型在参考点附近线性化 |
| 车辆倒车最终姿态偏离车位 | 位置权重过低或轨迹参考点不密 | 提高Q中位置权重,插值参考路径 |
| 优化无解,报错“不可行” | 约束过紧或松弛变量未加 | 加入松弛变量并设置较高惩罚权重 |
| 动画中车辆轮廓与车位错位 | 坐标基准不一致 | 统一用后轴中心点定义车辆位置与车位位置 |
6.2 一个典型排查过程:MPC在圆弧段突然失控
我复现时碰到过一件事:参考路径前半段是直线,车辆跟踪得很好;进入圆弧段后,车辆像被弹开一样冲出边界。排查第一步,我打印了MPC每一步的控制量和预测轨迹,发现预测轨迹在圆弧段到不了参考点,且参考航向角存在跳变。检查发现,参考路径生成时,我在圆弧段只是对(x, y)做了插值,航向角是直接调用atan2算的相邻点向量。问题在于插值点间距不均,导致atan2计算出错误的航向角,MPC预测模型拼命扭向错误的方向。
修复方式很简单:参考路径生成的圆弧段,直接用圆弧几何公式计算出每一个采样点的坐标和切向角,而不是事后通过相邻点差分求角度。这个教训让我意识到,参考航向角在MPC里的重要性常常被低估,实际上它决定了预测模型的朝向误差,一点小偏差都会被优化放大。
6.3 更多避坑经验
EKF里有一个经常被忽略的细节:过程噪声协方差矩阵Q建议写成与状态相关,或者至少随速度变化。倒车速度从0.3变到0.8时,模型离散化误差会变大,如果Q不变,低俗段和高速段的表现会有明显差异。我一般把Q近似设为diag([v_scale*0.01^2, v_scale*0.01^2, v_scale*0.005^2]),其中v_scale是当前车速与标准车速的比值。
MPC求解器建议先在一个缩小的问题规模上调试。比如先设Np=5, Nc=3,跑通后再逐步扩大时域。这样做能极大缩短调试时间,因为大时域优化一旦出错,很难判断是模型错误还是数值问题。
另外参数文件里所有物理量单位必须统一。我曾经一度把车身尺寸写成了米,但车位尺寸写成了厘米,结果车辆看起来比车位还大,还以为MPC整体失控,查了两个小时才发现是单位问题。这类低级错误,越早使用带单位注释的参数文件,越能避免。
最后再说说我对这个项目后续扩展的看法。EKF和MPC这套框架之间其实有很多可以替换的模块——EKF可以换成无迹卡尔曼滤波或粒子滤波,MPC可以换成纯跟踪+Stanley控制来做对比实验。MATLAB工程里预留接口,后续接真车传感器数据也只是换个观测函数的事。如果你打算把这个项目作为硕士课题或者简历里的展示项目,建议在后面加一层更高层的路径规划器,让MPC轨迹跟踪变成真正的“规划+跟踪”完整系统。我个人体会是,调试自动泊车时最值钱的不是那些高大上的算法公式,而是能快速定位“到底是观测坏了还是控制坏了”的那套调试手段。把这个能力练出来,比多跑通几个仿真案例有价值得多。