用CasADi和Python搞定差分小车MPC控制:从运动学建模到仿真避坑全流程

张开发
2026/5/10 9:12:35 15 分钟阅读

分享文章

用CasADi和Python搞定差分小车MPC控制:从运动学建模到仿真避坑全流程
用CasADi和Python实现差分小车MPC控制从零构建到仿真优化的完整指南引言在机器人控制领域模型预测控制MPC因其出色的处理多变量约束和优化未来行为的能力而备受青睐。CasADi作为一个强大的符号计算框架为MPC的实现提供了高效便捷的工具链。本文将带您从零开始使用Python和CasADi构建一个完整的差分驱动机器人MPC控制器。无论您是控制工程的学生还是机器人爱好者本指南都将帮助您理解差分驱动机器人的运动学建模原理掌握CasADi在MPC问题构建中的应用技巧学会如何将数学模型转化为可执行的Python代码规避实际实现中的常见陷阱和性能瓶颈我们将采用循序渐进的方式从基础理论到代码实现最后通过仿真验证控制效果。所有代码示例都经过精心设计可直接运行和修改为您提供一个真正开箱即用的学习体验。1. 差分驱动机器人运动学建模1.1 坐标系与状态定义差分驱动机器人也称为差速驱动小车是移动机器人中最常见的构型之一。要描述其在平面中的运动我们需要定义以下状态变量全局坐标系状态x机器人在全局坐标系下的x坐标y机器人在全局坐标系下的y坐标θ机器人前进方向与全局x轴的夹角航向角这三个变量构成了机器人的状态向量X [x, y, θ]ᵀ1.2 控制输入与运动方程差分驱动机器人通过两个独立驱动的轮子实现运动其控制输入通常为v前进速度线速度ω转向角速度基于刚体运动学我们可以推导出连续时间下的运动学方程ẋ v * cos(θ) ẏ v * sin(θ) θ̇ ω或者用矩阵形式表示为[ẋ] [cos(θ) 0][v] [ẏ] [sin(θ) 0][ω] [θ̇] [0 1]1.3 离散化模型为了便于数字控制器实现我们需要将连续时间模型离散化。采用前向欧拉法离散时间步长为ΔT时离散状态更新方程为x(k1) x(k) ΔT * v(k) * cos(θ(k)) y(k1) y(k) ΔT * v(k) * sin(θ(k)) θ(k1) θ(k) ΔT * ω(k)提示离散化方法的选择会影响控制精度。对于高速运动场景可考虑更高阶的离散化方法如Runge-Kutta。2. MPC问题构建与优化目标2.1 模型预测控制基本原理MPC的核心思想是在每个控制周期基于当前状态和系统模型预测未来N步的系统行为求解一个优化问题得到最优控制序列只执行第一步控制然后在下一个周期重复整个过程这种滚动时域策略使MPC能够及时响应系统变化和干扰。2.2 代价函数设计一个典型的MPC代价函数包含以下部分状态偏差惩罚驱使系统状态趋近参考值控制量惩罚避免过大的控制输入终端代价确保预测时域末端接近目标数学表达式为min J Σ [ (x(k)-x_ref)ᵀQ(x(k)-x_ref) u(k)ᵀRu(k) ] (x(N)-x_ref)ᵀP(x(N)-x_ref)其中Q、R、P为权重矩阵需要根据控制需求调整。2.3 约束条件处理实际系统中我们需要考虑各种约束状态约束如工作空间限制控制约束如电机速度/扭矩限制动力学约束由系统物理特性决定在CasADi中这些约束可以方便地表示为不等式或等式约束。3. CasADi实现详解3.1 CasADi基础与符号系统CasADi提供了两种主要的符号变量类型SX标量符号适合小型问题MX矩阵符号适合大型问题对于我们的差分小车MPC使用SX即可满足需求。import casadi as ca # 定义状态变量 x ca.SX.sym(x) y ca.SX.sym(y) theta ca.SX.sym(theta) states ca.vertcat(x, y, theta) n_states states.size()[0] # 定义控制变量 v ca.SX.sym(v) omega ca.SX.sym(omega) controls ca.vertcat(v, omega) n_controls controls.size()[0]3.2 运动学模型的符号表达将离散运动学方程转化为CasADi函数# 右端项(RHS)方程 rhs ca.vertcat(v*ca.cos(theta), v*ca.sin(theta), omega) # 创建运动学函数 f ca.Function(f, [states, controls], [rhs], [input_state, control_input], [rhs])3.3 MPC问题构建采用Single Shooting方法构建MPC问题# 定义优化变量 U ca.SX.sym(U, n_controls, N) # N步控制序列 X ca.SX.sym(X, n_states, N1) # N1步状态序列 P ca.SX.sym(P, 2*n_states) # 参数(初始状态和目标状态) # 初始状态约束 X[:, 0] P[:3] # 构建状态预测 for i in range(N): f_value f(X[:, i], U[:, i]) X[:, i1] X[:, i] f_value * T # 创建预测函数 ff ca.Function(ff, [U, P], [X], [input_U, target_state], [horizon_states])3.4 优化求解器配置# 权重矩阵 Q np.diag([1.0, 5.0, 0.1]) # 状态权重 R np.diag([0.5, 0.05]) # 控制权重 # 构建目标函数 obj 0 for i in range(N): obj ca.mtimes([(X[:,i]-P[3:]).T, Q, X[:,i]-P[3:]]) ca.mtimes([U[:,i].T, R, U[:,i]]) # 定义约束 g [] for i in range(N1): g.append(X[0, i]) # x约束 g.append(X[1, i]) # y约束 # NLP问题定义 nlp_prob { f: obj, x: ca.reshape(U, -1, 1), p: P, g: ca.vertcat(*g) } # IPOPT求解器配置 opts { ipopt.max_iter: 100, ipopt.print_level: 0, print_time: 0, ipopt.acceptable_tol: 1e-8, ipopt.acceptable_obj_change_tol: 1e-6 } # 创建求解器 solver ca.nlpsol(solver, ipopt, nlp_prob, opts)4. 仿真实现与性能优化4.1 仿真主循环# 约束条件定义 lbx [] ubx [] for _ in range(N): lbx.append(-v_max) ubx.append(v_max) lbx.append(-omega_max) ubx.append(omega_max) # 仿真初始化 x0 np.array([0, 0, 0]) # 初始状态 xs np.array([1.5, 1.5, 0]) # 目标状态 u0 np.zeros((N, 2)) # 初始控制猜测 # 主循环 while np.linalg.norm(x0 - xs) 1e-2: # 设置参数 c_p np.concatenate((x0, xs)) # 求解优化问题 res solver( x0np.reshape(u0, (-1, 1)), pc_p, lbg[-2]*2*(N1), # x,y约束 ubg[2]*2*(N1), lbxlbx, ubxubx ) # 获取最优控制 u_sol np.reshape(res[x], (n_controls, N)) # 状态预测 ff_value ff(u_sol, c_p) # 应用控制并更新状态 x0 x0 T * f(x0, u_sol[:, 0]).full().flatten() u0 np.hstack((u_sol[:, 1:], u_sol[:, -1:]))4.2 常见问题与调试技巧在实际实现中您可能会遇到以下典型问题维度不匹配错误确保所有向量和矩阵的维度一致特别注意CasADi中列向量和行向量的区别求解器收敛困难检查约束是否合理尝试调整IPOPT参数提供更好的初始猜测性能瓶颈减少预测时域N尝试MX符号而不是SX考虑使用Multi-Shooting方法4.3 仿真结果分析成功实现后您应该能看到机器人从起点平滑运动到目标点。典型性能指标包括计算时间单次优化求解耗时控制精度最终位置误差能量消耗控制输入的积分通过调整权重矩阵Q、R和预测时域N可以平衡这些指标。5. 进阶优化与扩展5.1 Multi-Shooting方法实现与Single Shooting相比Multi-Shooting将状态也作为优化变量通常能提供更好的数值稳定性# Multi-Shooting变量定义 X ca.SX.sym(X, n_states, N1) U ca.SX.sym(U, n_controls, N) # 添加连续性约束 g [] for i in range(N): x_next X[:,i] T*f(X[:,i], U[:,i]) g.append(X[:,i1] - x_next) # 连续性约束5.2 实时性能优化技巧热启动使用上一周期的解作为当前优化的初始猜测代码生成将CasADi问题编译为C代码加速求解并行计算利用多核处理器并行计算梯度5.3 实际系统集成考虑将MPC控制器部署到真实机器人时还需考虑状态估计融合传感器数据获取准确状态时延补偿处理计算和通信延迟鲁棒性设计应对模型不确定性和干扰6. 完整代码示例与资源为方便读者实践我们提供了一个完整的Python实现包含以下功能可配置的MPC参数实时可视化性能统计# 差分小车MPC完整实现 import numpy as np import casadi as ca import matplotlib.pyplot as plt import time class DifferentialDriveMPC: def __init__(self, T0.2, N10, QNone, RNone): # 参数初始化 self.T T # 采样时间 self.N N # 预测时域 # 默认权重矩阵 self.Q np.diag([1.0, 5.0, 0.1]) if Q is None else Q self.R np.diag([0.5, 0.05]) if R is None else R # 物理约束 self.v_max 0.6 self.omega_max np.pi/4.0 self.rob_diam 0.3 # 构建MPC问题 self.build_mpc() def build_mpc(self): # 状态和控制变量定义 x ca.SX.sym(x); y ca.SX.sym(y); theta ca.SX.sym(theta) self.states ca.vertcat(x, y, theta) self.n_states self.states.size()[0] v ca.SX.sym(v); omega ca.SX.sym(omega) self.controls ca.vertcat(v, omega) self.n_controls self.controls.size()[0] # 运动学模型 rhs ca.vertcat(v*ca.cos(theta), v*ca.sin(theta), omega) self.f ca.Function(f, [self.states, self.controls], [rhs]) # 优化变量 U ca.SX.sym(U, self.n_controls, self.N) X ca.SX.sym(X, self.n_states, self.N1) P ca.SX.sym(P, 2*self.n_states) # 状态预测 X[:,0] P[:3] for i in range(self.N): f_value self.f(X[:,i], U[:,i]) X[:,i1] X[:,i] self.T*f_value # 目标函数 obj 0 for i in range(self.N): obj ca.mtimes([(X[:,i]-P[3:]).T, self.Q, X[:,i]-P[3:]]) \ ca.mtimes([U[:,i].T, self.R, U[:,i]]) # 约束 g [] for i in range(self.N1): g.append(X[0,i]) # x约束 g.append(X[1,i]) # y约束 # NLP问题 nlp_prob { f: obj, x: ca.reshape(U, -1, 1), p: P, g: ca.vertcat(*g) } # 求解器选项 opts { ipopt.max_iter: 100, ipopt.print_level: 0, print_time: 0, ipopt.acceptable_tol: 1e-8, ipopt.acceptable_obj_change_tol: 1e-6 } self.solver ca.nlpsol(solver, ipopt, nlp_prob, opts) # 约束边界 self.lbg [-2, -2]*(self.N1) # 状态约束 self.ubg [2, 2]*(self.N1) self.lbx [] self.ubx [] for _ in range(self.N): self.lbx [-self.v_max, -self.omega_max] self.ubx [self.v_max, self.omega_max] def solve(self, x0, xs): # 初始控制猜测 u0 np.zeros((self.N, 2)) # 设置参数 c_p np.concatenate((x0, xs)) # 求解 res self.solver( x0np.reshape(u0, (-1, 1)), pc_p, lbgself.lbg, ubgself.ubg, lbxself.lbx, ubxself.ubx ) # 获取解 u_sol np.reshape(res[x], (self.n_controls, self.N)) return u_sol[:,0] # 返回第一步控制 # 使用示例 mpc DifferentialDriveMPC(N10) x_current np.array([0, 0, 0]) x_target np.array([1.5, 1.5, 0]) # 仿真循环 trajectory [x_current.copy()] for _ in range(100): u_opt mpc.solve(x_current, x_target) x_current mpc.T * mpc.f(x_current, u_opt).full().flatten() trajectory.append(x_current.copy()) if np.linalg.norm(x_current - x_target) 0.01: break # 可视化 trajectory np.array(trajectory) plt.figure() plt.plot(trajectory[:,0], trajectory[:,1], b-) plt.plot(x_target[0], x_target[1], ro) plt.axis(equal) plt.xlabel(x); plt.ylabel(y) plt.title(MPC轨迹跟踪) plt.show()7. 学习资源与后续方向7.1 推荐学习资料CasADi官方文档最权威的API参考和教程MPC理论教材Model Predictive Controlby Eduardo F. CamachoPredictive Control for Linear and Hybrid Systemsby Borrelli et al.开源项目ACADO Toolkitdo-mpc7.2 扩展应用方向掌握了基础MPC实现后您可以进一步探索路径跟踪跟随预定义的轨迹而非固定点避障控制在代价函数中加入障碍物约束多机器人协同协调多个机器人的运动非线性MPC处理更复杂的动力学模型在实际项目中我发现最关键的调参经验是先确定合理的状态权重Q然后逐步调整控制权重R直到获得满意的响应速度和稳定性平衡。预测时域N的选择需要权衡计算负担和控制性能通常5-20步是一个实用的范围。

更多文章