设为首页收藏本站

Crossin的编程教室

 找回密码
 立即加入
查看: 4967|回复: 0
打印 上一主题 下一主题

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

[复制链接]

169

主题

1

好友

733

积分

版主

Rank: 7Rank: 7Rank: 7

跳转到指定楼层
楼主
发表于 2020-8-5 19:19:00 |只看该作者 |倒序浏览

说起来贝氏曲线,我们可能首先会想到下面这个男人:
图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代码来展示一下。首先还是导入各种包:
  1. import numpy as np
  2. import matplotlib.pyplot as plt
  3. from matplotlib import animation
复制代码
因为我们用的是matplotlib来做的演示,所以要设置一下matplotlib的后端,也就是显示方式。这一行代码最好单独使用,否则容易失效,所以把它单独列出来:
  1. %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的数量尽可能大一些,做动画时更连贯一些,所以上面这些参数的设置如下:
  1. x1=10
  2. y1=80
  3. x2=50
  4. y2=10
  5. x3=90
  6. y3=80
  7. dots_num=100
复制代码
接下来是获得贝塞尔曲线的轨迹的函数:
  1. def two_degree_bc(x1=10, y1=80, x2=50, y2=10, x3=90, y3=80, dots_num=100): #bezier curve
  2.     global xt, yt, x_dots12, x_dots23, y_dots12, y_dots23
  3.     xt = [] #目标点的x坐标
  4.     yt = [] #目标点的y坐标
  5.     x_dots12 = np.linspace(x1, x2, dots_num) #线段AB的x坐标
  6.     y_dots12 = np.linspace(y1, y2, dots_num) #线段AB的y坐标
  7.     x_dots23 = np.linspace(x2, x3, dots_num) #线段BC的x坐标
  8.     y_dots23 = np.linspace(y2, y3, dots_num) #线段BC的y坐标
  9.     for i in range(dots_num): #获得目标点的轨迹
  10.         x = x_dots12[i] + (x_dots23[i]-x_dots12[i])*i / (dots_num-1)
  11.         y = y_dots12[i] + (y_dots23[i]-y_dots12[i])*i / (dots_num-1)
  12.         xt.append(x)
  13.         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上取这么多的点,然后用这些点推导目标点的坐标,点的数量越多,目标点的坐标也就越多,绘制出来的曲线也就更平滑。

接下来是动画函数,这个函数后面再解释:
  1. def run(i):
  2.     art1.set_data(x_dots12[i], y_dots12[i])
  3.     art2.set_data(x_dots23[i], y_dots23[i])
  4.     art3.set_data([x_dots12[i], x_dots23[i]], [y_dots12[i], y_dots23[i]])
  5.     art4.set_data(xt[i], yt[i])
  6. return art1,art2,art3,art4
复制代码
最后就是绘制动画了,代码如下:
  1. two_degree_bc() #先生成目标点的轨迹
  2. fig, ax = plt.subplots(figsize=(8,8))
  3. ax.set_aspect(1) #让两个坐标轴等比例
  4. plt.xlim([0,100]) #设置坐标轴范围
  5. plt.ylim([0,100])
  6. ax.plot([x1, x2], [y1, y2], color='#3e82fc') #绘制AB线段
  7. ax.plot([x2, x3], [y2, y3], color='#3e82fc') #绘制BC线段
  8. ax.plot(xt,yt,color='orange') #绘制目标曲线
  9. art1, = ax.plot(x_dots12[0], y_dots12[0], color='green', marker='o') #不能用scatter,因为得到的对象不是一个list,是一个object
  10. art2, = ax.plot(x_dots23[0], y_dots23[0], color='green', marker='o')
  11. art3, = ax.plot([x_dots12[0], x_dots23[0]], [y_dots12[0], y_dots23[0]], color = 'purple') #plot得到的结果是一个list,只包含一个元素,即一个形状object
  12. art4, = ax.plot(xt[0], yt[0], color='red', marker='o')
  13. ani = animation.FuncAnimation(
  14.     fig, run, frames=range(100), interval=2, save_count=50)
  15. 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, y_dots12)方法就生成了一个对应的帧,变量i就是前面讲的frames里的一个参数,生成的这个帧返回给FuncAnimation,让其用于连续播放,这就形成了动画。后面art2、art3和art4的道理是一样的。生成的动画效果的静态截图如下:
图7. 二阶贝赛尔曲线静态成图
最后再放上一个动图,让我们在一个深V的运动中结束本次话题:
图8. 二阶贝赛尔曲线动态示意图
二阶贝塞尔曲线的推导相对还容易一些,而三阶甚至更高阶的推导就复杂一点,笔者目前正在研究三阶贝赛尔曲线,以后会给大家分享一下。

完整代码已经上传:

https://gitee.com/leonmovie/two_degree_bc

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

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

------

欢迎搜索及关注:Crossin的编程教室

这里还有更多精彩。一起学,走得远

回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即加入

QQ|手机版|Archiver|Crossin的编程教室 ( 苏ICP备15063769号  

GMT+8, 2024-11-22 12:12 , Processed in 0.018150 second(s), 21 queries .

Powered by Discuz! X2.5

© 2001-2012 Comsenz Inc.

回顶部