使用路径规划可以得到一系列的路径点,如下图所示有5个路径点,这条路径是很曲折的,这不利于机器人的移动,试想一个机器人直接沿着这条路径进行移动,当到达一个路径点后速度减到零,原地旋转,指向下一个路径点,再继续移动,这不仅智障,而且浪费能量。于是我们就想,能不能规划出一条轨迹,使机器人能够在每一个路径点处能够平滑过渡,从而表现得机灵一点。这就是轨迹规划问题。

ceres轨迹优化 什么是轨迹优化_路径规划

ceres轨迹优化 什么是轨迹优化_二次规划_02

路径规划得到的路径点

轨迹优化得到的轨迹(橙色)

二次规划(Quadratic Programming, QP)

由于轨迹规划问题可以归结为一个二次规划问题,所以这里先对二次规划进行一个简单的介绍

二次型

含有ceres轨迹优化 什么是轨迹优化_ceres轨迹优化_03个变量ceres轨迹优化 什么是轨迹优化_ceres轨迹优化_04的二次齐次函数:
ceres轨迹优化 什么是轨迹优化_运动规划_05
称为二次型。取ceres轨迹优化 什么是轨迹优化_移动机器人_06,则有ceres轨迹优化 什么是轨迹优化_移动机器人_07,上式可表示为:
ceres轨迹优化 什么是轨迹优化_移动机器人_08
矩阵表示为:
ceres轨迹优化 什么是轨迹优化_ceres轨迹优化_09

二次规划问题

当目标函数ceres轨迹优化 什么是轨迹优化_二次规划_10为二次型,且约束为线性约束时,该优化问题就是二次规划问题,一般形式表述如下:
ceres轨迹优化 什么是轨迹优化_ceres轨迹优化_11
二次规划是一类凸优化问题,目前有很多商业或者开源的求解器来求解这类问题,不再赘述

轨迹表示形式

使用路径规划可以得到一系列的路径点,这些路径点是不带时间信息的,而轨迹则是时间ceres轨迹优化 什么是轨迹优化_路径规划_12的函数,一般用ceres轨迹优化 什么是轨迹优化_ceres轨迹优化_03阶多项式表示,即:
ceres轨迹优化 什么是轨迹优化_移动机器人_14

其中ceres轨迹优化 什么是轨迹优化_二次规划_15是轨迹参数,也是我们的优化参数。将ceres轨迹优化 什么是轨迹优化_二次规划_16写成向量相乘形式,得到:
ceres轨迹优化 什么是轨迹优化_二次规划_17
对轨迹函数求导,还能写出速度、加速度、jerk等参数随时间变化的函数:

ceres轨迹优化 什么是轨迹优化_ceres轨迹优化_18
其中:ceres轨迹优化 什么是轨迹优化_运动规划_19,概括得到轨迹导数的通式,ceres轨迹优化 什么是轨迹优化_ceres轨迹优化_20表示ceres轨迹优化 什么是轨迹优化_ceres轨迹优化_20阶导数:
ceres轨迹优化 什么是轨迹优化_路径规划_22
一段复杂的轨迹常常需要用多个多项式来表示,可以将完整轨迹按照时间划分成多段,如下,将完整轨迹划分为ceres轨迹优化 什么是轨迹优化_路径规划_23段多项式轨迹:
ceres轨迹优化 什么是轨迹优化_运动规划_24
其中:ceres轨迹优化 什么是轨迹优化_ceres轨迹优化_25表示第ceres轨迹优化 什么是轨迹优化_ceres轨迹优化_26段轨迹的第ceres轨迹优化 什么是轨迹优化_移动机器人_27个参数,每段轨迹的时长都是预先分配好的,分配方法以后再叙述,本文暂时设置为固定值

Minimum-jerk

以下将以Minimum-jerk算法为例来介绍轨迹优化算法的设计流程,Minimum-snap等算法的设计流程与之同理。

