在Python中生成电影而不保存单独帧到文件

78 投票
6 回答
74189 浏览
提问于 2025-04-16 06:31

我想用我在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

[2] 如何使用x264 C API将一系列图像编码为H264?

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

6 个回答

16

把东西转换成图片格式的过程比较慢,而且还需要额外的工具。经过查看这个页面和其他一些资料,我用原始的未编码的缓冲区通过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()

我获得了一些不错的速度提升。

23

在对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
58

这个功能现在已经集成在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

关于animation的文档

撰写回答