Générer des films à partir de python sans sauvegarder des cadres individuels dans des fichiers
j'aimerais créer un film h264 ou divx à partir de cadres que je génère dans un script python en matplotlib. Il y a environ 100k images dans ce film.
Dans les exemples sur le web [par exemple. 1], Je n'ai vu que la méthode pour sauvegarder chaque image en png et ensuite lancer mencoder ou ffmpeg sur ces fichiers. Dans mon cas, sauver chaque image est impraticable. Y a-t-il un moyen de prendre une parcelle générée à partir de matplotlib et de la Piper directement à ffmpeg, ne générant aucun intermédiaire fichiers?
programmer avec l'api C de ffmpeg est trop difficile pour moi [par ex. 2]. En outre, j'ai besoin d'un encodage qui a une bonne compression comme x264 car le fichier du film sera autrement trop grand pour une étape ultérieure. Ce serait donc bien de s'en tenir à mencoder/ffmpeg/x264.
y a-t-il quelque chose à faire avec les pipes [3]?
[1] http://matplotlib.sourceforge.net/examples/animation/movie_demo.html
[2] comment encoder une série d'images dans H264 en utilisant l'API x264 C?
5 réponses
cette fonctionnalité est maintenant (au moins à partir de 1.2.0, peut-être 1.1) cuite dans matplotlib via la classe MovieWriter
et il est 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é la ffmpeg (voir les commentaires de Joe Kington à ma question), j'ai pu faire passer les png à la 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 sur libavcodec/Makefile
. Enfin, j'ai enlevé '- 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é cette page et d'autres, je l'ai fait fonctionner en utilisant des tampons bruts non codés en utilisant mencoder (solution ffmpeg toujours désiré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 de belles vitesses.
ce sont de très bonnes réponses. Voici une autre suggestion. @user621442 est correct que le goulot d'étranglement est typiquement l'écriture de l'image, donc si vous écrivez des 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.
Aussi, dans mon cas, je voulais juste sauver l'image dans un axe, au lieu de sauvegarder toutes les étiquettes, le titre de la figure, le fond de la figure, etc. Fondamentalement, je voulais faire un film / animation en utilisant le code matplotlib, mais pas l'avoir "ressembler à un graphique". J'ai inclus ce code ici, mais vous pouvez faire des graphiques standard et les Piper à 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()
C'est génial! Je voulais faire la même chose. Mais je n'ai jamais pu compiler la source corrigée ffmpeg (0.6.1) dans Vista avec MingW32+MSYS+pr environment... png_parser.c produit Error1 lors de la compilation.
donc, j'ai trouvé une solution jpeg à cela en utilisant PIL. Mettez juste votre ffmpeg.exe dans le même dossier que le script. Cela devrait fonctionner avec ffmpeg sans le patch sous Windows. J'ai dû utiliser l'entrée standard.méthode d'écriture plutôt que la méthode de communication qui est recommandé dans la documentation officielle sur le sous-processus. Notez que l'option 2nd-vcodec spécifie le codec d'encodage. Le tube est fermé par p.stdin.proche.)(
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()