Minimum-jerk,顾名思义就是求解每段轨迹的系数ceres轨迹优化 什么是轨迹优化_运动规划_28使得总的jerk最小,同时还要满足约束条件,所以这是一个带约束的最优化问题,通过后面的证明知道,它其实是一个二次规划问题。

多项式阶次选择

jerk是轨迹ceres轨迹优化 什么是轨迹优化_二次规划_16的3阶导数,所以令ceres轨迹优化 什么是轨迹优化_ceres轨迹优化_30。Minimum-jerk会直接对首尾的位置、速度和加速度进行约束,这就有6个等式约束了,所以优化参数必须提供6个以上的自由度,而5阶多项式有6个系数,所以符合要求的多项式的最小阶次为 ceres轨迹优化 什么是轨迹优化_路径规划_31。同理Minimum-snap还会对首尾的jerk进行约束,所以Minimum-snap的多项式最小阶数为 ceres轨迹优化 什么是轨迹优化_二次规划_32

因此可以选择五阶多项式来表示每一段轨迹,得到:
ceres轨迹优化 什么是轨迹优化_ceres轨迹优化_33
其中包含ceres轨迹优化 什么是轨迹优化_路径规划_23段多项式轨迹,每一段多项式轨迹有ceres轨迹优化 什么是轨迹优化_二次规划_35个系数,一共有ceres轨迹优化 什么是轨迹优化_二次规划_36个系数

构造目标函数

ceres轨迹优化 什么是轨迹优化_路径规划_37可表示为:
ceres轨迹优化 什么是轨迹优化_ceres轨迹优化_38
要使其最小,也就是使ceres轨迹优化 什么是轨迹优化_路径规划_37在整段时间上的积分最小,在minimum-jerk中选择使ceres轨迹优化 什么是轨迹优化_路径规划_37的2-范数最小即:
ceres轨迹优化 什么是轨迹优化_移动机器人_41
所以目标函数ceres轨迹优化 什么是轨迹优化_运动规划_42定义如下:
ceres轨迹优化 什么是轨迹优化_移动机器人_43

ceres轨迹优化 什么是轨迹优化_路径规划_44,对ceres轨迹优化 什么是轨迹优化_路径规划_45求平方,得到:
ceres轨迹优化 什么是轨迹优化_二次规划_46
ceres轨迹优化 什么是轨迹优化_ceres轨迹优化_47,得到:
ceres轨迹优化 什么是轨迹优化_移动机器人_48
所以ceres轨迹优化 什么是轨迹优化_二次规划_49的积分为:
ceres轨迹优化 什么是轨迹优化_ceres轨迹优化_50
ceres轨迹优化 什么是轨迹优化_二次规划_51的积分为矩阵ceres轨迹优化 什么是轨迹优化_ceres轨迹优化_52,则:
ceres轨迹优化 什么是轨迹优化_ceres轨迹优化_53
最终的目标函数可表述为:
ceres轨迹优化 什么是轨迹优化_路径规划_54
其中:

  • ceres轨迹优化 什么是轨迹优化_二次规划_55是优化变量,它是每一段多项式轨迹系数的列组合,其维数为ceres轨迹优化 什么是轨迹优化_二次规划_56
  • Q是一个维数为ceres轨迹优化 什么是轨迹优化_运动规划_57的实对称矩阵

显然,ceres轨迹优化 什么是轨迹优化_运动规划_42是一个二次型,所以基于Minimum-jerk的轨迹优化问题可以转换为一个二次规划问题

以上定义了Minimum-jerk中的目标函数,接下来添加约束。如果不考虑障碍物,主要有两类约束,一类是导数约束(Derivative Constraint),它约束了轨迹的初始状态和终止状态,以及每一段轨迹的开始/结束位置,也就是用路径规划得到的路径点对轨迹进行约束;另一类约束是连续性约束(Continuity Constraint),它可以使相邻轨迹平滑过渡。

