Generación de películas desde python sin guardar fotogramas individuales en archivos


Me gustaría crear una película h264 o divx a partir de fotogramas que genere en un script python en matplotlib. Hay alrededor de 100 mil cuadros en esta película.

En ejemplos en la web [eg. 1], solo he visto el método de guardar cada fotograma como png y luego ejecutar mencoder o ffmpeg en estos archivos. En mi caso, guardar cada fotograma no es práctico. ¿Hay una manera de tomar una parcela generada desde matplotlib y canalizarla directamente a ffmpeg, sin generar archivos intermedios?

Programar con C-api de ffmpeg es demasiado difícil para mí [eg. 2]. Además, necesito una codificación que tenga una buena compresión, como x264, ya que 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 tuberías [3]?

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

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

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

Author: Community, 2010-11-04

5 answers

Esta funcionalidad está ahora (al menos a partir de 1.2.0, tal vez 1.1) incrustada en matplotlib a través de la clase MovieWriter y sus subclases en el módulo 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

Documentación para animation

 46
Author: tacaswell,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-11-26 19:35:34

Después de parchear ffmpeg (ver comentarios de Joe Kington a mi pregunta), pude obtener PNG de tubería 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 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 de 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
 20
Author: Paul,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-08-13 18:43:54

La conversión a formatos de imagen es bastante lenta y agrega dependencias. Después de mirar estas páginas y otras, lo conseguí trabajando con buffers raw sin codificar usando mencoder (solución ffmpeg todavía 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 buenas aceleraciones.

 11
Author: user621442,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2011-12-16 12:28:47

Estas son realmente 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 está escribiendo archivos PNG en su compresor de video, será bastante lento (incluso si los está enviando a través de una tubería en lugar de escribir en el disco). Encontré una solución usando pure ffmpeg, que personalmente encuentro más fácil de usar que matplotlib.animación o mencoder.

También, en mi caso, quería guardar la imagen en un eje, en lugar de guardar todas las etiquetas de marca, título de la figura, fondo de la figura, etc. Básicamente quería hacer una película / animación usando el código matplotlib, pero no que "pareciera un gráfico". He incluido ese código aquí, pero puede hacer gráficos estándar y canalizarlos a ffmpeg en su 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()
 6
Author: cxrodgers,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-05-23 12:26:21

¡Esto es genial! Yo quería hacer lo mismo. Pero, nunca pude compilar la fuente ffmpeg parcheada (0.6.1) en Vista con MINGW32+MSYS+pr enviroment... png_parser.c produjo Error1 durante la compilación.

Entonces, se me ocurrió una solución jpeg para esto usando PIL. Solo pon tu ffmpeg.exe en la misma carpeta que este script. Esto debería funcionar con ffmpeg sin el parche bajo Windows. Tuve que usar stdin.escribir método en lugar del método de comunicación que se recomienda en el oficial documentación sobre subproceso. Tenga en cuenta que la opción 2nd-vcodec especifica el códec de codificación. El tubo está cerrado por p. stdin.cerca().

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()
 5
Author: otterb,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2011-01-14 18:46:15