轨迹跟踪是自动驾驶和机器人控制领域的核心问题之一。最近我在研究如何利用CasADi框架实现模型预测控制(MPC)来解决这个问题,特别是在质点车辆模型上的应用。这个方案最大的优势在于,它能够将复杂的控制问题转化为优化问题,并通过高效的数值求解器得到控制指令。
在实际测试中,我发现这套方法不仅能处理常规的轨迹跟踪任务,还能很好地应对各种约束条件,比如速度限制、加速度限制等。下面我就详细分享一下这个项目的实现过程和经验心得。
模型预测控制之所以适合轨迹跟踪问题,主要基于三个特性:
对于车辆轨迹跟踪这种需要考虑未来状态的控制问题,MPC提供了完美的解决方案框架。
CasADi是一个专门用于非线性优化和最优控制的框架,相比直接使用Matlab的优化工具箱,它有几点优势:
我们采用简化的质点车辆模型来描述车辆动力学:
code复制dx/dt = v * cos(θ)
dy/dt = v * sin(θ)
dθ/dt = v * tan(δ)/L
dv/dt = a
其中:
这个模型虽然简化了轮胎动力学等复杂因素,但对于轨迹跟踪问题已经足够。
MPC的核心是将控制问题转化为优化问题。我们需要定义:
具体来说,代价函数可以表示为:
J = Σ(||x_k - x_ref||_Q + ||u_k||_R) + ||x_N - x_ref||_P
其中Q、R、P是权重矩阵,N是预测时域。
首先需要安装CasADi的Matlab工具箱:
import casadi.*不应报错建议使用Matlab R2020b或更新版本,与CasADi 3.5.5兼容性较好。
matlab复制import casadi.*
% 定义状态变量和控制变量
x = SX.sym('x', 4); % [x_pos, y_pos, theta, v]
u = SX.sym('u', 2); % [accel, steer]
% 定义模型参数
L = 2.5; % 车辆轴距
% 定义动力学方程
xdot = [x(4)*cos(x(3)); % x_pos_dot
x(4)*sin(x(3)); % y_pos_dot
x(4)*tan(u(2))/L; % theta_dot
u(1)]; % v_dot
为了在MPC中使用,我们需要将连续时间模型离散化。这里采用RK4方法:
matlab复制% 定义RK4积分函数
dt = 0.1; % 时间步长
k1 = xdot;
k2 = subs(xdot, x, x + dt/2*k1);
k3 = subs(xdot, x, x + dt/2*k2);
k4 = subs(xdot, x, x + dt*k3);
x_next = x + dt/6*(k1 + 2*k2 + 2*k3 + k4);
% 创建离散模型函数
F = Function('F', {x, u}, {x_next}, {'x', 'u'}, {'x_next'});
matlab复制N = 20; % 预测时域
Q = diag([10, 10, 1, 0.5]); % 状态权重
R = diag([0.1, 1]); % 控制权重
P = Q; % 终端权重
% 初始化优化问题
opti = casadi.Opti();
% 定义决策变量
X = opti.variable(4, N+1); % 状态轨迹
U = opti.variable(2, N); % 控制序列
% 初始条件约束
opti.subject_to(X(:,1) == x0);
% 动力学约束
for k = 1:N
opti.subject_to(X(:,k+1) == F(X(:,k), U(:,k)));
end
% 控制量约束
opti.subject_to(-3 <= U(1,:) <= 3); % 加速度限制
opti.subject_to(-0.5 <= U(2,:) <= 0.5); % 转向角限制
% 速度约束
opti.subject_to(0 <= X(4,:) <= 25); % 0-25 m/s
% 代价函数
J = 0;
for k = 1:N
J = J + (X(:,k)-xref)'*Q*(X(:,k)-xref) + U(:,k)'*R*U(:,k);
end
J = J + (X(:,N+1)-xref)'*P*(X(:,N+1)-xref);
opti.minimize(J);
% 选择求解器
p_opts = struct('expand', true);
s_opts = struct('max_iter', 100);
opti.solver('ipopt', p_opts, s_opts);
我们使用正弦曲线作为测试轨迹:
matlab复制T = 20; % 总时长
t = 0:dt:T;
xref_traj = [t; 5*sin(0.5*t); atan2(5*0.5*cos(0.5*t), 1); sqrt(1 + (5*0.5*cos(0.5*t)).^2)];
matlab复制x_current = [0; 0; 0; 1]; % 初始状态
for i = 1:length(t)-N
% 设置当前状态
opti.set_value(x0, x_current);
% 设置参考轨迹
xref = xref_traj(:,i:i+N);
opti.set_value(xref, xref);
% 求解优化问题
sol = opti.solve();
% 获取最优控制
u_opt = sol.value(U(:,1));
% 应用控制并模拟系统
x_current = full(F(x_current, u_opt));
% 存储结果
X_hist(:,i) = x_current;
U_hist(:,i) = u_opt;
end
matlab复制figure;
plot(xref_traj(1,:), xref_traj(2,:), 'r--'); hold on;
plot(X_hist(1,:), X_hist(2,:), 'b-');
legend('参考轨迹', '实际轨迹');
xlabel('x位置'); ylabel('y位置');
title('轨迹跟踪效果');
matlab复制if i > 1
opti.set_initial(X, X_prev);
opti.set_initial(U, U_prev);
end
X_prev = sol.value(X);
U_prev = sol.value(U);
减少决策变量:可以尝试使用控制参数化方法,比如用B样条表示控制轨迹
并行计算:对于长时间仿真,可以将不同时间段的优化问题并行处理
权重选择对控制性能影响很大,我的经验是:
一个实用的调试方法是:
matlab复制Q = diag([1, 1, 0.1, 0.01]); % 初始猜测
R = diag([0.01, 0.1]);
% 自动调整循环
for scale = logspace(-2, 2, 10)
test_Q = Q * scale;
% 运行仿真并评估性能
% 选择使跟踪误差和控制量都合理的scale值
end
问题现象:IPOPT报告"Restoration Failed"或"Invalid Number"
可能原因和解决方案:
可能原因:
解决方案:
matlab复制% 增加预测时域
N = 30;
% 调整权重(更注重位置跟踪)
Q = diag([20, 20, 0.5, 0.01]);
% 或者考虑更精确的车辆模型
对于实时控制,需要保证单次优化时间小于控制周期dt:
实际应用中还需要考虑道路边界约束:
matlab复制% 定义道路中心线
road_center = @(s) [s; 5*sin(0.5*s)];
% 计算车辆到中心线的距离
[~, dist] = dsearchn(road_center(0:0.1:100)', X(1:2,k)');
% 添加道路约束
opti.subject_to(-3 <= dist <= 3); % 3米宽的道路
可以通过添加距离约束来实现避障:
matlab复制for obs in obstacles
dist = sqrt((X(1,k)-obs(1))^2 + (X(2,k)-obs(2))^2);
opti.subject_to(dist >= obs(3) + safe_margin);
end
更精确的模型可以考虑轮胎侧偏特性:
matlab复制% Pacejka轮胎模型
B = 10; C = 1.9; D = 1; E = 0.97;
alpha = atan2(v*sin(beta), v*cos(beta)) - delta;
Fy = D*sin(C*atan(B*alpha - E*(B*alpha - atan(B*alpha))));
matlab复制function [u_opt, info] = mpc_controller(x_current, x_ref_traj, mpc_params)
% 参数包括N, Q, R, P等
% 返回最优控制和求解信息
end
matlab复制info.iterations = sol.stats.iter_count;
info.solve_time = sol.stats.t_proc_total;
info.cost = sol.value(J);
matlab复制try
sol = opti.solve();
catch
warning('求解失败,使用上一控制量');
u_opt = U_prev(:,1);
end
matlab复制if mod(i,10) == 0
clf;
plot(xref_traj(1,:), xref_traj(2,:), 'r--'); hold on;
plot(X_pred(1,:), X_pred(2,:), 'g-');
plot(x_current(1), x_current(2), 'bo');
drawnow;
end