2010-11-04 11 views
58

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.Generación de película desde python sin guardar fotogramas individuales en archivos

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] How does one encode a series of images into H264 using the x264 C API?

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

+0

todavía tengo que encontrar una manera de hacer esto con las bibliotecas actualmente mantenidos ... (utilicé pymedia en el pasado, pero ya no es mantenida, y no construir sobre cualquier sistema que utilizo .. .) Si ayuda, puede obtener un buffer RGB de una figura matplotlib usando 'buffer = fig.canvas.tostring_rgb()', y el ancho y alto de la figura en píxeles con 'fig.canvas.get_width_height()' (o 'fig.bbox.width', etc.) –

+0

OK, gracias. Eso es útil. Me pregunto si alguna transformación del buffer puede ser canalizada a ffmpeg. pyffmpeg tiene un sofisticado envoltorio Cython, recientemente actualizado, para leer un avi cuadro por cuadro. Pero no escribiendo Eso suena como un posible lugar para comenzar para alguien familiarizado con la biblioteca ffmpeg. Incluso algo como im2frame de matlab sería genial. – Paul

+1

Estoy jugando con la lectura de ffmpeg desde un conducto de entrada (con la opción '-f image2pipe' para que espere una serie de imágenes), o desde un socket local (por ejemplo,' udp: // localhost: some_port') y escribir en el zócalo en python ... Hasta ahora, solo el éxito parcial ... Siento que estoy casi allí, aunque ... No estoy lo suficientemente familiarizado con ffmpeg ... –

Respuesta

43

Esta funcionalidad es ahora (al menos a partir de 1.2.0, tal vez 1.1) horneado en matplotlib a través de la clase MovieWriter y sus subclases en el módulo animation. También necesita instalar ffmpeg con anticipación.

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 for animation

20

Después de parcheo ffmpeg (véanse los comentarios de Joe Kington a mi pregunta), yo era capaz de obtener de png de tuberías a 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 la patch, que trivialmente mod crea dos archivos y agrega libavcodec/png_parser.c. Tuve que aplicar manualmente el parche al libavcodec/Makefile. Por último, eliminé '-number' del 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 
+0

¡Bien hecho! +1 (Nunca pude obtener ffmpeg para aceptar una transmisión de .png, creo que necesito actualizar mi versión de ffmpeg ...) Y, en caso de que te lo estés preguntando, es perfectamente aceptable marcar tu respuesta como la respuesta a tu pregunta. Vea la discusión aquí: http://meta.stackexchange.com/questions/17845/stack-overflow-etiquette-for-answering-your-ow-question –

+0

Ok, lo marcaré como respondí. Gracias por los consejos de nuevo. – Paul

+0

Guau, genial. He estado tratando de hacer lo mismo. –

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() 
11

la conversión a formatos de imagen es bastante lento y añade 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 aceleraciones agradables.

+0

Modifiqué esto para ffmpeg, vea mi respuesta a continuación si todavía la quiere – cxrodgers

4

Estos son todos muy grandes 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 guardar la imagen en un eje, en lugar de guardar todas las etiquetas, título, fondo, etc. Básicamente quería hacer una película/animación con el código matplotlib , pero no lo tienen "parece un gráfico". He incluido that code 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://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() 
Cuestiones relacionadas