speed - Generando películas desde Python sin guardar cuadros individuales en archivos
real time graph python (5)
¡Esto es genial! Yo quería hacer lo mismo. Pero, nunca pude compilar la fuente de ffmpeg parcheada (0.6.1) en Vista con MingW32 + MSYS + pr medio ambiente ... png_parser.c produjo Error1 durante la compilación.
Entonces, se me ocurrió una solución jpeg para esto usando PIL. Simplemente ponga su ffmpeg.exe en la misma carpeta que este script. Esto debería funcionar con ffmpeg sin el parche bajo Windows. Tuve que usar el método stdin.write en lugar del método de comunicación que se recomienda en la documentación oficial sobre el subproceso. Tenga en cuenta que la opción 2nd -vcodec especifica el códec de codificación. La tubería está cerrada por 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()
Me gustaría crear una película h264 o divx a partir de fotogramas que genero en una secuencia de comandos python en matplotlib. Hay alrededor de 100k cuadros en esta película.
En ejemplos en la web [ej. 1], solo he visto el método de guardar cada cuadro como png y luego ejecutar mencoder o ffmpeg en estos archivos. En mi caso, guardar cada fotograma no es práctico. ¿Hay alguna manera de tomar un diagrama generado de matplotlib y canalizarlo directamente a ffmpeg, sin generar archivos intermedios?
La programación con C-api de ffmpeg es demasiado difícil para mí [por ej. 2]. Además, necesito una codificación que tenga buena compresión, como x264, ya que de lo contrario el archivo de película será demasiado grande para un paso posterior. Así que sería genial seguir con mencoder / ffmpeg / x264.
¿Hay algo que se pueda hacer con las tuberías [3]?
[1] http://matplotlib.sourceforge.net/examples/animation/movie_demo.html
[2] ¿Cómo codifica una serie de imágenes en H264 usando la API x264 C?
Después de parchear ffmpeg (ver los comentarios de Joe Kington a mi pregunta), pude hacer que los png''s png''s ffmpeg de la siguiente manera:
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'')
No funcionaría sin el patch , que modifica trivialmente dos archivos y agrega libavcodec/png_parser.c
. Tuve que aplicar manualmente el parche a libavcodec/Makefile
. Por último, eliminé ''-number'' de Makefile
para obtener las páginas man para compilar. Con opciones de compilación,
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
Esta funcionalidad es ahora (al menos a partir de 1.2.0, tal vez 1.1) incorporada en matplotlib a través de la clase MovieWriter
y sus subclases en el módulo de animation
. También necesita instalar ffmpeg
por adelantado.
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
Estas son todas las mejores respuestas. Aquí hay otra sugerencia. @ user621442 tiene razón en que el cuello de botella suele ser la escritura de la imagen, por lo que si está escribiendo archivos png en su compresor de video, será bastante lento (incluso si los envía a través de un conducto en lugar de escribir en el disco). Encontré una solución usando ffmpeg puro, que personalmente encuentro más fácil de usar que matplotlib.animation o mencoder.
Además, en mi caso, quería simplemente guardar la imagen en un eje, en lugar de guardar todas las etiquetas de tilde, título de figura, fondo de figura, etc. Básicamente, quería hacer una película / animación usando el código matplotlib, pero no tenía "parece un gráfico". He incluido ese código aquí, pero puedes hacer gráficos estándar y canalizarlos a ffmpeg si lo deseas.
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://.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()
La conversión a formatos de imagen es bastante lenta y agrega dependencias. Después de mirar estas páginas y otras, conseguí que funcionara usando búferes sin codificar crudos usando mencoder (la solución ffmpeg aún se desea).
Detalles en: 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()
Tengo algunas bonitas aceleraciones.