无法使用 ffmpeg 保存 matplotlib 动画

Posted

技术标签:

【中文标题】无法使用 ffmpeg 保存 matplotlib 动画【英文标题】:Cannot save matplotlib animation with ffmpeg 【发布时间】:2014-05-29 06:17:56 【问题描述】:

我正在尝试从 Jake Vanderplas 保存一个简单的 matplotlib 动画,但我不断收到 OSError: [Errno 13] Permission denied

我应该注意我对 Jake Vanderplas 的示例做了两个小的修改。我从 MacPorts 安装了 ffmpeg,所以我添加了 plt.rcParams['animation.ffmpeg_path'] = '/opt/local/bin' 行,遇到了 (Using FFmpeg and IPython) 中讨论的问题,所以我添加了 FFwriter = animation.FFMpegWriter()

代码如下:

import numpy as np
from matplotlib import pyplot as plt
from matplotlib import animation
plt.rcParams['animation.ffmpeg_path'] = '/opt/local/bin'

fig = plt.figure()
ax = plt.axes(xlim=(0, 2), ylim=(-2, 2))
line, = ax.plot([], [], lw=2)

def init():
    line.set_data([], [])
    return line,

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,

anim = animation.FuncAnimation(fig, animate, init_func=init,
                           frames=200, interval=20, blit=True)

FFwriter = animation.FFMpegWriter()
anim.save('basic_animation.mp4', writer = FFwriter, fps=30, extra_args=['-vcodec', 'libx264'])

这是回溯:

File "ani_debug.py", line 34, in <module>
  anim.save('basic_animation.mp4', writer = FFwriter, fps=30, extra_args=['-vcodec', 'libx264'])
File "/Users/Ben/Library/Enthought/Canopy_64bit/User/lib/python2.7/site- packages/matplotlib/animation.py", line 712, in save
  with writer.saving(self._fig, filename, dpi):
File "/Applications/Canopy.app/appdata/canopy-1.3.0.1715.macosx-x86_64/Canopy.app/Contents/lib/python2.7/contextlib.py", line 17, in __enter__
  return self.gen.next()
File "/Users/Ben/Library/Enthought/Canopy_64bit/User/lib/python2.7/site-packages/matplotlib/animation.py", line 169, in saving
  self.setup(*args)
File "/Users/Ben/Library/Enthought/Canopy_64bit/User/lib/python2.7/site-packages/matplotlib/animation.py", line 159, in setup
  self._run()
File "/Users/Ben/Library/Enthought/Canopy_64bit/User/lib/python2.7/site-packages/matplotlib/animation.py", line 186, in _run
  stdin=subprocess.PIPE)
File "/Applications/Canopy.app/appdata/canopy-1.3.0.1715.macosx-x86_64/Canopy.app/Contents/lib/python2.7/subprocess.py", line 709, in __init__
  errread, errwrite)
File "/Applications/Canopy.app/appdata/canopy-1.3.0.1715.macosx-x86_64/Canopy.app/Contents/lib/python2.7/subprocess.py", line 1326, in _execute_child
  raise child_exception
OSError: [Errno 13] Permission denied

我也尝试过使用 Spyder 的内置 python 并收到类似的回溯。有什么建议吗?


编辑:我意识到我没有给出 ffmpeg 的正确路径。显然,plt.rcParams['animation.ffmpeg_path']PYTHONPATH 的工作方式不同。你必须用plt.rcParams['animation.ffmpeg_path'] = '/opt/local/bin/ffmpeg'告诉动画模块ffmpeg在哪里。

现在,我得到一个可以播放的电影文件,但内容完全是乱码。我不知道我在看什么。

这是回溯:

Exception in Tkinter callback
Traceback (most recent call last):
  File "Tkinter.pyc", line 1470, in __call__
  File "Tkinter.pyc", line 531, in callit
  File "/Applications/Spyder.app/Contents/Resources/lib/python2.7/matplotlib/backends/backend_tkagg.py", line 141, in _on_timer
    TimerBase._on_timer(self)
  File "/Applications/Spyder.app/Contents/Resources/lib/python2.7/matplotlib/backend_bases.py", line 1203, in _on_timer
    ret = func(*args, **kwargs)
  File "/Applications/Spyder.app/Contents/Resources/lib/python2.7/matplotlib/animation.py", line 876, in _step
    still_going = Animation._step(self, *args)
  File "/Applications/Spyder.app/Contents/Resources/lib/python2.7/matplotlib/animation.py", line 735, in _step
    self._draw_next_frame(framedata, self._blit)
  File "/Applications/Spyder.app/Contents/Resources/lib/python2.7/matplotlib/animation.py", line 753, in _draw_next_frame
    self._pre_draw(framedata, blit)
  File "/Applications/Spyder.app/Contents/Resources/lib/python2.7/matplotlib/animation.py", line 766, in _pre_draw
    self._blit_clear(self._drawn_artists, self._blit_cache)
  File "/Applications/Spyder.app/Contents/Resources/lib/python2.7/matplotlib/animation.py", line 806, in _blit_clear
    a.figure.canvas.restore_region(bg_cache[a])
KeyError: <matplotlib.axes.AxesSubplot object at 0x104cfb150>

编辑: 出于某种原因,现在一切正常。我在我的家用电脑和工作电脑上都试过了,在我修复了 ffmpeg 路径问题后,没有人可以重新创建我得到的乱码视频文件。


