Question

Je voudrais créer un h264 ou divx film à partir d'images que je produis dans un script python dans matplotlib. Il y a environ 100k cadres dans ce film.

Dans les exemples sur le web [par exemple. 1], je ne l'ai vu la méthode de sauvegarde chaque image comme un .png et en cours d'exécution puis mencoder ou ffmpeg sur ces fichiers. Dans mon cas, l'enregistrement de chaque trame est peu pratique. Est-il possible de prendre une parcelle générée par matplotlib et le tuyau directement à FFmpeg, générant pas de fichiers intermédiaires?

Programmation avec ffmpeg de C-api est trop difficile pour moi [par exemple. 2]. , Je dois aussi un codage qui a une bonne compression tels que x264 que le fichier vidéo sera autrement trop grand pour une étape ultérieure. Donc, il serait grand MEncoder / ffmpeg / x264.

Y at-il quelque chose qui peut être fait avec des tuyaux [3]?

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

[2] Comment peut-on encode une série d'images en H264 en utilisant l'API x264 C?

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

Était-ce utile?

La solution

Cette fonctionnalité est maintenant (au moins de 1.2.0, peut-être 1.1) cuit au four dans matplotlib par la classe MovieWriter et ses sous-classes dans le module animation. Vous devez également installer ffmpeg à l'avance.

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

Documentation pour animation

Autres conseils

Après la correction ffmpeg (voir les commentaires Joe Kington à ma question), je suis en mesure d'obtenir la tuyauterie de .png à FFmpeg comme suit:

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')

Il ne travaillerait sans le patch , qui trivialement modifie deux fichiers et ajoute libavcodec/png_parser.c. Je devais appliquer manuellement le patch à libavcodec/Makefile. Enfin, je l'ai enlevé « -nombre » de Makefile pour obtenir les pages de manuel à construire. Avec des options de compilation,

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

Conversion aux formats d'image est assez lent et ajoute des dépendances. Après avoir examiné ces pages et d'autres je l'ai eu de travail en utilisant des tampons non codés à l'aide de premières mencoder (solution ffmpeg toujours voulu).

Détails sur: 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()

J'ai eu quelques speedups BELLES.

Ce sont toutes les réponses vraiment super. Voici une autre suggestion. @ User621442 est exact que le goulot d'étranglement est généralement l'écriture de l'image, donc si vous écrivez les fichiers .png à votre compresseur vidéo, il sera assez lent (même si vous les envoyez par un tuyau au lieu d'écrire sur le disque). J'ai trouvé une solution en utilisant ffmpeg pur, que je trouve personnellement plus facile à utiliser que matplotlib.animation ou mencoder.

En outre, dans mon cas, je voulais simplement enregistrer l'image dans un axe, au lieu d'enregistrer toutes les étiquettes de tiques, le titre de figure, arrière-plan figure, etc. En fait, je voulais faire un film / animation en utilisant le code matplotlib, mais pas l'avoir « ressembler à un graphique ». J'ai inclus que le code , mais vous pouvez faire des graphiques standards et les tuyaux à fFmpeg à la place si vous voulez.

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()

est grand! Je voulais faire la même chose. Mais, je ne pourrais jamais compiler la source ffmpeg patché (0.6.1) dans Vista avec MingW32 + MSYS + pr ... png_parser.c produit enviroment Error1 lors de la compilation.

Alors, je suis venu avec une solution à ce jpeg en utilisant PIL. Il suffit de mettre votre ffmpeg.exe dans le même dossier que ce script. Cela devrait fonctionner avec ffmpeg sans le patch sous Windows. Je devais utiliser la méthode stdin.write plutôt que la méthode qui est recommandé de communiquer dans la documentation officielle sur le sous-processus. Notez que la 2ème option -vcodec spécifie le codec d'encodage. Le tube est fermé par 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()

Voici une version modifiée de réponse de l » @tacaswell. Modifié ce qui suit:

  1. Ne pas exiger la dépendance pylab
  2. Correction de plusieurs endroits S.T. cette fonction est directement runnable. (L'original ne peut pas être copier-coller et exécuter directement et-doivent fixer plusieurs endroits.)

Merci beaucoup pour @tacaswell merveilleuse » réponse !!!

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
Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top