用Python动画来展示二阶贝赛尔曲线

简介: 在Photoshop等多种设计软件中,画曲线时主要用到的是就是贝赛尔曲线,就是类似于上图中的这个曲线,设计师们可以通过控制中间的控制点来画出自己需要的曲线。

说起来贝氏曲线,我们可能首先会想到下面这个男人:


图1. 一位不愿透露姓名的英国球员

但实际上我们说的不是这个叫贝克汉姆的英国男人,而是另外一个人,就是下面这个叫“皮埃尔·贝塞尔”(Pierre Bézier)的法国男人:

图2. 皮埃尔·贝塞尔

贝塞尔论起知名度,也许不如小贝,但说起对人类的贡献,那可是杠杠的,著名的“贝塞尔曲线”就出自他之手。1962年,贝塞尔发表了贝塞尔曲线的相关理论研究,当时在雷诺公司工作的他,主要运用贝塞尔曲线进行汽车设计。说到这里可能还是有很多人没明白贝塞尔曲线到底是什么,看一下下面这个图,大家就明白了。

图3. 设计中用到的贝赛尔曲线

在Photoshop等多种设计软件中,画曲线时主要用到的是就是贝赛尔曲线,就是类似于上图中的这个曲线,设计师们可以通过控制中间的控制点来画出自己需要的曲线。早先设计师们想要用电脑画出一条直线灰常简单,但要画出一条平滑的曲线却非常难,而贝塞尔曲线的诞生,让大家用电脑绘制出一条平滑曲线成为了现实,这也就是贝塞尔曲线的最大用途。

而今天我们就来说一下最简单的二阶贝塞尔曲线的推导,并用matplotlib进行展示。(实际上最简单的是一阶,但因为其只有一条直线,所以没有什么实际用途,就忽略了)

我们先来了解一下二阶贝赛尔曲线的原理。假如连在一起的两条线段AB和BC,如下图:

图4. 二阶贝赛尔曲线原理图 1

现在AB上取一点D,BC上取一点E,使得AD/AB=BE/BC,如下图:

图5. 二阶贝赛尔曲线原理图 2

 

而在线段DE上还要求一点F,使得DF/DE=AD/AB=BE/BC,如下图:

图6. 二阶贝赛尔曲线原理图 3

 

而当D在AB上不断移动,E在BC上不断移动,形成的F点的轨迹便是一条曲线,这条曲线就是二阶贝塞尔曲线。这就是今天我们要推导并演示的曲线。

下面直接用Python代码来展示一下。首先还是导入各种包:

import numpy as np
import matplotlib.pyplot as plt
from matplotlib import animation

因为我们用的是matplotlib来做的演示,所以要设置一下matplotlib的后端,也就是显示方式。这一行代码最好单独使用,否则容易失效,所以把它单独列出来:

%matplotlib

这是非常重要的一步,这一步能让matplotlib在绘图时弹出一个新窗口,而不是在原窗口直接绘制,因为原窗口无法显示动画,这里用了ipython的magic 命令,也就是在一个命令前面加上“%”。在这里说明一下,笔者用的是win7系统,开发工具为Anaconda最新版,可以直接去Anaconda官网下载。

因为我们要用到A、B、C三个点,所以要设置一下三个点坐标。A点坐标为x1和y1,B点为x2和y2,C点为x3和y3,再设置一个区间内点的个数dots_num,这个dots_num后面会有解释。所有这些变量的值都可以随意设定,但有一些要求,因为笔者把绘图的坐标系设定在100,也就是x轴和y轴的范围都是0—100,所以上面三个点的坐标都不要超过这个范围,而dots_num的数量尽可能大一些,做动画时更连贯一些,所以上面这些参数的设置如下:


x1=10
y1=80
x2=50
y2=10
x3=90
y3=80
dots_num=100

接下来是获得贝塞尔曲线的轨迹的函数:


