前文介绍了ffmpeg的安装,对于一些人来说也许有点复杂,但就相当于万事具备,只欠东风了。接着下来,就是回到matplotlib里怎么样生成动画,再调用ffmpeg生成视频。其实动画的过程是非常简单的,在一张白纸上画上图像的一部分,接着另外一张白纸上再画上一部分,依次类推,就可以画了很多张白纸,然后让这些白纸不断地从眼前飘过,眼睛里看到的画面就是动画了。同理,在matplotlib也是采用这个原理,只不过把白纸变成画布,把绘图变成函数生成曲线。
matplotlib里生成动画,共三种方式:
- Animation : 继续这个基数,重载相关的函数。
-
FuncAnimation : 使用回调函数,主要提供一个初始化函数和一个更新画面的函数。
-
ArtistAnimation : 用一帧一帧的数据生成的动画。
后面会对这三种方式,进行一一介绍,现在先来学习一个简单的方式,也就是使用回调函数的方式—FuncAnimation,这种方式比较简单,只要提供两个函数就可以实现简单的动画显示了。FuncAnimation的定义如下:
class matplotlib.animation.FuncAnimation(fig, func, frames=None, init_func=None, fargs=None, save_count=None, *, cache_frame_data=True, **kwargs)
- fig:绘制动画的画布
-
func:动画函数
-
frams:动画长度,一次循环包含的帧数,在函数运行时,其值会传给动画函数update(i)的形参i
-
init_func:动画的起始状态
-
interval:更新频率,interval=20表示每隔20ms从头来一次
-
blit:是否更新整张图,False表示更新整张图,True表示只更新有变化的点。mac用户请使blit=False。
从上面的参数里,就可以看到主要有几个参数,先要提供画布fig,然后提供每帧动画的更新函数func,还需要知道动画显示多长时间frames,以及循环一周回来时要清空init_func函数。在这里关键点,就是定义两个回调函数func和init_func,只要实现这两个函数,就可以完成动画绘制。那么问题就来了,怎么样来定义func和init_func函数?
先来看init_func函数的要求,如下:
def init_func() -> iterable_of_artists
从定义里可以看到这个函数,没有输入的参数,返回值是一个可迭代的artist对象,artist对象,就是继承matplotlib.artist.Artist类,比如:
提供上面这些对象,都是可以合法的,才可以正确地运行,如果提供不合法的对象,就会导致出错。有于返回值是一个可迭代的对象,一般情况下是返回元组对象,当然你也可以返回列表对象,或者合法的生成器对象。另外由于python语言返回元组对象最为方便,如下:
return 1,2
return (1,2)
这两行语句是功能相当的,因此常常会使用前面第一种方式编写,在回调函数经常使用下面的返回值:
return line,
这时返回值就是相当于元组(line,),返回一个元素的元组,需要后面添加逗号。如果你省略掉后面的逗号,就不行了,此时返回的line对象,而不是元组了。所以定义初始化函数如下:
def init():
line.set_data([], [])
return line,
在函数只是简单地调用Line2D的函数set_data,数值全是空的,相当于什么曲线也没有显示,把画面进行清空的操作,为了后面显示曲线提供了空白的画布。在函数最后返回元组(line,)给动画函数。
接着下来定义每帧动画的更新函数:
def func(frame, *fargs) -> iterable_of_artists
在这个函数里,与前面初始化函数有点不一样了,虽然都是同样的回调函数,但是多了参数,第一个参数frame,就是调用时传递进来的帧参数,后面一个关键字参数。在这里只要使用第一个帧参数就可以了,返回值跟前面初始化函数是一样的,都是返回一个可迭代对象,可以是元组,也可以是列表,或者其它合法的迭代对象。因而这个函数定义如下:
def animate(i):
x = np.linspace(0, 2, 1000)
y = np.sin(2 * np.pi * (x - 0.01 * i)) # 更新数据
line.set_data(x, y)
return line,
函数第一行定义函数名称animate,输入帧参数i。
第二行代码对0到2进行1000个点分割。
第三行代码根据帧参数来对1000个点进行偏移计算,再调用sin函数生成值。
第四行代码是调用line对象来画曲线。
第五行代码是返回line对象在画布里显示曲线。
当i的值在不断地发生变化时,sin曲线就会不一样,从而产生不一样的画面,多个画面连续显示形成动画。
准备好这两个函数之后,就可以调用FuncAnimation来进行动画显示了,如下:
anim = animation.FuncAnimation(fig, animate, init_func = init,
frames = 200, interval = 20, blit = True)
这里将产生200帧的动画,每帧间隔时间为20毫秒,使用blit的值为True,也就是使用加速功能,之前产生的显示的画面不会删除,因此需要正确地使用显示顺序,否则可能显示不正确。
到这里就可以正确地显示动画了,但是还不能生成MP4文件,还需要编写与它相关的代码才可以产生MP4文件,继续来做这方面的工作,先要设置matplotlib运行ffmpeg的路径:
plt.rcParams['animation.ffmpeg_path'] = 'D:\\ffmpeg-2020-12-20\\bin\\ffmpeg.exe'
这个路径就是前一文里安装路径,如果你安装的目录跟我这里不一样,要记得修改,否则运行会出错。
接着再定义一个写入到文件的对象animation.FFMpegWriter,代码如下:
FFwriter = animation.FFMpegWriter(fps=20, extra_args=['-vcodec', 'libx264'])
anim.save('basic_animation.mp4', writer = FFwriter)
这里fps是表示每秒20帧的速度保存,压缩的库为vcodec,格式为’libx264’,保存的文件名称为basic_animation.mp4。
最后保存MP4文件如下:
打开这个MP4文件,就可以进行播放了。到这里,已经完全实现了动画的生成和MP4文件的生成,这个过程还是比较简单的,现在来看一下全部的代码:
import numpy as np
from matplotlib import pyplot as plt
from matplotlib import animation
plt.rcParams['animation.ffmpeg_path'] = 'D:\\ffmpeg-2020-12-20\\bin\\ffmpeg.exe'
fig = plt.figure()
# 创建绘图轴对象
ax = plt.axes(xlim=(0, 2), ylim=(-2, 2))
# 先显示一条空的曲线
line, = ax.plot([], [], lw=2)
# 初始化函数:每帧用来清空背景
def init():
line.set_data([], [])
return line,
# 动画绘制函数:参数i是表示帧序号.
def animate(i):
x = np.linspace(0, 2, 1000)
y = np.sin(2 * np.pi * (x - 0.01 * i)) # 更新数据
line.set_data(x, y)
return line,
# 在这里设置一个200帧的动画,每帧之间间隔20毫秒
anim = animation.FuncAnimation(fig, animate, init_func = init,
frames = 200, interval = 20, blit = True)
#保存的动画视频文件名为当前文件夹下的basic_animation.mp4,帧率为20帧每秒,格式为MP4。
FFwriter = animation.FFMpegWriter(fps=20, extra_args=['-vcodec', 'libx264'])
anim.save('basic_animation.mp4', writer = FFwriter)
plt.show() # 会一直循环播放动画
动画的结果,需要自己运行代码来体验一下。