导数约束

包含以下约束:

  • 初始状态和终止状态约束,如位置、速度、加速度等状态,即:ceres轨迹优化 什么是轨迹优化_ceres轨迹优化_59
  • 每一段轨迹的初始位置由路径点给出,也即:ceres轨迹优化 什么是轨迹优化_移动机器人_60ceres轨迹优化 什么是轨迹优化_二次规划_61表示第ceres轨迹优化 什么是轨迹优化_二次规划_61段轨迹,取值为ceres轨迹优化 什么是轨迹优化_ceres轨迹优化_63

其中ceres轨迹优化 什么是轨迹优化_移动机器人_64表示路径规划得到的路径点。

对于有ceres轨迹优化 什么是轨迹优化_二次规划_65个路径点的轨迹,一共有ceres轨迹优化 什么是轨迹优化_二次规划_66个导数约束

连续性约束

在两段轨迹的连接点处,我们希望这个点处的轨迹是平滑的,可以通过施加连续性约束来实现这个要求,也就是使相邻两段轨迹在连接点处的ceres轨迹优化 什么是轨迹优化_二次规划_67阶导数分别相等,即:
ceres轨迹优化 什么是轨迹优化_路径规划_68
其中ceres轨迹优化 什么是轨迹优化_ceres轨迹优化_26表示第ceres轨迹优化 什么是轨迹优化_ceres轨迹优化_26段轨迹,取值为ceres轨迹优化 什么是轨迹优化_ceres轨迹优化_71,对于有ceres轨迹优化 什么是轨迹优化_二次规划_65个路径点的轨迹,一共有ceres轨迹优化 什么是轨迹优化_路径规划_23段多项式轨迹,其中的连续性约束一共有ceres轨迹优化 什么是轨迹优化_路径规划_74个。

对于Minimum-jerk,要求每一段轨迹连接处的位置、速度、加速度一致,也就是:
ceres轨迹优化 什么是轨迹优化_移动机器人_75

编程实现

使用Python实现一维的Minimum-jerk算法,其中求解二次规划问题需要用到cvxopt库,安装方法:

pip install cvxopt

完整程序如下:

# -*- coding: utf-8 -*-
"""
Created on Fri Dec 10 2021
一维轨迹优化
@author: ghowoght
"""
#%% 生成路径点
import numpy as np
import matplotlib.pyplot as plt

path = [[1, 3], [3, 5], [4, 2], [2.5, 1.2], [2, -2.5]]
path = np.array(path)
# plt.plot(path[:, 0], path[:, 1])
# plt.plot(path[:, 0], path[:, 1], "*")
# plt.show()

#%% 一维轨迹优化
x = path[:, 0]
# 分配时间
deltaT = 2 # 每一段2秒
T = np.linspace(0, deltaT * (len(x) - 1), len(x))

########### 目标函数 ###########
######## 1/2xTQx + qTx ########
K = 3                   # jerk为3阶导数,取K=3
n_order = 2 * K - 1     # 多项式阶数
M = len(x) - 1          # 轨迹的段数
N = M * (n_order + 1)   # 矩阵Q的维数

def getQk(T_down, T_up):
    Q = np.zeros((6, 6))
    Q[3][4] =  72 * (T_up**2 - T_down**2)
    Q[3][5] = 120 * (T_up**3 - T_down**3)
    Q[4][5] = 360 * (T_up**4 - T_down**4)
    Q = Q + Q.T # Q为对称矩阵
    Q[3][3] =  36 * (T_up**1 - T_down**1)
    Q[4][4] = 192 * (T_up**3 - T_down**3)
    Q[5][5] = 720 * (T_up**5 - T_down**5)
    return Q
    
Q = np.zeros((N, N))
for k in range(1, M + 1):
    Qk = getQk(T[k - 1], T[k])
    Q[(6 * (k - 1)) : (6 * k), (6 * (k - 1)) : (6 * k)] = Qk