def two_degree_bc(x1=10, y1=80, x2=50, y2=10, x3=90, y3=80, dots_num=100): #bezier curve
    global xt, yt, x_dots12, x_dots23, y_dots12, y_dots23
    xt = [] #目标点的x坐标
    yt = [] #目标点的y坐标
    x_dots12 = np.linspace(x1, x2, dots_num) #线段AB的x坐标
    y_dots12 = np.linspace(y1, y2, dots_num) #线段AB的y坐标
    x_dots23 = np.linspace(x2, x3, dots_num) #线段BC的x坐标
    y_dots23 = np.linspace(y2, y3, dots_num) #线段BC的y坐标
    for i in range(dots_num): #获得目标点的轨迹
        x = x_dots12[i] + (x_dots23[i]-x_dots12[i])*i / (dots_num-1)
        y = y_dots12[i] + (y_dots23[i]-y_dots12[i])*i / (dots_num-1)
        xt.append(x)
        yt.append(y)

这里的xt和yt是两个list,就是用来存放目标点的x坐标和y坐标,而x_dots12和y_dots12分别是线段AB的x和y坐标,x_dots23和y_dots23分别是线段BC的x和y坐标,这四个是numpy的array格式,所有这些数据都设置成全局变量。而从上面的代码中我们可以看到变量dots_num的作用是在线段AB和BC上取这么多的点,然后用这些点推导目标点的坐标,点的数量越多,目标点的坐标也就越多,绘制出来的曲线也就更平滑。

接下来是动画函数,这个函数后面再解释:


def run(i):
    art1.set_data(x_dots12[i], y_dots12[i])
    art2.set_data(x_dots23[i], y_dots23[i])
    art3.set_data([x_dots12[i], x_dots23[i]], [y_dots12[i], y_dots23[i]])
    art4.set_data(xt[i], yt[i])
return art1,art2,art3,art4

最后就是绘制动画了,代码如下:


two_degree_bc() #先生成目标点的轨迹
fig, ax = plt.subplots(figsize=(8,8))
ax.set_aspect(1) #让两个坐标轴等比例
plt.xlim([0,100]) #设置坐标轴范围
plt.ylim([0,100])
ax.plot([x1, x2], [y1, y2], color='#3e82fc') #绘制AB线段
ax.plot([x2, x3], [y2, y3], color='#3e82fc') #绘制BC线段
ax.plot(xt,yt,color='orange') #绘制目标曲线
art1, = ax.plot(x_dots12[0], y_dots12[0], color='green', marker='o') #不能用scatter,因为得到的对象不是一个list,是一个object
art2, = ax.plot(x_dots23[0], y_dots23[0], color='green', marker='o')
art3, = ax.plot([x_dots12[0], x_dots23[0]], [y_dots12[0], y_dots23[0]], color = 'purple') #plot得到的结果是一个list,只包含一个元素,即一个形状object
art4, = ax.plot(xt[0], yt[0], color='red', marker='o')
ani = animation.FuncAnimation(
    fig, run, frames=range(100), interval=2, save_count=50)
plt.show()

这里首先运行two_degree_bc()函数得到目标点的轨迹,然后绘制AB、BC线段以及目标曲线,这些都是静态图。接着从变量art1开始就是绘制动画的部分了。这部分比较复杂,一共有art1、art2、art3和art4这四个变量,其分别对应线段AB、BC、DE和目标曲线的移动轨迹,点在这四个轨迹上移动,才能形成动画。而要生成动画,就要用到animation的方法FuncAnimation,其含有多个参数,fig就是我们绘图的那个画布,run就是我们生成动画时运行的函数,frames是帧画面,其每一帧画面包含了这些移动轨迹中的一个点所对应的静态图,把这些点的轨迹也就是每一帧连起来就是动画轨迹,frames一般是一个sequence,也就是包含多个变量,每个变量都赋值给run函数,run函数利用这个参数生成一个静态图,这么多静态图连起来就是动画,这和我们在电影或电视中看到的动画片是一样道理。interval是帧之间的时间间隔,200代表0.2秒,这个可以随意设定。save_count=50是把帧缓存起来用于回放,缓存帧的数量越多,回放越流畅,这个影响不大,随意设定。

