机器人控制利器:MPC入门与实践解析

🕒 2025-09-28 📁 算法 👤 laumy 🔥 11 热度

背景

MPC(Model Predictive Control)模型预测控制,是一种控制方法,广泛应用在机器人、无人驾驶、过程控制、能源系统等领域。它的核心思想用一句话来总结:利用系统模型预测未来,并通过优化选择当前最优的控制输入。

如上图是一个MPC应用框图,先来看看框图中的各个变量。

  • r(t):参考输入,系统希望达到的目标(设定值/reference signal),例如机器人要达到的位置、温度控制的目标值、车辆期望的速度等等。
  • e:误差e(t) = y(t) – r(t),用来计算实际的输出期望值的差距。
  • μ(t):控制输入,由MPC控制器计算出来,施加在系统上的控制量。
  • y(t):系统实际输出,比如位置、速度、温度等。

系统的目标是由MPC控制器计算输出一个μ(t)控制量,然后作用到系统,让系统的输出能够达到期望值。其中有一个反馈回路,形成一个闭环系统,实际的输出y(t)会反馈给比较器,与目标值对比,当未达到目标时,系统自动修正直到达到目标。

原理

系统模型

为了简单,我们先令系统的实际输出y=x,下面用数学建模来描述系统状态模型是

x_{k+1} = A x_k + B u_k

  • $x_k$:系统在时刻$k$的状态,比如汽车的位置和速度[p,v]。
  • $u_k$:系统在时刻$k$的控制输入,比如油门大小、方向盘角度、电机电压等。
  • A:状态的转移矩阵,决定了系统状态的演化方式。
  • B:输入矩阵,描述了控制输入如何影响状态的变换。
  • $x_{k+1}$:系统在下一时刻$k+1$的状态。

A和B是两个矩阵,A决定系统 在没有控制输入时,状态如何随时间演化。B决定控制输入u_k如何影响的状态。

预测未来

MPC原理就是从当前状态x+k出发,用模型递推,预测出未来的N步(如果看过之前关于ACT原理,其实MPC有很多类似之处):

x_{k+1},x_{k+2},x_{k+3},……,x_{k+N}

目标函数

工程中我们最主要的目标是要求出控制量u_t以便让系统最终调整到我们预期的状态。 那如何来设计这个系统了? 前面我们建模了k时刻的状态x_t,那么这个状态+动作需要满足什么样的数学关系了?

与深度学习类似,我们要对状态+动作的数学关系求一个最小值的表达式。看公式:

J = \sum_{i=0}^{N-1} \Big[ (x_{k+i} – x_{k+i}^{\text{ref}})^T Q (x_{k+i} – x_{k+i}^{\text{ref}}) + u_{k+i}^T R u_{k+i} \Big]

上面的公式可以分为两部分,前面部分代表的是k时刻输出状态与目标状态误差值,后面部分是控制的动作。也就是说我们最核心的是要误差越小越好,同时控制的动作不要太大。误差小好理解,动作变化不要太大是因为要保证控制动作要平滑。两部分都是求平方e^2u^2放大比例(跟深度学习中的损失差不多),同时各自有一个权重Q和R,这两个参数是权重参数可调,Q用来平衡快速到目标,R用来调节平衡动作要平稳。

公式中xJ可以说是已知的,那么就可以求出u控制量了。

约束条件

约束条件是在求解代价函数最小值过程中,显示的对控制量ux_k做约束。这样的目的是比如对于车来说油门不能超过100%,速度不能超速,机械臂必能超过关节限位等。

因此在求救J时,可以添加约束条件,如下:

u_{\min} \leq u_k \leq u_{\max};x_{\min} \leq x_k \leq x_{\max}

第一个是 控制输入约束(油门、方向盘角度不能无限大)。第二个是 状态约束(位置、速度等不能超过物理限制)。

工作流程

MPC控制系统中,其工作流程最核心的滚动时域控制,其核心点是每次预测出N步,但是并不是一下就全部执行完N步,因为预测也是有偏差同时在执行过程中会有变化。而是每次预测N步,但是只取第一步进行执行,然后根据新的输出结果重新预测下一个N步。这个其实跟ACT的时间集成有点类似,也就是在每个时间刻都会预测N步,而MPC是取第一步执行,而ACT是取k时刻和此前时刻的加权平均。

下面来看看具体的执行流程:

  • 当前时刻K:系统处于某个状态如蓝线上面的红点,控制器采集到当前状态作为优化的起点。
  • 预测未来N步:橙色窗口覆盖未来N步的时间区;红色虚线显示预测的未来轨迹,如果采用这一串控制动作U=[u_k,u_{k+1}…]系统怎么走。
  • 优化并得到最优控制序列:在预测窗口里,MPC通过解J(误差+控制代价)最小值得到结果一整串最优控制动作序列U
  • 只执行一步:红色箭头指向当前窗口里的第一个控制输入u_k,下方蓝色阶梯控制曲线更新,系统真是状态推进到下一个时刻;上方状态曲线的红点更新到新的位置。
  • 丢弃其余动作:窗口里的控制动作(u_{k+1},u_{k+2}…)丢弃,因为下一时刻会重新优化得到新的控制动作。
  • 窗口前移,重复循环:橙色预测窗口右移一格(k->k+1->k+2),控制器基于新状态重新预测、重新优化,再次执行第一步,丢弃其余周而复始直到达到目标。

