在Python中生成电影而不保存单独帧到文件
我想用我在Python脚本中用matplotlib生成的帧来创建一个h264或divx格式的电影。这个电影大约有10万帧。
在网上的例子中[例如1],我只看到过把每一帧保存为png文件,然后再用mencoder或ffmpeg处理这些文件的方法。但对我来说,保存每一帧是不现实的。有没有办法直接把matplotlib生成的图像传给ffmpeg,而不生成中间文件呢?
用ffmpeg的C语言接口编程对我来说太难了[例如2]。而且,我需要一种压缩效果好的编码方式,比如x264,因为如果不这样,电影文件会太大,后续处理会很麻烦。所以我希望能继续使用mencoder/ffmpeg/x264。
有没有什么方法可以使用管道[3]来实现呢?
[1] http://matplotlib.sourceforge.net/examples/animation/movie_demo.html
6 个回答
把东西转换成图片格式的过程比较慢,而且还需要额外的工具。经过查看这个页面和其他一些资料,我用原始的未编码的缓冲区通过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()
我获得了一些不错的速度提升。
在对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')
如果没有这个补丁,是无法实现的。这个补丁很简单,只修改了两个文件,并添加了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
这个功能现在已经集成在matplotlib里了(至少从1.2.0版本开始,可能1.1版本也有),通过一个叫MovieWriter
的类和它的子类在animation
模块中实现。你还需要提前安装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