Generating movie from python without saving individual frames to files

我想从 matplotlib 中的 Python 脚本中生成的帧创建一个 h264或 divx 电影。这部电影大约有10万帧。

在 web 上的示例中[例如1] ,我只看到了将每个帧保存为 png,然后在这些文件上运行 mencoder 或 ffmpeg 的方法。在我的情况下,保存每个帧是不切实际的。有没有一种方法可以获取从 matplotlib 生成的 plot,并将其直接导向 ffmpeg,而不生成中间文件?

使用 ffmpeg 的 C-api 编程对我来说太难了[例如2]。另外,我需要一个具有良好压缩的编码,比如 x264,否则影片文件对于后续步骤来说会太大。因此,如果坚持使用 mencoder/ffmpeg/x264就太好了。

有什么东西可以用管子来做吗?

[1] http://matplotlib.sourceforge.net/examples/animation/movie_demo.html

[2] 如何使用 x264CAPI 将一系列图像编码到 H264中?

[3] http://www.ffmpeg.org/ffmpeg-doc.html#SEC41

70325 次浏览

在修补 ffmpeg 之后(请参阅 Joe Kington 对我的问题的评论) ,我能够将 png 通过管道传输到 ffmpeg,如下所示:

import subprocess
import numpy as np
import matplotlib
matplotlib.use('Agg')
import matplotlib.pyplot as plt


outf = 'test.avi'
rate = 1


cmdstring = ('local/bin/ffmpeg',
'-r', '%d' % rate,
'-f','image2pipe',
'-vcodec', 'png',
'-i', 'pipe:', outf
)
p = subprocess.Popen(cmdstring, stdin=subprocess.PIPE)


plt.figure()
frames = 10
for i in range(frames):
plt.imshow(np.random.randn(100,100))
plt.savefig(p.stdin, format='png')

如果没有 patch,它将无法工作,因为 patch会修改两个文件并添加 libavcodec/png_parser.c。我不得不手动将补丁应用到 libavcodec/Makefile。最后,我从 Makefile中删除了“-number”以获得要构建的手册页。通过编译选项,

FFmpeg version 0.6.1, Copyright (c) 2000-2010 the FFmpeg developers
built on Nov 30 2010 20:42:02 with gcc 4.2.1 (Apple Inc. build 5664)
configuration: --prefix=/Users/paul/local_test --enable-gpl --enable-postproc --enable-swscale --enable-libxvid --enable-libx264 --enable-nonfree --mandir=/Users/paul/local_test/share/man --enable-shared --enable-pthreads --disable-indevs --cc=/usr/bin/gcc-4.2 --arch=x86_64 --extra-cflags=-I/opt/local/include --extra-ldflags=-L/opt/local/lib
libavutil     50.15. 1 / 50.15. 1
libavcodec    52.72. 2 / 52.72. 2
libavformat   52.64. 2 / 52.64. 2
libavdevice   52. 2. 0 / 52. 2. 0
libswscale     0.11. 0 /  0.11. 0
libpostproc   51. 2. 0 / 51. 2. 0

太棒了!我也想这么做。但是,我无法用 MingW32 + MSYS + pr 环境在 Vista 中编译打了补丁的 ffmpeg 源代码(0.6.1) ... ... png _ parser.c 在编译过程中生成了 Error1。

因此,我想出了一个解决这个问题的 JPEG 解决方案,使用 PIL。只需将 ffmpeg.exe 放在与此脚本相同的文件夹中。这应该可以在不使用 Windows 下的补丁程序的情况下使用 ffmpeg。我必须使用 stdin.write 方法,而不是官方文档中推荐的通信方法。注意,第2-vcodec 选项指定编码 codec。管道由 p.stdin.close ()关闭。

import subprocess
import numpy as np
from PIL import Image


rate = 1
outf = 'test.avi'


cmdstring = ('ffmpeg.exe',
'-y',
'-r', '%d' % rate,
'-f','image2pipe',
'-vcodec', 'mjpeg',
'-i', 'pipe:',
'-vcodec', 'libxvid',
outf
)
p = subprocess.Popen(cmdstring, stdin=subprocess.PIPE, shell=False)


for i in range(10):
im = Image.fromarray(np.uint8(np.random.randn(100,100)))
p.stdin.write(im.tostring('jpeg','L'))
#p.communicate(im.tostring('jpeg','L'))


p.stdin.close()

转换到图像格式相当缓慢,并增加了依赖性。在查看这些页面和其他页面之后,我使用 mencoder (仍然需要 ffmpeg 解决方案)使用原始的未编码缓冲区工作。

详情请浏览: http://vokicodder.blogspot.com/2011/02/numpy-arrays-to-video.html

import subprocess


import numpy as np


class VideoSink(object) :