编辑: 啊啊啊!我追踪到了这个傻逼。有时我会导入一个包含plt.rcParams['savefig.bbox'] = 'tight' 的模块。 (我永远不会使用该模块,但 rcParams 会一直存在,直到您重新启动 python 解释器。)该设置会导致视频出现乱码。我将在下面发布我的解决方案。

【问题讨论】:

等等.. 现在你得到了回溯但也得到了输出? 好吧,粗鲁。我刚刚在我的工作计算机上尝试了上面的代码(使用正确的 ffmpeg 路径),它运行良好。在此之前我的所有测试都是在我的家用电脑上进行的。也许我需要在家里的电脑上重新安装 ffmpeg。 【参考方案1】:

原来有两个问题。

问题 #1:ffmpeg 的路径错误。我以为我需要提供 ffmpeg 所在目录的路径,但我需要提供一直到 ffmpeg 二进制文件的路径。

问题 #2:在测试我的代码以生成视频之前,我有时会导入一个设置为 plt.rcParams['savefig.bbox'] = 'tight' 的模块。 (我没想太多,因为我没有使用该模块,但是rcParams一直存在,直到您重新启动python解释器。)这个plt.rcParams['savefig.bbox'] = 'tight'导致视频文件保存没有任何错误,但是当您使用时帧都是乱码尝试播放视频。虽然我花了整个晚上才找到这个,但事实证明这是a known issue。

这是一个更新的解决方案,它为我创建了一个带有漂亮的正弦波转换的视频文件。

import numpy as np
from matplotlib import pyplot as plt
from matplotlib import animation
plt.rcParams['animation.ffmpeg_path'] = '/opt/local/bin/ffmpeg'

fig = plt.figure()
ax = plt.axes(xlim=(0, 2), ylim=(-2, 2))
line, = ax.plot([], [], lw=2)

def init():
    line.set_data([], [])
    return line,

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,

anim = animation.FuncAnimation(fig, animate, init_func=init, frames=200, interval=20, blit=True)

FFwriter = animation.FFMpegWriter(fps=30, extra_args=['-vcodec', 'libx264'])
anim.save('basic_animation.mp4', writer=FFwriter)

【讨论】:

【参考方案2】:

感谢 Stretch 的宝贵回答。我发现在 anim.save() 中提及额外的参数会导致错误。因此代码更新如下,

import numpy as np
from matplotlib import pyplot as plt
from matplotlib import animation
plt.rcParams['animation.ffmpeg_path'] = r'I:\FFmpeg\bin\ffmpeg' #make sure you download FFmpeg files for windows 10 from https://ffmpeg.zeranoe.com/builds/

fig = plt.figure()
ax = plt.axes(xlim=(0, 2), ylim=(-2, 2))
line, = ax.plot([], [], lw=2)

def init():
    line.set_data([], [])
    return line,

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,

anim = animation.FuncAnimation(fig, animate, init_func=init, frames=200, interval=20, blit=True)

FFwriter=animation.FFMpegWriter(fps=30, extra_args=['-vcodec', 'libx264'])
anim.save(r'I:\Understanding_objective functions\test\basic_animation.mp4', writer=FFwriter)

plt.show()

希望这对尝试将动画图保存为 .mp4 格式的人有所帮助。

【讨论】:

【参考方案3】:

进一步Stretch's answer,我发现传递给anim.save()的一些参数似乎没有达到预期的效果。具体来说,fps 是 5(默认),而不是设置的 30。通过将fps=30 传递给animation.FFMpegWriter,它确实有效。

所以:

FFwriter = animation.FFMpegWriter(fps=30)
anim.save('basic_animation.mp4', writer=FFwriter, extra_args=['-vcodec', 'libx264'])

请注意,视频现在为 7 秒(200 帧 @ 30 fps)而不是 40 秒(200 帧 @ 5 fps)。另请注意,默认的 5 fps 对应于FuncAnimation 中默认的 200 毫秒/帧间隔,严格来说,此处使用的 20 毫秒动画间隔对应于 50 fps。

对于那些努力获得更好视频质量的人,也可以将比特率(以 kbps 为单位的整数)传递给animation.FFMpegWriter,例如:

FFwriter = animation.FFMpegWriter(fps=30, bitrate=2000)

我尝试了各种 extra_args 以获得更好的质量,但没有多大成功。

【讨论】:

【参考方案4】:

当我第一次(天真地)尝试修改工作时,我遇到了乱码问题 Stretch's answer 的示例以实时显示图表(以及保留电影)。

Stretch's answer 的模组不太正确(对我有用)

    plt.ion()互动开启 plt.draw() plt.show()animate 函数中,在return 声明之前 frames=20, interval=200 稍微减慢图形创建速度,但仍制作 4 秒电影

现在绘图在创建时显示在窗口中, 但是输出的电影是乱码。

正确的第 2 步:

2a: plt.draw() 内部 animate函数 2b: plt.show() 只是 animate 函数之后

现在电影播放时不会乱码。

【讨论】:

请记住,在任何情况下,不仅仅是动画,您可能只需要.show()您的人物一次。

以上是关于无法使用 ffmpeg 保存 matplotlib 动画的主要内容,如果未能解决你的问题,请参考以下文章

如何使用 FFMPEG 保存 rtsp 流而不丢包

无法在 jupiter notebook 中将 matplotlib 图形保存为 jpeg(它是空白的)

python 使用vtk和matplotlib保存高质量图片

使用ffmpeg保存PCM文件

如何在 Pyqt5 环境中使用 Pickle 保存 Matplotlib 图?

FFMPEG 无法取帧并另存为图像