用matplotlib的Animation画动图

用matplotlib的Animation画动图

2 年前 · 来自专栏 CS&AI技能学习
摘要:本文通过几个例子学习了 matplotlib.animation 画动图的方法
---
对算法,数学,计算机感兴趣的同学,欢迎关注我哈,阅读更多原创文章
我的网站: 潮汐朝夕的生活实验室
我的公众号: 潮汐朝夕
我的知乎: 潮汐朝夕
我的github: FennelDumplings
我的leetcode: FennelDumplings

我们在使用 matplotlib 时,常用的是 pyplot,可以画各种静态图,常见的代码模板如下

fig = plt.figure()
ax = fig.add_subplot(1, 1, 1)
ax.plot(...)
ax.plot(...)
plt.show()

有时我们希望画一些动态图,类似于放动画片的那种效果,例如再用算法拟合曲线时候,想要每隔几步就画一下当前拟合的曲线。

在 matplotlib 中,不仅可以绘制静态图形,也可以绘制动态图形。在 Matplotlib 中绘制动态图形的方法主要有两种,第一种是用模块 animation ,另一种是用 pyplot 的 API。但是如果要存 gif 图片的话,还是要用 animation 模块。

animation 的用法

用 animation 绘制复杂动画,最核心的是 FuncAnimation 这个类。下面我们看一下 FuncAnimation 的用法。

FuncAnimation(fig, func, frames, init_func, interval, blit)

主要参数的含义如下

  • fig : 绘制动图的画布名称
  • func : 回调函数,每次更新时调用,即下边程序定义的函数update,可以在这个方法中更新 line2d 对象。
  • frames : 真个动画 frame 的取值范围,在函数运行时,其值会传递给函数 update(n) 的形参 n。
  • init_func : 自定义开始帧,即传入刚定义的函数init,初始化函数。
  • interval : frame之间的更新频率,以 ms 计。
  • blit : 选择更新所有点,还是仅更新产生变化的点。

frames

以上 FuncAnimation 构造函数中的参数中,frames 参数可以取值 iterable, int, generator 或者 None。如果取整数 n,相当于给参数赋值 range(n)

frames 会在 interval 时间内迭代一次,然后将值传给 func,直至 frames 迭代完毕。

init 与 update

init 的作用是绘制下一帧画面前清空画布窗口的当前画面。update 的作用是绘制每帧画面

注意 init 和 update 的返回值的逗号不可省略,如果不带逗号,返回的是 list,带了逗号之后,返回的才是 Line2D 对象。类似地, ax.plot 返回值 line 后面也要加逗号。

保存 gif 或 mp4

用 FuncAnimation 生成的动图,如果要存 mp4,需要安装 ffmpeg,如果要存 gif,需要安装 imagemagick

ani.save("animation.mp4", fps=20, writer="ffmpeg")
ani.save("animation.gif", fps=50, writer="imagemagick")

例子

例子1: 每一轮 x 不变,清空并一次性更新 y

import matplotlib.pyplot as plt
import numpy as np
from matplotlib.animation import FuncAnimation
fig = plt.figure()
ax = fig.add_subplot(1, 1, 1)
x = np.linspace(0, 2 * np.pi, 5000)
y = np.exp(-x) * np.cos(2 * np.pi * x)
line, = ax.plot(x, y, color="cornflowerblue", lw=3)
ax.set_ylim(-1.1, 1.1)
# 清空当前帧
def init():
    line.set_ydata([np.nan] * len(x))
    return line,
# 更新新一帧的数据
def update(frame):
    line.set_ydata(np.exp(-x) * np.cos(2 * np.pi * x + float(frame)/100))
    return line,
# 调用 FuncAnimation
ani = FuncAnimation(fig
                   ,update
                   ,init_func=init
                   ,frames=200
                   ,interval=2
                   ,blit=True
ani.save("animation.gif", fps=25, writer="imagemagick")

例子2: 每一轮同时新增 x 和 y 的一个点

一个点一个点地画 sin 函数,每一帧新增一个点 (x, y)

fig = plt.figure()
ax = fig.add_subplot(1, 1, 1)
line, = plt.plot([], [], "r-", animated=True)
x = []
y = []
def init():
    ax.set_xlim(-np.pi, np.pi)
    ax.set_ylim(-1, 1)
    return line,
def update(frame):
    x.append(frame)
    y.append(np.sin(frame))
    line.set_data(x, y)
    return line,
ani = FuncAnimation(fig
                   ,update
                   ,frames=np.linspace(-np.pi ,np.pi, 90)
                   ,interval=10
                   ,init_func=init
                   ,blit=True
ani.save("animation.gif", fps=25, writer="imagemagick")

例子3: 同时画两条线,每轮新增两个点

代码与例子2整体差不多,但是是同时画两条线。每一轮新增两个点 (x, y1), (x, y2)

代码中的 init 函数只有一个壳,实际去掉也可以。

fig = plt.figure()
ax = fig.add_subplot(1, 1, 1)
x = np.linspace(0, 10, 100)
y1 = np.sin(x)
y2 = np.cos(x)
line1, = ax.plot(x, y1, color='k')
line2, = ax.plot(x, y2, color='b')
def init():
    return line1, line2,
def update(num):
    line1.set_data(x[:num], y1[:num])
    line2.set_data(x[:num], y2[:num])
    return line1, line2
ani = FuncAnimation(fig
                   ,update
                   ,init_func=init
                   ,frames=len(x)
                   ,interval=25
                   ,blit=True
ani.save("3.gif", fps=25, writer="imagemagick")

例子4: 同时画两条线,每轮重新画两条线

每秒更新一帧,FuncAnimation 对象的 interval 设为 1000(ms),save 时 fps 设为 1。

在 update 中设置 ax 属性

import matplotlib.pyplot as plt
import numpy as np
from matplotlib.animation import FuncAnimation
import matplotlib.font_manager as fm
font = fm.FontProperties(fname="./simsun.ttc")
fig = plt.figure()
ax = fig.add_subplot(1, 1, 1)
ax.set_title("动态图", fontproperties=font)
ax.grid(True)
ax.set_xlabel("X轴", fontproperties=font)
ax.set_ylabel("Y轴", fontproperties=font)
line1, = ax.plot([], [], "b--", linewidth=2.0, label="sin示例")
line2, = ax.plot([], [], "g+", linewidth=2.0, label="cos示例")
ax.legend(loc="upper left", prop=font, shadow=True)
def init():
    line1, = ax.plot([], [], "b--", linewidth=2.0, label="sin示例")
    line2, = ax.plot([], [], "g+", linewidth=2.0, label="cos示例")
    return line1, line2
def update(frame):
    x = np.linspace(-np.pi + 0.1 * frame, np.pi + 0.1 * frame, 256, endpoint=True)
    y_cos, y_sin = np.cos(x), np.sin(x)
    ax.set_xlim(-4 + 0.1 * frame, 4 + 0.1 * frame)
    ax.set_xticks(np.linspace(-4 + 0.1 * frame, 4 + 0.1 * frame, 9, endpoint=True))
    ax.set_ylim(-1.0, 1.0)
    ax.set_yticks(np.linspace(-1, 1, 9, endpoint=True))
    line1, = ax.plot(x, y_cos, "b--", linewidth=2.0, label="sin示例")
    line2, = ax.plot(x, y_sin, "g+", linewidth=2.0, label="cos示例")
    return line1, line2
ani = FuncAnimation(fig
                   ,update
                   ,init_func=init