下面再说一下运行动画的函数run的作用,可以看到run一共有5行代码,前4行代码是绘图代码,最后一个是返回参数的代码,前4行中每一行都代表了前面我们说过的点的轨迹, art1是线段AB上的点,用set_data(x_dots12[i], y_dots12[i])方法就生成了一个对应的帧,变量i就是前面讲的frames里的一个参数,生成的这个帧返回给FuncAnimation,让其用于连续播放,这就形成了动画。后面art2、art3和art4的道理是一样的。生成的动画效果的静态截图如下:

图7. 二阶贝赛尔曲线静态成图

 

最后再放上一个动图,让我们在一个深V的运动中结束本次话题:

图8. 二阶贝赛尔曲线动态示意图

 

二阶贝塞尔曲线的推导相对还容易一些,而三阶甚至更高阶的推导就复杂一点,笔者目前正在研究三阶贝赛尔曲线,以后会给大家分享一下。

完整代码已经上传:


https://gitee.com/leonmovie/two_degree_bc


有需要的可以去自行下载。

作者简介:小李子,数据分析爱好者,擅长数据可视化,比较关注机器学习领域,希望能和业内朋友多学习交流

相关文章
|
5月前
|
Python
使用Python绘制动态螺旋线:旋转动画效果
使用Python绘制动态螺旋线:旋转动画效果
77 1
|
7月前
|
Python
核密度曲线(python
核密度曲线(python
70 0
|
4月前
|
机器学习/深度学习 开发者 Python
Python 与 R 在机器学习入门中的学习曲线差异
【8月更文第6天】在机器学习领域,Python 和 R 是两种非常流行的编程语言。Python 以其简洁的语法和广泛的社区支持著称,而 R 则以其强大的统计功能和数据分析能力受到青睐。本文将探讨这两种语言在机器学习入门阶段的学习曲线差异,并通过构建一个简单的线性回归模型来比较它们的体验。
72 7
|
5月前
|
Python
使用Python绘制彩虹效果:动态彩虹动画
使用Python绘制彩虹效果:动态彩虹动画
85 3
|
5月前
|
流计算 Python
Python实现动态银河系:模拟旋转的银河动画
Python实现动态银河系:模拟旋转的银河动画
73 0
|
5月前
|
图形学 Python
Python绘制动态树形:实现分形树动画
Python绘制动态树形:实现分形树动画
94 0
|
5月前
|
Python
用Python制作动态钟表:实时显示时间的动画
用Python制作动态钟表:实时显示时间的动画
87 0
|
7月前
|
搜索推荐 数据可视化 Python
Python应用实战,用动画生成冒泡排序的过程
冒泡排序是一种简单的排序算法,它重复地遍历要排序的数列,一次比较两个元素,如果它们的顺序错误就把它们交换过来。遍历数列的工作是重复地进行直到没有再需要交换,也就是说该数列已经排序完成。
|
6月前
|
Python
Python学习笔记之Matplotlib模块入门(直线图、折线图、曲线图、散点图、柱状图、饼状图、直方图、等高线图和三维图的绘制)-2
Python学习笔记之Matplotlib模块入门(直线图、折线图、曲线图、散点图、柱状图、饼状图、直方图、等高线图和三维图的绘制)
|
6月前
|
数据可视化 开发者 Python
Python学习笔记之Matplotlib模块入门(直线图、折线图、曲线图、散点图、柱状图、饼状图、直方图、等高线图和三维图的绘制)-1
Python学习笔记之Matplotlib模块入门(直线图、折线图、曲线图、散点图、柱状图、饼状图、直方图、等高线图和三维图的绘制)