轨迹规划之 贝塞尔曲线

  • 前言
  • 贝塞尔曲线
  • 低次贝塞尔曲线的表达式
  • 贝塞尔曲线的切线
  • 高次贝塞尔曲线
  • 高次贝塞尔曲线表达式
  • 贝塞尔曲线的递归性
  • 贝塞尔曲线的连接
  • 贝塞尔曲线的速度
  • 代码示例1:普通贝塞尔
  • 代码示例2:递归贝塞尔
  • 后记


前言

本篇开启轨迹规划内容。由寻路算法获得路点后,还要根据机器人的运动学、动力学约束优化生成机器人期望的运动轨迹。

本篇首先从贝塞尔曲线开始

贝塞尔曲线

贝塞尔曲线是常用的图形学设计、轨迹规划等方法。贝塞尔曲线是参数化曲线,n次贝塞尔曲线由n+1个控制点决定。

android 贝塞尔曲线动画 贝塞尔曲线轨迹规划_代码示例

低次贝塞尔曲线的表达式

二次贝塞尔曲线由android 贝塞尔曲线动画 贝塞尔曲线轨迹规划_线性变换_02决定:
android 贝塞尔曲线动画 贝塞尔曲线轨迹规划_贝塞尔曲线_03

三次贝塞尔曲线由android 贝塞尔曲线动画 贝塞尔曲线轨迹规划_代码示例_04决定:
android 贝塞尔曲线动画 贝塞尔曲线轨迹规划_代码示例_05

android 贝塞尔曲线动画 贝塞尔曲线轨迹规划_代码示例_06表达式可知,贝塞尔曲线上每个点实际上是控制点的凸组合,因此,贝塞尔曲线位于控制点形成的凸包内,并且仅通过起始和终止控制点。

贝塞尔曲线有良好的线性性质,对于线性变换android 贝塞尔曲线动画 贝塞尔曲线轨迹规划_线性变换_07和平移android 贝塞尔曲线动画 贝塞尔曲线轨迹规划_代码示例_08
android 贝塞尔曲线动画 贝塞尔曲线轨迹规划_线性变换_09
也就是说,对贝塞尔曲线作线性变换和平移,等同于对控制点做线性变换和平移。

贝塞尔曲线的切线

对二次贝塞尔曲线求导:
android 贝塞尔曲线动画 贝塞尔曲线轨迹规划_线性变换_10
可知在起始点和终止点处,贝塞尔曲线的切线与控制线是相切的。

android 贝塞尔曲线动画 贝塞尔曲线轨迹规划_贝塞尔曲线_11

高次贝塞尔曲线

高次贝塞尔曲线表达式

当控制点数量较多时,贝塞尔曲线的次数就越高。此时,贝塞尔曲线的表达式相对复杂,n次贝塞尔曲线:
android 贝塞尔曲线动画 贝塞尔曲线轨迹规划_代码示例_12
用矩阵乘法形式简化表示:
android 贝塞尔曲线动画 贝塞尔曲线轨迹规划_贝塞尔曲线_13

特别地,三次贝塞尔曲线可以写成如下的矩阵表达式:
android 贝塞尔曲线动画 贝塞尔曲线轨迹规划_android 贝塞尔曲线动画_14
其中,由控制点形成的矩阵android 贝塞尔曲线动画 贝塞尔曲线轨迹规划_线性变换_15称为几何矩阵,多项式系数矩阵android 贝塞尔曲线动画 贝塞尔曲线轨迹规划_贝塞尔曲线_16称为贝塞尔基矩阵。其它的三次曲线,如三次B样条曲线、三次埃尔米特曲线,都可以通过该矩阵形式表示,只要把基矩阵替换为对应的基即可。

贝塞尔曲线的递归性

一次贝塞尔曲线,可以表示为两个“零次贝塞尔曲线”(其实就是两个点)的凸组合:
android 贝塞尔曲线动画 贝塞尔曲线轨迹规划_几何学_17
二次贝塞尔曲线,可以表示为两个一次贝塞尔曲线的凸组合:
android 贝塞尔曲线动画 贝塞尔曲线轨迹规划_代码示例_18

