Domanda

Vorrei creare un H264 o film DivX da fotogrammi che ho generato in uno script python in matplotlib. Ci sono circa 100k fotogrammi di questo film.

Nel esempi sul web [ad es. 1], ho visto solo il metodo di salvataggio ciascun fotogramma come png e quindi eseguendo mencoder o ffmpeg su questi file. Nel mio caso, il risparmio ogni fotogramma è impraticabile. C'è un modo per prendere una trama generato dal matplotlib e tubo direttamente a ffmpeg, generando nessun file intermedi?

Programmazione con C-API di ffmpeg è troppo difficile per me [ad es. 2]. Inoltre, ho bisogno di una codifica che ha una buona compressione come x264 del file video sarà altrimenti troppo grande per una fase successiva. Quindi, sarebbe bello restare con mencoder / ffmpeg / x264.

C'è qualcosa che può essere fatto con tubi [3]?

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

[2] Come si fa a codificare una serie di immagini in H264 utilizzando l'API x264 C?

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

È stato utile?

Soluzione

Questa funzionalità è ora (almeno a partire da 1.2.0, forse 1,1) cotto in matplotlib tramite la classe MovieWriter ed è sotto-classi nel modulo animation. È inoltre necessario installare ffmpeg in anticipo.

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

Documentazione per animation

Altri suggerimenti

Dopo l'applicazione di patch ffmpeg (vedi Joe Kington commenti a mia domanda), sono stato in grado di ottenere tubazioni png di ffmpeg per come segue:

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

E non funzionerebbe senza il cerotto , che banalmente modifica due file e aggiunge libavcodec/png_parser.c. Ho dovuto applicare manualmente la patch per libavcodec/Makefile. Infine, ho rimosso '-numero' dal Makefile per ottenere le pagine man di costruire. Con le opzioni di compilazione,

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 conversione di formati di immagine è piuttosto lento e aggiunge le dipendenze. Dopo aver guardato questi pagina e altri ho capito di lavoro utilizzando i buffer non codificati prime che utilizzano mencoder (soluzione ffmpeg ancora voluto).

la scheda a: 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()

Ho avuto alcuni incrementi nella velocità piacevoli.

Questi sono tutti molto grandi risposte. Ecco un altro suggerimento. @ User621442 è corretto che il collo di bottiglia è in genere la scrittura dell'immagine, quindi se si sta scrivendo i file PNG al compressore video, sarà piuttosto lento (anche se li sta inviando attraverso un tubo invece di scrivere su disco). Ho trovato una soluzione che utilizza ffmpeg pura, che personalmente trovo più facile da usare rispetto matplotlib.animation o mencoder.

Inoltre, nel mio caso, ho voluto solo salvare l'immagine in un asse, invece di salvare tutte le etichette delle tacche, il titolo figura, figura sfondo, ecc Fondamentalmente ho voluto fare un film / animazione utilizzando il codice matplotlib, ma non averlo "aspetto di un grafico". Ho incluso che il codice qui, ma si possono fare i grafici standard e pipe loro di ffmpeg, invece, se si desidera.

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

Questo è grande! Ho voluto fare lo stesso. Ma, non potrei mai compilare il sorgente ffmpeg patch (0.6.1) in Vista con MingW32 + MSYS + pr ambiente ... png_parser.c prodotta Error1 durante la compilazione.

Quindi, mi si avvicinò con una soluzione a questo jpeg utilizzando PIL. Basta mettere il ffmpeg.exe nella stessa cartella di questo script. Questo dovrebbe funzionare con ffmpeg, senza la patch in ambiente Windows. Ho dovuto usare il metodo stdin.write piuttosto che il metodo che è raccomandato nella documentazione ufficiale circa il sottoprocesso comunicare. Si noti che l'opzione -vcodec secondo specifica il codec di codifica. Il tubo è chiusa da 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()

Ecco una versione modificata di risposta s' @tacaswell. Modificata la seguente:

  1. Non richiede l'pylab dipendenza
  2. Fissare diversi luoghi S.T. questa funzione è direttamente eseguibile. (L'originale non può essere copia-e-incolla-and-run direttamente e devono correggere diversi posti.)

Grazie mille per @tacaswell 's risposta meravigliosa !!!

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
Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top