从python生成电影而不将单个帧保存到文件

从python生成电影而不将单个帧保存到文件,python,numpy,ffmpeg,matplotlib,x264,Python,Numpy,Ffmpeg,Matplotlib,X264,我想从matplotlib中python脚本中生成的帧创建h264或divx电影。这部电影大约有10万帧 在web上的示例中[1],我只看到了将每个帧保存为png,然后在这些文件上运行mencoder或ffmpeg的方法。在我的例子中,保存每个帧是不切实际的。有没有一种方法可以将matplotlib生成的绘图直接传输到ffmpeg,而不生成中间文件 用ffmpeg的C-api编程对我来说太难了[例2]。另外,我需要一个具有良好压缩的编码,比如x264,否则电影文件对于后续步骤来说会太大。因此,坚

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

在web上的示例中[1],我只看到了将每个帧保存为png,然后在这些文件上运行mencoder或ffmpeg的方法。在我的例子中,保存每个帧是不切实际的。有没有一种方法可以将matplotlib生成的绘图直接传输到ffmpeg,而不生成中间文件

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

有什么可以用管道做的吗[3]

[1]

[2]

[3]

在修补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\u 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

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

因此,我提出了一个使用PIL的jpeg解决方案。只需将您的ffmpeg.exe与此脚本放在同一文件夹中。这应该适用于ffmpeg,而不使用Windows下的修补程序。我不得不使用stdin.write方法,而不是关于子流程的官方文档中推荐的通信方法。注意,第二个-vcodec选项指定了编码解码器。管道由p.stdin.close()关闭


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

详情见:

我得到了一些不错的加速。

这个功能现在(至少从1.2.0开始,可能是1.1)通过
MovieWriter
类及其
动画
模块中的子类烘焙到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

这些都是非常好的答案。这里还有一个建议@user621442是正确的,瓶颈通常是写入图像,因此,如果将png文件写入视频压缩程序,速度会非常慢(即使是通过管道发送而不是写入磁盘)。我找到了一个使用纯ffmpeg的解决方案,我个人认为它比matplotlib.animation或mencoder更容易使用

另外,在我的例子中,我只想将图像保存在一个轴中,而不是保存所有的记号标签、人物标题、人物背景等。基本上,我想使用matplotlib代码制作一部电影/动画,但不想让它“看起来像一个图”。我已经包括在这里,但是如果您愿意,您可以制作标准图形并将它们传输到ffmpeg

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答案的修改版本。修改如下:

  • 不需要
    pylab
    依赖项
  • 修复多个s.t位置。此函数可直接运行。(原始版本不能直接复制粘贴和运行,必须修复多个位置。)
  • 非常感谢@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
    

    我还没有找到一种方法来使用当前维护的库实现这一点。。。(我过去使用过pymedia,但它不再维护,也不会构建在我使用的任何系统上…)如果有帮助,您可以使用
    buffer=fig.canvas.tostring_RGB()
    ,并使用
    fig.canvas.get_width\u height()
    以像素为单位获取matplotlib图形的RGB缓冲区(或
    图bbox.width
    等)好的,谢谢。这很有用。我想知道缓冲区的一些转换是否可以通过管道传输到ffmpeg。pyffmpeg有一个复杂的Cython包装器,最近更新过,用于逐帧读取avi。但不用于编写。对于熟悉ffmpeg库的人来说,这听起来是一个可能的起点。即使是像matlab的im2frame这样的东西也会很棒.我正在尝试让ffmpeg从输入管道(使用
    -f image2pipe
    选项)或本地套接字(例如
    udp://localhost:some_port
    )用python编写socket…到目前为止,只取得了部分成功…我感觉我就快到了,不过…我只是对ffmpeg不够熟悉…值得一提的是,我的问题是ffmpeg接受.png或原始RGB缓冲区流的问题,(有一个bug已经存档:)如果使用JPEG,它就可以工作。(使用
    ffmpeg-f image2pipe-vcodec mjpeg-i-output.which
    。您可以打开
    subprocess.Popen(cmdstring.split(),stdin=subprocess.PIPE)
    并将每个帧写入其
    stdin
    )如果有机会,我将发布一个更详细的示例…太好了!我明天将尝试这个。做得很好!+1(我从未能让ffmpeg接受.png的流,我想我需要更新我的ffmpeg版本…)如果你想知道的话,将你的答案标记为你问题的答案是完全可以接受的。请参阅这里的讨论:嗨@Paul,补丁链接已经死了。你知道它是否已经被主分支吸收了吗?如果不知道,是否有办法获得该补丁?@Gabe,我猜补丁已经从以下帖子中吸收了:@tcaswell,我已将答案更改为您的答案(我不知道这是可能的)。您可以进行必要的编辑吗?我的意思是您可以编辑您的问题以反映新功能,但这是有效的。我已回滚了我的编辑。您对现状满意吗
    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()
    
    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