三次贝塞尔曲线,可以表示为两个二次贝塞尔曲线的凸组合:
android 贝塞尔曲线动画 贝塞尔曲线轨迹规划_几何学_19

因此,任何高次贝塞尔曲线,最终都可以从一次贝塞尔曲线递归获得。这也是各大博客里下面这张图背后的数学原理。

android 贝塞尔曲线动画 贝塞尔曲线轨迹规划_几何学_20

贝塞尔曲线的连接

当贝塞尔曲线次数增大时,控制点对曲线局部位置形状的影响就变的很小(每次递归时,控制点前乘的系数都小于1)。为了更好的控制曲线形状,同时降低计算量,通常使用分段三次贝塞尔曲线连接来替代高次贝塞尔曲线。

已经贝塞尔曲线必然通过起始和终止控制点,如果直接将两个三次贝塞尔曲线的起点与终点拼接,得到的曲线会出现尖点,如下右图所示。

android 贝塞尔曲线动画 贝塞尔曲线轨迹规划_贝塞尔曲线_21


为了保证运动的平滑性,连接贝塞尔曲线时,需要保证第一条曲线在连接点处的切线与第二条曲线的切线方向相同:

android 贝塞尔曲线动画 贝塞尔曲线轨迹规划_几何学_22

贝塞尔曲线的速度

贝塞尔曲线android 贝塞尔曲线动画 贝塞尔曲线轨迹规划_代码示例_06并不是匀速运动的,其速度和加速度可由一阶导和二阶导获得。

代码示例1:普通贝塞尔

我写了一个基本表达式的高次贝塞尔曲线python代码示例,带有一个控制点和曲线的matplotlib可视化:

import numpy as np
import matplotlib.pyplot as plt
import math
import time


def bezier_base(n, i, t, pt):
    coeff = math.factorial(n) / (math.factorial(i)*math.factorial(n-i)) * np.power(1-t, n-i) * np.power(t, i)
    base = coeff * pt

    return base


def bezier_point(t, control_points):
    n = len(control_points) - 1
    bezier_pt = np.array([0, 0], dtype=np.float64)
    for i in range(n + 1):
        bezier_pt += bezier_base(n, i, t, control_points[i])

    return bezier_pt


def visualize(bezier_line, bezier_control_points):
    plt.figure()
    plt.plot(bezier_line[:, 0], bezier_line[:, 1], color='red')
    plt.scatter(bezier_control_points[:, 0], bezier_control_points[:, 1])
    plt.show()


if __name__ == '__main__':
    control_points = np.array([(1, 2), (4, 3), (6, 1)])
    bezier_line = []
    start = time.time()
    for t in np.arange(0, 1.01, 0.01):
        bezier_line.append(bezier_point(t, control_points))
    end = time.time()
    print('time cost: {}'.format(end - start))
    bezier_lines = np.stack(bezier_line, axis=0)
    visualize(bezier_lines, control_points)

代码示例2:递归贝塞尔

import numpy as np
import matplotlib.pyplot as plt
import math
import time


def visualize(bezier_line, bezier_control_points):
    plt.figure()
    plt.plot(bezier_line[:, 0], bezier_line[:, 1], color='red')
    plt.scatter(bezier_control_points[:, 0], bezier_control_points[:, 1])
    plt.show()


def recursive_bezier(pts, t):
    while True:
        recursive_pts = np.empty(shape=(0, 2))
        for i in np.arange(0, pts.shape[0] - 1):
            pt = t * pts[i] + (1 - t) * pts[i+1]
            recursive_pts = np.append(recursive_pts, np.expand_dims(pt, axis=0), axis=0)

        pts = recursive_pts
        if len(recursive_pts) == 1:
            print(recursive_pts)
            break

    return recursive_pts[0]


if __name__ == '__main__':
    control_points = np.array([(1, 2), (4, 4), (6, 3), (7, 2)])
    recursive_bezier_line = []

    for t in np.arange(0, 1.01, 0.01):
        recursive_bezier_line.append(recursive_bezier(control_points, t))
    bezier_lines = np.stack(recursive_bezier_line, axis=0)
    visualize(bezier_lines, control_points)

后记

下篇学习记录B样条曲线