76 votes

Générer un film à partir de python sans enregistrer les images individuelles dans des fichiers

Je voudrais créer un film h264 ou divx à partir d'images que je génère dans un script python dans matplotlib. Il y a environ 100 000 images dans ce film.

Dans les exemples sur le web [eg. 1], je n'ai vu que la méthode consistant à sauvegarder chaque image en png et ensuite à exécuter mencoder ou ffmpeg sur ces fichiers. Dans mon cas, la sauvegarde de chaque image n'est pas pratique. Existe-t-il un moyen de prendre un graphique généré par matplotlib et de le transmettre directement à ffmpeg, sans générer de fichiers intermédiaires ?

La programmation avec l'interface C-api de ffmpeg est trop difficile pour moi [ex. 2]. De plus, j'ai besoin d'un encodage avec une bonne compression, comme x264, car sinon le fichier vidéo sera trop volumineux pour une étape ultérieure. Ce serait donc bien de s'en tenir à mencoder/ffmpeg/x264.

Peut-on faire quelque chose avec les tuyaux [3] ?

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

[2] http://stackoverflow.com/questions/2940671

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

57voto

tcaswell Points 21489

Cette fonctionnalité est maintenant (au moins à partir de la version 1.2.0, peut-être 1.1) intégrée dans matplotlib via la fonction MovieWriter et de ses sous-classes dans l'arborescence de l'UE. animation module.

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

23voto

Paul Points 550

Après avoir Parcheando ffmpeg (voir les commentaires de Joe Kington à ma question), j'ai pu obtenir des png's de piping à 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 fonctionnerait pas sans le patch qui modifie trivialement deux fichiers et ajoute libavcodec/png_parser.c . J'ai dû appliquer manuellement le patch à libavcodec/Makefile . Enfin, j'ai supprimé le paramètre '-number' de la commande Makefile pour obtenir les pages de manuel à compiler. 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

16voto

user621442 Points 91

La conversion en format image est assez lente et ajoute des dépendances. Après avoir regardé ces pages et d'autres, j'ai réussi à le faire fonctionner en utilisant des tampons bruts non codés avec mencoder (la solution ffmpeg est toujours recherchée).

Détails à : 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 de belles accélérations.

6voto

otterb Points 577

C'est génial ! Je voulais faire la même chose. Mais, je n'ai jamais pu compiler la source corrigée de ffmpeg (0.6.1) sous Vista avec l'environnement MingW32+MSYS+pr... png_parser.c a produit Error1 pendant la compilation.

J'ai donc trouvé une solution jpeg à ce problème 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. J'ai dû utiliser la méthode stdin.write plutôt que la méthode communicate qui est recommandée dans la documentation officielle sur les sous-processus. Notez que la 2ème option -vcodec spécifie le codec d'encodage. Le pipe 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()

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X