Génération film de python sans enregistrer des images individuelles aux fichiers
-
28-09-2019 - |
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?
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
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:
- Ne pas exiger la dépendance
pylab
- 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