Pregunta

Me gustaría crear una h264 o divx película a partir de las tramas que genero en una secuencia de comandos Python en matplotlib. Hay alrededor de 100 mil marcos en esta película.

En los ejemplos en la web [por ejemplo. 1], sólo he visto el método de ahorro de cada fotograma como un png y luego ejecutar el mencoder o ffmpeg en estos archivos. En mi caso, el ahorro de cada cuadro es poco práctico. ¿Hay una manera de tomar un trazado generado a partir matplotlib y el tubo directamente a ffmpeg, generando no hay archivos intermedios?

Programación con C-API de ffmpeg es demasiado difícil para mí [por ejemplo. 2]. Además, necesito una codificación que tiene una buena compresión como x264 como el archivo de película de otro modo sería demasiado grande para una etapa posterior. Por lo tanto, sería muy bueno para seguir con mencoder / ffmpeg / x264.

¿Hay algo que se puede hacer con tuberías [3]?

[1] http://matplotlib.sourceforge.net/examples/animation/movie_demo .html

[2] ¿Cómo se puede codificar una serie de imágenes en H264 con la API de C x264?

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

¿Fue útil?

Solución

Esta funcionalidad es ahora (al menos como de 1.2.0, tal vez 1.1) al horno en matplotlib través de la clase MovieWriter y es sub-clases en el módulo animation. También es necesario instalar ffmpeg de antemano.

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

Documentación para animation

Otros consejos

Después de parcheo ffmpeg (véanse los comentarios de Joe Kington a mi pregunta), yo era capaz de conseguir tuberías de png 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 el parche , que modifica trivialmente dos archivos y añade libavcodec/png_parser.c. Tenía que aplicar manualmente el parche a libavcodec/Makefile. Por último, me quita '-número' de Makefile para obtener las páginas del manual para construir. 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

La conversión a formatos de imagen es bastante lento y añade dependencias. Después de buscar en la página éstas y otras Lo tengo trabajo usando tampones no codificados usando mencoder primas (solución ffmpeg todavía quería).

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.

Estos son todos muy grandes respuestas. Aquí hay otra sugerencia. @ User621442 es correcto que el cuello de botella es típicamente la escritura de la imagen, por lo que si usted está escribiendo archivos PNG a su compresor de vídeo, será bastante lento (incluso si va a enviar a través de una tubería en lugar de escribir en el disco). He encontrado una solución usando ffmpeg puro, que personalmente me parece 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 garrapatas, figura el título, la figura de fondo, etc. Básicamente quería hacer una película / animación utilizando el código matplotlib, pero no tenerlo "mirada como un gráfico". He incluido que el código aquí, pero se puede hacer gráficos estándar y los tubos que fFmpeg lugar si lo desea.

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

Esto es grande! Yo quería hacer lo mismo. Sin embargo, nunca podría compilar la fuente de FFmpeg parcheado (0.6.1) en Vista con MingW32 + MSYS + pr ... png_parser.c ambiente producida ERROR1 durante la compilación.

Por lo tanto, se me ocurrió una solución a este jpeg usando PIL. Sólo hay que poner 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 que se recomienda en la documentación oficial sobre subproceso comunicarse. Tenga en cuenta que la segunda opción -vcodec especifica el códec de codificación. El tubo está cerrado 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()

Esta es una versión modificada de respuesta @tacaswell 's. Variaciones en las siguientes:

  1. No requieren la dependencia pylab
  2. Fijar varios lugares S.T. esta función es directamente ejecutable. (El original no puede ser distribuido, copiado y pegar y correr directamente y tienen que solucionar varios lugares.)

Muchas gracias por @tacaswell 's maravillosa respuesta !!!

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
Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top