Matplotlib生成MP4动画时如何实现两个函数曲线依次分层绘制
问题描述
运行环境
- Windows 10
- Python 3.6
- Matplotlib 3.3.2
实现需求
需要制作MP4格式的Matplotlib分阶段动画,效果要求:
- 第一阶段:动态逐点绘制平方根(
sqr,即y=x^(1/2))函数曲线 - 第一阶段结束后,sqr曲线完整固定显示在画布上
- 第二阶段:在sqr曲线上层,动态逐点绘制立方根(
cub,即y=x^(1/3))函数曲线
禁止两条曲线同步动态推进,必须实现递进展示效果
现存问题
先后创建两个FuncAnimation对象时出现覆盖问题,实际运行效果为:sqr曲线直接完整显示,仅cub曲线逐点动态绘制,不符合预期。原有测试代码如下:
import numpy as np import matplotlib.pyplot as plt import matplotlib.animation as animation # params plt.rcParams['animation.ffmpeg_path'] = 'C:/xxx/ffmpeg.exe' x_val = np.arange(0, 10) lbl = ['sqr', 'cub', ] # plot fig, ax = plt.subplots(figsize=(4,2)) line_2, = ax.plot([], []) line_3, = ax.plot([], []) ax.set_xlim(x_val.min(), x_val.max()) ax.set_ylim(0, 4) ax.set_title('sqr & cub') ax.legend(lbl, bbox_to_anchor=(1.3, 1)) plt.tight_layout() def func_2(x): return x**(1/2) def func_3(x): return x**(1/3) X, Y_2, Y_3 = [], [], [] def animation_frame_2(x): X.append(x) Y_2.append(func_2(x)) line_2.set_xdata(X) line_2.set_ydata(Y_2) return line_2, def animation_frame_3(x): X.append(x) Y_2 = func_2(x_val) line_2.set_xdata(x_val) line_2.set_ydata(Y_2) Y_3.append(func_3(x)) line_3.set_xdata(X) line_3.set_ydata(Y_3) return line_2, line_3, anim = animation.FuncAnimation(fig, func=animation_frame_2, frames=x_val, interval=100, repeat=False) anim = animation.FuncAnimation(fig, func=animation_frame_3, frames=x_val, interval=100, repeat=False) plt.show()
问题原因
Matplotlib中同一画布同一时间仅会运行最后一个被赋值的FuncAnimation实例,之前创建的动画实例会被垃圾回收直接失效,因此原有代码中第一个sqr绘制动画根本没有执行,直接运行的是第二个动画逻辑——而第二个动画的更新函数第一行就直接把完整的sqr曲线赋值给了line_2,所以才会出现sqr直接全量显示、只有cub逐点绘制的问题。
解决方案
不需要创建两个FuncAnimation对象,把两段动画逻辑合并到同一个更新函数里,通过帧序号判断当前处于哪个绘制阶段即可:
- 把总帧数设置为两个阶段的帧数之和,前N帧绘制sqr曲线,后N帧绘制cub曲线
- 前一阶段绘制完成后,直接把完整的sqr曲线数据固定到对应line对象上,再开始逐点绘制cub
- 导出MP4时直接调用
anim.save()方法即可
修正后的可运行代码:
import numpy as np import matplotlib.pyplot as plt import matplotlib.animation as animation # 配置ffmpeg路径 plt.rcParams['animation.ffmpeg_path'] = 'C:/xxx/ffmpeg.exe' # 基础数据准备 x_val = np.arange(0, 10) point_count = len(x_val) lbl = ['sqr', 'cub'] # 画布初始化 fig, ax = plt.subplots(figsize=(4,2)) line_sqr, = ax.plot([], [], label='sqr') line_cub, = ax.plot([], [], label='cub') ax.set_xlim(x_val.min(), x_val.max()) ax.set_ylim(0, 4) ax.set_title('sqr & cub') ax.legend(lbl, bbox_to_anchor=(1.3, 1)) plt.tight_layout() # 函数定义 def func_sqr(x): return x**(1/2) def func_cub(x): return x**(1/3) # 预计算全量sqr数据,第一阶段结束后直接调用 full_sqr_y = func_sqr(x_val) cub_x, cub_y = [], [] def update(frame): # 前point_count帧:逐点绘制sqr曲线 if frame < point_count: current_x = x_val[:frame+1] current_y = func_sqr(current_x) line_sqr.set_data(current_x, current_y) return line_sqr, # 帧序号超过point_count后:进入第二阶段,先固定完整sqr,再逐点绘制cub else: # 固定sqr为完整曲线 line_sqr.set_data(x_val, full_sqr_y) # 计算当前cub要绘制到的点位置 cub_frame_idx = frame - point_count cub_x.append(x_val[cub_frame_idx]) cub_y.append(func_cub(x_val[cub_frame_idx])) line_cub.set_data(cub_x, cub_y) return line_sqr, line_cub, # 总帧数为两个阶段帧数之和,每帧间隔100ms anim = animation.FuncAnimation( fig, func=update, frames=point_count * 2, interval=100, repeat=False, blit=True ) # 预览效果打开plt.show(),导出MP4注释掉show打开save即可 plt.show() # anim.save('func_anim.mp4', writer='ffmpeg', fps=10, dpi=200)
效果说明
- 前10帧逐点绘制平方根曲线,和第一阶段预期效果完全一致
- 第11帧开始,平方根曲线直接固定为完整形态,开始逐点绘制上层的立方根曲线
- 全程只有一个
FuncAnimation实例,不存在实例覆盖、垃圾回收导致动画失效的问题 - 导出MP4时直接调用
save方法即可,不需要额外调整逻辑
内容的提问来源于stack exchange,提问作者Cy Bu
相关产品推荐
相关产品推荐