Q = Q * 2 # 因为标准目标函数为: 1/2xTQx + qTx,所以要乘2

########### 约束 ###########
# 1.导数约束 Derivative Constraint
A0 = np.zeros((2 * K + M - 1, N)) 
b0 = np.zeros(len(A0))

# 添加首末状态约束(包括位置、速度、加速度)
for k in range(K): 
    for i in range(k, 6):
        c = 1
        for j in range(k):
            c *= (i - j)
        A0[0 + k * 2][i]                = c * T[0]**(i - k)
        A0[1 + k * 2][(M - 1) * 6 + i]  = c * T[M]**(i - k)
b0[0] = x[0]
b0[1] = x[M]
# 添加每段轨迹的初始位置约束
for m in range(1, M):
    for i in range(6):
        A0[6 + m - 1][m * 6 + i] = T[m]**i
    b0[6 + m - 1] = x[m]

# 2.连续性约束 Continuity Constraint
A1 = np.zeros(((M - 1) * 3, N))
b1 = np.zeros(len(A1))
for m in range(M - 1):
    for k in range(3): # 最多两阶导数相等    
        for i in range(k, 6):
            c = 1
            for j in range(k):
                c *= (i - j)
            index = m * 3 + k
            A1[index][m * 6 + i] = c * T[m + 1]**(i - k)
            A1[index][(m + 1)* 6 + i] = -c * T[m + 1]**(i - k)
A = np.vstack((A0, A1))
b = np.hstack((b0, b1))
#%% 解二次规划问题
from cvxopt import matrix, solvers
# 目标函数
Q = matrix(Q)
q = matrix(np.zeros(N))
# 等式约束: Ax = b
A = matrix(A)
b = matrix(b)
result = solvers.qp(Q, q, A=A, b=b)
p_coff = np.asarray(result['x']).flatten()

#%% 可视化x轴优化结果
Pos = []
Vel = []
Acc = []
for k in range(M):
    t = np.linspace(T[k], T[k + 1], 100)
    t_pos = np.vstack((t**0, t**1, t**2, t**3, t**4, t**5))
    t_vel = np.vstack((t*0, t**0, 2 * t**1, 3 * t**2, 4 * t**3, 5 * t**4))
    t_acc = np.vstack((t*0, t*0, 2 * t**0, 3 * 2 * t**1, 4 * 3 * t**2, 5 * 4 * t**3))
    coef = p_coff[k * 6 : (k + 1) * 6]
    coef = np.reshape(coef, (1, 6))
    pos = coef.dot(t_pos)
    vel = coef.dot(t_vel)
    acc = coef.dot(t_acc)
    Pos.append([t, pos[0]])
    Vel.append([t, vel[0]])
    Acc.append([t, acc[0]])
Pos = np.array(Pos)
Vel = np.array(Vel)
Acc = np.array(Acc)
plt.subplot(3, 1, 1)
plt.plot(Pos[:, 0, :].T, Pos[:, 1, :].T)
# plt.title("position")
plt.xlabel("time(s)")
plt.ylabel("position(m)")
plt.subplot(3, 1, 2)
plt.plot(Vel[:, 0, :].T, Vel[:, 1, :].T)
# plt.title("velocity")
plt.xlabel("time(s)")
plt.ylabel("velocity(m/s)")
plt.subplot(3, 1, 3)
plt.plot(Acc[:, 0, :].T, Acc[:, 1, :].T)
# plt.title("accel")
plt.xlabel("time(s)")
plt.ylabel("accel(m/s^2)")
plt.show()

x轴上的规划结果如下,可以看到生成轨迹的位置、速度和加速度曲线都是连续的。

ceres轨迹优化 什么是轨迹优化_ceres轨迹优化_76


分别求X轴和Y轴方向的轨迹,合并后的结果如下图,程序见这里

ceres轨迹优化 什么是轨迹优化_移动机器人_77