因此整个过程核心就是MPC的滚动时域机制:预测未来——>优化整串动作——>执行第一步——>丢弃其余——>窗口前移——>重新计算。

示例程序

下面是一个简单的示例加深对MPC的认识。场景是一个一维的小车:

  • 状态x=[位置,速度],控制输入u=加速度
  • 小车从0开始,目标位置在10米,并希望最后速度接近0.
  • 用MPC做控制,预测N步,计算代价,找到最优控制序列,但只执行第一个动作然后滚动。
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.animation as animation

# --- 系统模型 (离散时间) ---
dt = 0.2
# 状态空间模型: x_{k+1} = A x_k + B u_k
# x = [位置, 速度],u = 加速度
A = np.array([[1, dt],
              [0, 1]])
B = np.array([[0.5*dt**2],
              [dt]])

# 初始状态: 位置=0, 速度=0
x = np.array([0.0, 0.0])
# 目标状态: 位置=10, 速度=0 (停在10米处)
target = np.array([10.0, 0.0])

# --- MPC 参数 ---
N = 5                        # 预测时域长度 (未来看5步)
U_candidates = [-1, 0, 1]    # 控制输入候选集合 (加速度: -1=刹车, 0=不动, 1=加速)

def simulate(x0, u_seq):
    """
    给定初始状态 x0 和一段控制序列 u_seq,
    用系统模型递推未来轨迹,并计算代价 J
    """
    x = x0.copy()
    cost = 0.0
    traj = [x.copy()]  # 保存预测轨迹 (用于可视化)
    for u in u_seq:
        # 状态更新 (预测未来)
        x = A @ x + B.flatten()*u
        traj.append(x.copy())
        # 代价函数 J = 误差项 + 控制代价
        err = x - target
        cost += err[0]**2 + 0.3*err[1]**2 + 0.1*(u**2)
        # 位置误差^2 + 速度误差^2(权重0.3) + 控制输入^2(权重0.1)
    return cost, np.array(traj)

# --- MPC 主循环 (滚动时域控制) ---
history_x = []     # 真实执行的状态轨迹
history_u = []     # 实际执行的控制输入 (只取最优序列的第一步)
pred_trajs = []    # 每次优化得到的预测轨迹 (整串)

for step in range(60):
    best_cost = 1e9
    best_seq = None
    best_traj = None

    # 穷举所有可能的控制序列 U = [u_k, u_{k+1}, ..., u_{k+N-1}]
    for U in np.array(np.meshgrid(*[U_candidates]*N)).T.reshape(-1,N):
        cost, traj = simulate(x, U)
        if cost < best_cost:
            best_cost = cost
            best_seq = U      # 当前最优控制序列
            best_traj = traj  # 当前最优预测轨迹

    # 保存数据 (真实轨迹、控制输入、预测轨迹)
    history_x.append(x.copy())
    history_u.append(best_seq[0])  # 只执行第一步 u_k*
    pred_trajs.append(best_traj)   # 保存整条预测轨迹用于画红虚线

    # 执行第一步 (滚动时域控制的核心:只执行u_k)
    u = best_seq[0]
    x = A @ x + B.flatten()*u

    # 收敛条件 (位置接近10, 速度≈0)
    if abs(x[0]-target[0]) < 0.1 and abs(x[1]-target[1]) < 0.1:
        history_x.append(x.copy())
        history_u.append(0)   # 终端时控制输入设为0
        break

history_x = np.array(history_x)

# --- 动态可视化 ---
fig, (ax1, ax2) = plt.subplots(2, 1, figsize=(8, 6))

# 上图:位置随时间
ax1.axhline(target[0], color="green", linestyle="--", label="Target position")
(line_real,) = ax1.plot([], [], "bo-", label="Real trajectory")   # 蓝点=真实轨迹
(line_pred,) = ax1.plot([], [], "r--", label="Predicted trajectory")  # 红虚线=预测轨迹
(point_exec,) = ax1.plot([], [], "ro", markersize=8, label="Execute point") # 红点=执行点
ax1.set_xlim(0, len(history_x)+N)
ax1.set_ylim(0, target[0]+5)
ax1.set_ylabel("Position")
ax1.legend()

# 下图:控制输入
(line_u,) = ax2.step([], [], where="post", label="Control input u")
ax2.set_xlim(0, len(history_u))
ax2.set_ylim(min(U_candidates)-0.5, max(U_candidates)+0.5)
ax2.set_xlabel("Time step")
ax2.set_ylabel("u")
ax2.legend()

# --- 动画更新函数 ---
def update(frame):
    # 蓝线:实际轨迹
    line_real.set_data(np.arange(frame+1), history_x[:frame+1,0])
    # 红虚线:预测轨迹 (窗口内)
    pred = pred_trajs[frame]
    line_pred.set_data(np.arange(frame, frame+len(pred)), pred[:,0])
    # 红点:当前执行点
    point_exec.set_data([frame], [history_x[frame,0]])
    # 阶梯:控制输入
    line_u.set_data(np.arange(frame+1), history_u[:frame+1])
    return line_real, line_pred, point_exec, line_u

# 动画循环:frames=len(pred_trajs),表示每个MPC优化时刻
ani = animation.FuncAnimation(fig, update, frames=len(pred_trajs),
                              interval=800, blit=True, repeat=False)

plt.show()

发表你的看法

\t