ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

LQR车辆轨迹跟踪Matlab仿真:从运动学模型到调参实战

LQR车辆轨迹跟踪Matlab仿真:从运动学模型到调参实战 简介本资源是一份面向本科毕业设计与控制类课程作业的Matlab实践项目聚焦线性二次调节器LQR在车辆轨迹跟踪中的工程实现帮助学习者将控制理论转化为可运行的闭环控制系统。压缩包共9个文件107KB包含4个.mat数据文件存储预设轨迹与误差数据、2个.m主程序脚本含主控逻辑与圆轨迹生成、2个.png结果图直观展示跟踪效果及1份README.md说明文档代码结构清晰、注释完整便于理解状态空间建模、Riccati方程求解与反馈增益设计全过程。已有474人学习下载读者可直接复现LQR控制器设计、仿真车辆横向跟踪性能并基于提供的路径数据如CircleGen生成的圆形轨迹开展参数调优与对比分析是掌握现代控制理论实践落地的典型教学案例。 最近在调试车辆轨迹跟踪算法时我把纯跟踪和Stanley控制器都跑了一遍结果在弯道连续的路段上超调量始终压不下去。后来把控制器换成LQR线性二次型调节器整套方案才真正落地。Matlab里用LQR做车辆轨迹跟踪工程上相当成熟网上也流传不少“完整源码.zip”但很多源码你拿到手未必能一次跑通更别提应用到自己的项目里。这篇文章想跟你聊透一件事从运动学模型到LQR误差模型再到仿真代码实操和调参LQR车辆轨迹跟踪到底该怎么做。我尽量用自己实际调试项目的口吻写代码贴核心原理讲人话踩过的坑也一并放出来。适合正在学自动驾驶控制、做智能车竞赛或者刚接手轨迹跟踪项目还不太清楚怎么选控制器的同学。1. 车辆轨迹跟踪为什么选LQR运动学模型与控制目标1.1 前轮转向自行车模型我们把车简化成什么车辆轨迹跟踪的控制对象不是车的发动机或刹车而是前轮转角。所以第一步是把车辆模型简化成一个能描述“给定前轮转角车辆如何运动”的数学模型。工程上用得最多的是前轮转向自行车模型它假设左右前轮转角一致、车辆质心侧偏角为零、轮胎侧偏特性处于线性区把四轮车等效成两轮自行车。状态量一般取车辆在大地坐标系下的位置(x, y)和航向角φ。前轮转角记为δ纵向速度记为v。运动学方程就是这三个量之间的微分关系dot(x) v * cos(φ)dot(y) v * sin(φ)dot(φ) v / L * tan(δ)其中L是轴距。这套方程的含义很直观车头朝哪个方向车就往哪个方向走前轮转角越大、轴距越短航向角变化越快。低速工况下这套运动学模型精度已经足够而LQR做轨迹跟踪通常也是在低速结构化场景里用比如园区巡逻车、仓储AGV、智能车竞赛。高速工况需要引入动力学模型那是另一套复杂度本文不展开。仿真的核心逻辑就是在每个采样周期内根据当前状态计算出应施加的前轮转角然后利用上述运动学方程更新车辆状态。这个过程会循环执行到仿真结束。压缩包里给的模型绝大多数也是这套自行车模型区别在于参考轨迹生成和目标函数加权矩阵的设置不同。1.2 纯跟踪与Stanley的局限为什么需要带状态预测的控制器纯跟踪和Stanley在低速、大曲率半径的路径上其实表现不差尤其Stanley在横向误差收敛上非常干脆。但它们的通病是“只看当前位置与参考路径的几何关系”没有把车辆未来的运动趋势纳入控制律设计。纯跟踪有一个至关重要的预瞄距离参数而这个参数往往需要根据车速动态调整调不好就会在弯道上画龙。LQR的思路跟它们有本质区别它把轨迹跟踪问题构造成一个最优控制问题同时考虑当前状态偏差和未来控制代价。你可以把LQR理解成“走一步看三步”的控制器——它不只在消除当前误差而是在最小化一段时间内误差与控制量的加权总和。这也是为什么LQR在曲率连续变化的路径上比几何类控制器更稳、更不容易振荡。当然LQR也有代价你需要一个相对可靠的线性状态方程而且需要调Q矩阵和R矩阵。很多拿到源码包的同学第一反应是跑通就完事但项目落地时最花时间的恰恰是调Q和R这两个矩阵。后面我会专门讲怎么调。2. LQR控制器推导把轨迹跟踪变成带权最优问题2.1 状态误差模型的建立LQR不能直接作用于原始车辆状态方程因为它是非线性系统。常规做法是定义跟踪误差向量然后围绕参考工作点做线性化。这里的误差不是简单的目标位置减当前位置而是要把误差转换到车身坐标系下。定义参考轨迹点(x_ref, y_ref, φ_ref)车辆当前状态为(x, y, φ)。横向偏差e_y和航向偏差e_φ按下式计算e_y (y_ref - y) * cos(φ) - (x_ref - x) * sin(φ)e_φ φ_ref - φ其中e_y是车辆坐标系下的横向偏差不是大地坐标系的y方向差值。很多初学者直接写ey y - y_ref结果仿真结果总是对不上就是因为忽略了坐标系旋转。对上述误差求导并将自行车模型在小角度假设下线性化可以得到一个简洁的误差状态方程dot(e_y) v * e_φdot(e_φ) v / L * tan(δ) - dot(φ_ref)定义控制量u tan(δ)忽略参考航向角速度的影响时线性连续状态方程为dot(e) A_c * e B_c * u其中A_c [[0, v], [0, 0]]B_c [[0], [v/L]]。如果参考轨迹的曲率变化明显dot(φ_ref)不能直接忽略它会被当作已知扰动加入控制器前馈项。这个前馈项在圆形或S形参考轨迹上尤其重要后面实操部分会给出代码实现。2.2 代价函数与Riccati方程Q和R到底在管什么LQR的核心是求解一个最优反馈控制律u -K * e使得如下代价函数最小J ∫(e^T * Q * e u^T * R * u) dtQ是状态权重矩阵R是控制权重矩阵。Q和R的具体含义可以这样直观理解Q矩阵对角线元素越大表示对应状态偏差的“惩罚”越重控制器会更激进地消除该偏差。R矩阵越大表示对控制量的惩罚越重控制器输出会更平缓但跟踪响应会变慢。以常用的二维误差状态[e_y; e_φ]为例Q diag([q1, q2])代表横向误差权重为q1航向误差权重为q2。q1明显大于q2时控制器优先把车辆拉回参考线反过来如果航向误差权重更大控制器则更倾向先摆正车头再平滑靠近路径。在Matlab中连续系统的LQR增益直接用lqr函数计算离散系统则用dlqr。两者对Q、R的意义相同但内部求解方式不同。因为数字控制器本身是离散的我习惯先用零阶保持器把连续状态方程离散化再用dlqr求解。这样代码逻辑和实际嵌入式部署更接近。3. Matlab完整实现主循环、误差计算与代码逐段拆解3.1 参考轨迹生成圆形和S形怎么构造源码包里第一个文件通常是轨迹生成部分作用是构造一组已知的参考路径点序列。为了验证LQR对不同曲率路径的适应能力我一般先用圆形轨迹做基础测试再切换S形或双移线路径观察跟踪效果。圆形轨迹参数方程x_ref R * sin(omega * t)y_ref -R * cos(omega * t) R这个写法让起点落在(0, 0)满足初始状态与参考点重合的测试条件。取R 10 m等速v 5 m/s时参考角速度omega v / R 0.5 rad/s。轨迹方向角不直接用atan2(y_ref, x_ref)硬算更稳妥的办法是对轨迹点做数值差分phi_ref atan2(diff(y_ref), diff(x_ref)); phi_ref [phi_ref, phi_ref(end)];这样求出的航向角没有解析式推导中的象限跳变问题。圆形轨迹闭合数值差分会有末尾一个无效点手动补一个末尾值即可。S形参考轨迹可以用多段正弦函数拼接或者直接给一个高次多项式。实际项目中参考轨迹一般来自全局路径规划模块格式是离散航点序列再通过插值生成稠密参考点。这里用圆形轨迹是最好调试的因为曲率恒定前馈计算简单直观。3.2 LQR增益求解与控制量计算下面是完整的主程序核心代码。这段代码跟压缩包中main_lqr_tracking.m的逻辑一一对应可以直接复制到Matlab中运行。% main_lqr_tracking.m % LQR车辆轨迹跟踪仿真主程序 clear; clc; close all; %% 1. 车辆参数 L 2.8; % 轴距(m) v 5.0; % 纵向速度(m/s)匀速假设 %% 2. 仿真参数与参考轨迹 dt 0.05; % 采样周期(s) T 30; % 仿真时长(s) t 0:dt:T; R_circle 10.0; % 参考圆形轨迹半径(m) omega v / R_circle; % 参考角速度(rad/s) x_ref R_circle * sin(omega * t); y_ref -R_circle * cos(omega * t) R_circle; dx_ref R_circle * omega * cos(omega * t); dy_ref R_circle * omega * sin(omega * t); phi_ref atan2(dy_ref, dx_ref); %% 3. 初始状态故意给一点偏差 x zeros(size(t)); y zeros(size(t)); phi zeros(size(t)); x(1) 0.8; y(1) 0.5; phi(1) 0.15; %% 4. 离散化状态方程与LQR求解 % 状态量 e [e_y(横向偏差); e_phi(航向偏差)] % dot(e) A_c * e B_c * u, u tan(delta) A_c [0, v; 0, 0]; B_c [0; v / L]; % 零阶保持器离散化 A_d eye(2) A_c * dt; B_d B_c * dt; Q_lqr diag([20, 10]); R_lqr 0.5; [K, S, e] dlqr(A_d, B_d, Q_lqr, R_lqr); %% 5. 仿真主循环 delta zeros(size(t)); for k 1:length(t) - 1 % 大地坐标系下的位置误差 ex x(k) - x_ref(k); ey y(k) - y_ref(k); % 转换到车身坐标系注意坐标系旋转 e_y ey * cos(phi(k)) - ex * sin(phi(k)); e_phi phi(k) - phi_ref(k); % 前馈项参考曲率对应的稳态前轮转角 delta_ff atan(L * omega / v); % LQR反馈控制量 u_fb -K * [e_y; e_phi]; % 最终前轮转角前馈反馈 delta(k 1) delta_ff u_fb; % 更新车辆运动状态自行车模型 x(k 1) x(k) v * cos(phi(k)) * dt; y(k 1) y(k) v * sin(phi(k)) * dt; phi(k 1) phi(k) v / L * tan(delta(k 1)) * dt; end %% 6. 绘图 figure; plot(x_ref, y_ref, b--, LineWidth, 1.5); hold on; plot(x, y, r-, LineWidth, 1.5); xlabel(x (m)); ylabel(y (m)); legend(参考轨迹, 实际轨迹); axis equal; grid on; title(LQR车辆轨迹跟踪-圆形路径); figure; plot(x_ref, y_ref); hold on; plot(x, y); for k 1:40:length(t) quiver(x(k), y(k), cos(phi(k)), sin(phi(k)), 0.5, g); end axis equal; grid on;运行这段代码车辆会在1到2秒内从初始偏差收敛到参考轨迹上。值得注意的是代码中控制律是u_fb -K * [e_y; e_phi]而传给tan的是delta delta_ff u_fb。这里前馈项delta_ff直接加在弧度角度上而反馈项本身是tan(δ)的近似量。在小角度假设下两者可以近似相加但如果你把R矩阵调得很小、反馈控制量很大这种近似会失效出现大转角下的振荡。3.3 仿真主循环的更新逻辑为什么用上一时刻航向角主循环里状态更新用的是phi(k)而不是phi(k1)这是前向欧拉积分与状态方程离散化方式一致。控制量delta(k1)参与状态更新时车辆实际上是在k时刻接收到新转角然后在dt时间内完成一次运动。如果改成中点法或四阶Runge-Kutta法精度会提高但对LQR轨迹跟踪结果影响很小因为采样周期通常只有20到50毫秒。在低精度浮点平台上跑时前向欧拉反而更稳定不容易出现数值振荡。实际项目里我在Matlab验证算法时用前向欧拉移植到C时用同样的离散方式保证仿真和实车逻辑一致。这里有一个很多源码包里容易出问题的地方有些版本的主循环里状态更新顺序是“先更新x/y再更新phi最后计算delta”导致控制量延迟了一个周期。低速下看不出来速度快一点或者R矩阵小一点整个系统就开始振荡。如果你拿到的压缩包跑出来轨迹发散先检查状态更新和控制量计算之间的先后顺序。4. 参数调优与实测表现一套能直接跑的参数和调试过程4.1 初版参数为什么振荡R矩阵太小不是好事拿到Q、R初值的时候我在源码包里用的是Q diag([1, 1])R 0.01。仿真结果是车辆在参考路径附近持续振荡横向误差始终在0.5米左右来回无法收敛。问题出在R太小。R 0.01表示控制器几乎不惩罚控制量前轮转角可以瞬间达到很大值。反馈增益K也被放大系统对误差过于敏感。自行车模型里航向角变化率与前轮转角的正切函数相关当δ超过30度时tan(δ)已经增长得很厉害模型线性化的小角度假设失效控制律计算值与实际运动出现严重偏差于是振荡。这跟很多教材里“R越小追踪越快”的说法有出入。教材假设系统始终工作在线性区真实车辆模型和线性近似之间存在截断误差当控制量过大时这个截断误差会直接把控制器带崩。4.2 最终参数表与结果分析经过多轮调整最终采用下面这组参数参数取值说明Q(1,1)20横向偏差权重Q(2,2)10航向偏差权重R(1,1)0.5控制量权重dt0.05s采样周期v5 m/s纵向速度这组参数下初始横向偏差0.8米、航向偏差0.15弧度收敛时间约2秒稳态横向误差小于5厘米前轮转角峰值约12度。整个过程没有明显超调曲线跟踪平滑。横向误差权重是航向误差权重的2倍说明控制器优先把车拉回参考线。如果路径曲率变化剧烈可以适当提高航向误差权重避免车头频繁摆动。如果你的高速车辆模型本身有较大惯性R矩阵要适当加大一般从0.5往1.0、2.0方向调试直到转角曲线不再出现高频抖动。一个实用的调试技巧是先把R固定在一个中间值比如1逐步增加Q(1,1)观察横向误差收敛时间。收敛时间太长就加大Q(1,1)出现振荡就减小Q(1,1)或者增加R。重复这个过程一般两三轮就能调到合适的区域。4.3 前馈项的意义没有它弯道永远追不上前面代码里的delta_ff atan(L * omega / v)是前馈项。圆形轨迹上车辆需要保持一个恒定的前轮转角才能维持转弯角速度。没有前馈项时LQR反馈控制必须先积累一定的航向误差才能产生所需的转角结果就是车辆始终滞后于参考轨迹一个固定角度横向误差表现为一个稳定的负偏移。加上前馈项后控制器在运行初期就输出维持转弯所需的基础转角反馈项只需要修正偏差压力小很多。实测中加前馈后稳态横向误差从约15厘米降到5厘米以下而且转角输出曲线更光滑。对于曲率变化的轨迹前馈项应使用当前参考点的曲率实时计算kappa_ref (dx_ref .* ddy_ref - dy_ref .* ddx_ref) ./ (dx_ref.^2 dy_ref.^2).^1.5; delta_ff atan(L * kappa_ref);这样写的好处是S形、双移线等任意参数化轨迹都能用不需要预先知道解析曲率公式。5. 从Matlab仿真到工程落地采样周期、状态估计与常见坑5.1 采样周期对控制性能的影响Matlab仿真的采样周期可以随便设但实际控制器是跑在MCU或工控机上的采样周期受限于算法耗时和传感器帧率。LQR控制器本身计算量很小在STM32级别单片机上都能跑到100Hz以上真正的瓶颈在定位模块的更新频率。车载定位常用组合惯导10Hz到50Hz不等。如果定位更新是20Hz而控制周期是50Hz就会出现控制指令在两次定位之间“盲飞”的情况。工程上通常把LQR控制频率设定为定位频率的2到3倍并在定位数据缺失时保持上一次控制输出不变直到收到新定位信息。把Matlab里的dt 0.05改成dt 0.1跑一次你会发现收敛时间明显变长甚至可能出现极限环。原因很简单离散化状态方程里LQR增益是通过离散模型算出来的采样周期变大后模型离散化低阶近似误差也变大控制精度下降。5.2 状态估计依赖没有精确位姿LQR理论再漂亮也没用LQR反馈量是横向偏差和航向偏差这两个量的精度直接取决于车辆当前位姿估计的准确性。仿真里我们直接从状态变量中读取x、y、phi但实车中这些量来自传感器融合。GPS在遮挡环境下可能漂移好几米惯导长时间运行会累积误差轮速里程计在轮胎打滑时失效。处理方式是给LQR控制器增加一个输入限幅也就是前轮转角不能超过物理转向机构的最大角度。这个限幅既保护执行机构也能防止状态估计算法出现短时跳变时控制器输出过大转角。我在项目里把转向限幅设为±30度超过直接截断。限幅会导致系统进入非线性区LQR的稳定性理论不再严格成立但工程上这种妥协是必要的。另外一个容易忽略的点是时间同步。控制指令发出到转向机构响应之间存在延迟如果延迟到达一个采样周期以上建议在状态更新中加入纯延迟补偿。具体做法是记录过去多个时刻的控制量用车辆模型从上一帧状态推算当前状态而不是直接用最新定位数据。小型车模延迟不大但大型工程车辆响应延迟可达100ms以上不做补偿的话控制器会出现明显的相位滞后。5.3 从Demo模型到项目复用的扩展思路这套LQR轨迹跟踪源码虽然跑的是圆形路径但稍微改动就能适配实际项目。首先把参考轨迹换成全局规划模块输出的航点序列用三次样条插值生成稠密参考点并在每个采样周期查找距离车辆当前位置最近的参考点用该点的曲率计算前馈。最近点搜索在航点数量大时要注意效率简单的做法是二分搜索前提是路径点按弧长单调排列。其次LQR反馈增益可以做成车速相关的表格。低速时使用一组Q、R高速时重新计算K值。因为状态方程中A和B矩阵包含车速vv变化后系统动态特性变化很大。如果你坚持使用固定增益在速度变化范围较大的场景里很难兼顾低速跟踪精度和高速稳定性。工程上可以在车速变化时实时调用dlqr重新求解K这个计算量在一台普通工控机上完全可接受。不过如果你把速度变化范围拉得很高比如从0.5m/s到15m/s那么车辆转向延迟和侧向动力学特性就不能忽略纯运动学LQR会到达能力上限。这时候更适合换LTV-MPC或者LQR前馈反馈解耦架构。我做过的项目中有一个低速清扫车直接沿用这套LQR逻辑只改了参考轨迹输入和转向限幅就能稳定跑完复杂园区路线。如果你的工况速度不超过8m/s这套方案足够用了。从调试体验来说LQR最让我省心的一点是它不像MPC那样需要处理约束和滚动时域优化也不像PID那样需要反复调三个系数Q和R的物理含义清晰调参方向不容易跑偏。你在Matlab里把本文代码跑通之后再换成自己的参考轨迹和车辆参数大概率遇到的问题都能从Q、R和前馈这三个角度解决。本文还有配套的精品资源点击获取
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进