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 100k images dans ce film.
Dans des exemples sur le Web [par exemple. 1], je n'ai vu que la méthode pour enregistrer chaque image au format png et ensuite exécuter mencoder ou ffmpeg sur ces fichiers. Dans mon cas, l'enregistrement de chaque image n'est pas pratique. Existe-t-il un moyen de prendre un tracé généré à partir de matplotlib et de le diriger directement vers ffmpeg, sans générer de fichiers intermédiaires?
La programmation avec le C-api de ffmpeg est trop difficile pour moi [par exemple. 2]. De plus, j'ai besoin d'un encodage qui a une bonne compression tel que x264 car le fichier vidéo sera sinon trop volumineux pour une étape ultérieure. Il serait donc formidable de rester avec mencoder/ffmpeg/x264.
Peut-on faire quelque chose avec des tuyaux [3]?
[1] http://matplotlib.sourceforge.net/examples/animation/movie_demo.html
[2] Comment coder une série d'images en H264 en utilisant l'API x264 C?
Cette fonctionnalité est maintenant (au moins à partir de 1.2.0, peut-être 1.1) intégrée dans matplotlib via 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
Après avoir corrigé ffmpeg (voir les commentaires de Joe Kington sur ma question), j'ai pu obtenir des fichiers PNG de tuyauterie pour 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')
Cela 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é '-number' de Makefile
pour obtenir les pages de manuel à construire. Avec les 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
La conversion aux formats d'image est assez lente et ajoute des dépendances. Après avoir regardé ces pages et d'autres, je l'ai fait fonctionner en utilisant des tampons bruts non codés en utilisant mencoder (la solution ffmpeg était toujours recherchée).
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 accélérations de Nice.
Ce sont toutes de très bonnes réponses. Voici une autre suggestion. @ user621442 a raison: le goulot d'étranglement est généralement l'écriture de l'image, donc si vous écrivez des fichiers png sur votre compresseur vidéo, cela sera assez lent (même si vous les envoyez via un tube 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.
De plus, dans mon cas, je voulais simplement enregistrer l'image dans un axe, au lieu d'enregistrer toutes les étiquettes de coche, le titre de la figure, l'arrière-plan de la figure, etc. En gros, je voulais faire un film/animation en utilisant le code matplotlib, mais pas il "ressemble à un graphique". J'ai inclus ce code ici, mais vous pouvez créer des graphiques standard et les diriger vers ffmpeg à la place si vous le souhaitez.
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()
C'est bien! Je voulais faire de même. Mais, je n'ai jamais pu compiler la source ffmpeg corrigée (0.6.1) dans Vista avec l'environnement MingW32 + MSYS + pr ... png_parser.c a produit l'erreur 1 lors de la compilation.
J'ai donc trouvé une solution jpeg pour cela en utilisant PIL. Placez simplement votre ffmpeg.exe dans le même dossier que ce script. Cela devrait fonctionner avec ffmpeg sans le correctif sous Windows. J'ai dû utiliser la méthode stdin.write plutôt que la méthode communic qui est recommandée dans la documentation officielle sur le sous-processus. Notez que l'option 2nd -vcodec spécifie le codec de codage. Le canal 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 la réponse de @tacaswell. Modifié les éléments suivants:
pylab
Merci beaucoup pour la merveilleuse réponse de @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