web-dev-qa-db-ja.com

個々のフレームをファイルに保存せずにpythonからムービーを生成

Matplotlibのpythonスクリプトで生成したフレームからh264またはdivxムービーを作成したいと思います。このムービーには約100kフレームがあります。

ウェブ上の例[例1]、各フレームをpngとして保存してから、これらのファイルに対してmencoderまたはffmpegを実行する方法を見てきました。私の場合、各フレームの保存は実用的ではありません。 matplotlibから生成されたプロットを取得し、ffmpegに直接パイプして中間ファイルを生成しない方法はありますか?

FfmpegのC-apiを使用したプログラミングは、私にとって非常に困難です(例: 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

65
Paul

この機能は、(少なくとも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のドキュメント

50
tacaswell

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 なしでは機能しません。2つのファイルを簡単に変更し、libavcodec/png_parser.cを追加します。 libavcodec/Makefileにパッチを手動で適用する必要がありました。最後に、Makefileから '-number'を削除して、manページを作成しました。コンパイルオプションを使用すると、

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
20
Paul

画像形式への変換は非常に遅く、依存関係が追加されます。これらのページや他のページを確認した後、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()

ニースの高速化が得られました。

13
user621442

これらはすべて本当に素晴らしい答えです。別の提案があります。 @ user621442は、ボトルネックが通常画像の書き込みであることが正しいため、PNGファイルをビデオコンプレッサーに書き込む場合、かなり遅くなります(ディスクに書き込む代わりにパイプを介して送信する場合でも)。私は個人的にmatplotlib.animationやmencoderよりも使いやすいと感じる純粋なffmpegを使用したソリューションを見つけました。

また、私の場合、すべての目盛りラベル、図のタイトル、図の背景などを保存するのではなく、軸に画像を保存したかっただけです。基本的に、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()
8
cxrodgers

これは素晴らしい!私も同じことをしたかった。しかし、MingW32 + MSYS + pr環境でVistaにパッチを適用したffmpegソース(0.6.1)をコンパイルすることはできませんでした... png_parser.cはコンパイル中にError1を生成しました。

そこで、PILを使用してこれに対するjpegソリューションを思い付きました。 ffmpeg.exeをこのスクリプトと同じフォルダーに置くだけです。これは、Windowsでパッチなしでffmpegで動作するはずです。サブプロセスに関する公式ドキュメントで推奨されている通信方法ではなく、stdin.writeメソッドを使用する必要がありました。 2番目の-vcodecオプションはエンコーディングコーデックを指定することに注意してください。パイプは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()
5
otterb

@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
0
fzyzcjy