def __init__( self, size, filename="output", rate=10, byteorder="bgra" ) :
self.size = size
cmdstring  = ('mencoder',
'/dev/stdin',
'-demuxer', 'rawvideo',
'-rawvideo', 'w=%i:h=%i'%size[::-1]+":fps=%i:format=%s"%(rate,byteorder),
'-o', filename+'.avi',
'-ovc', 'lavc',
)
self.p = subprocess.Popen(cmdstring, stdin=subprocess.PIPE, shell=False)


def run(self, image) :
assert image.shape == self.size
self.p.stdin.write(image.tostring())
def close(self) :
self.p.stdin.close()

我有一些不错的加速跑。

这个功能现在(至少在1.2.0,也许是1.1)通过 MovieWriter类以及它在 animation模块中的子类加入到 matplotlib 中。您还需要提前安装 ffmpeg

import matplotlib.animation as animation
import numpy as np
from pylab import *




dpi = 100


def ani_frame():
fig = plt.figure()
ax = fig.add_subplot(111)
ax.set_aspect('equal')
ax.get_xaxis().set_visible(False)
ax.get_yaxis().set_visible(False)


im = ax.imshow(rand(300,300),cmap='gray',interpolation='nearest')
im.set_clim([0,1])
fig.set_size_inches([5,5])




tight_layout()




def update_img(n):
tmp = rand(300,300)
im.set_data(tmp)
return im


#legend(loc=0)
ani = animation.FuncAnimation(fig,update_img,300,interval=30)
writer = animation.writers['ffmpeg'](fps=30)


ani.save('demo.mp4',writer=writer,dpi=dpi)
return ani

animation文档

这些答案都很棒。还有一个建议。@ user621442是正确的,瓶颈通常是图像的写入,所以如果您正在写 png 文件到您的视频压缩器,它将是相当慢的(即使您通过管道发送它们,而不是写到磁盘)。我找到了一个使用纯 ffmpeg 的解决方案,我个人发现使用它比 matplotlib.Anime.animer 或 mencoder 更容易。

此外,在我的情况下,我想只保存图像在一个轴,而不是保存所有的刻度标签,数字标题,数字背景等。基本上,我想使用 matplotlib 代码制作一个电影/动画,但不想让它“看起来像一个图形”。我在这里包含了 那个密码,但是如果需要,您可以制作标准图并将它们导入到 ffmpeg。

import matplotlib
matplotlib.use('agg', warn = False, force = True)


import matplotlib.pyplot as plt
import subprocess


# create a figure window that is the exact size of the image
# 400x500 pixels in my case
# don't draw any axis stuff ... thanks to @Joe Kington for this trick
# https://stackoverflow.com/questions/14908576/how-to-remove-frame-from-matplotlib-pyplot-figure-vs-matplotlib-figure-frame
f = plt.figure(frameon=False, figsize=(4, 5), dpi=100)
canvas_width, canvas_height = f.canvas.get_width_height()
ax = f.add_axes([0, 0, 1, 1])
ax.axis('off')


def update(frame):
# your matplotlib code goes here


# Open an ffmpeg process
outf = 'ffmpeg.mp4'
cmdstring = ('ffmpeg',
'-y', '-r', '30', # overwrite, 30fps
'-s', '%dx%d' % (canvas_width, canvas_height), # size of image string
'-pix_fmt', 'argb', # format
'-f', 'rawvideo',  '-i', '-', # tell ffmpeg to expect raw video from the pipe
'-vcodec', 'mpeg4', outf) # output encoding
p = subprocess.Popen(cmdstring, stdin=subprocess.PIPE)


# Draw 1000 frames and write to the pipe
for frame in range(1000):
# draw the frame
update(frame)
plt.draw()


# extract the image as an ARGB string
string = f.canvas.tostring_argb()


# write to pipe
p.stdin.write(string)


# Finish up
p.communicate()

以下是@tacaswell 答案的修改版本,修改内容如下:

  1. 不需要 pylab依赖项
  2. 修正了这个函数可以直接运行的几个地方。(原始版本不能直接复制粘贴并运行,必须修复多个地方。)

非常感谢@tacaswell 的精彩回答! ! !

def ani_frame():
def gen_frame():
return np.random.rand(300, 300)


fig = plt.figure()
ax = fig.add_subplot(111)
ax.set_aspect('equal')
ax.get_xaxis().set_visible(False)
ax.get_yaxis().set_visible(False)


im = ax.imshow(gen_frame(), cmap='gray', interpolation='nearest')
im.set_clim([0, 1])
fig.set_size_inches([5, 5])


plt.tight_layout()


def update_img(n):
tmp = gen_frame()
im.set_data(tmp)
return im


# legend(loc=0)
ani = animation.FuncAnimation(fig, update_img, 300, interval=30)
writer = animation.writers['ffmpeg'](fps=30)


ani.save('demo.mp4', writer=writer, dpi=72)
return ani