Domanda Generazione di film da Python senza salvare singoli frame su file


Mi piacerebbe creare un film h264 o divx da fotogrammi che ho generato in uno script python in matplotlib. Ci sono circa 100k frame in questo film.

Negli esempi sul web [es. 1], ho visto solo il metodo per salvare ogni frame come png e quindi eseguire mencoder o ffmpeg su questi file. Nel mio caso, salvare ogni frame non è pratico. C'è un modo per prendere una trama generata da matplotlib e collegarla direttamente a ffmpeg, generando nessun file intermedio?

Programmare con il C-api di ffmpeg è troppo difficile per me [es. 2]. Inoltre, ho bisogno di una codifica che abbia una buona compressione come x264 in quanto il filmato sarà troppo grande per un passo successivo. Quindi sarebbe bello restare con mencoder / ffmpeg / x264.

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

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

[2] Come si codifica una serie di immagini in H264 usando l'API C x264?

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


60
2017-11-04 00:30


origine


risposte:


Questa funzionalità è ora (almeno dalla 1.2.0, forse 1.1) cotta in matplotlib tramite il MovieWriter classe e le sue sottoclassi nel animation modulo. Devi anche 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


45
2017-12-21 03:13



Dopo aver applicato le patch a ffmpeg (vedi i commenti di Joe Kington alla mia domanda), sono riuscito a ottenere il png di png su ffmpeg 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')

Non funzionerebbe senza il toppa, che modifica banalmente due file e aggiunge libavcodec/png_parser.c. Ho dovuto applicare manualmente la patch a libavcodec/Makefile. Infine, ho rimosso "numero" da Makefile per ottenere le pagine man da costruire. Con 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

20
2017-11-07 01:39



La conversione in formati di immagini è piuttosto lenta e aggiunge dipendenze. Dopo aver guardato queste pagine e altre ho capito che funzionava usando i buffer non codificati grezzi usando mencoder (la soluzione ffmpeg era ancora richiesta).

Dettagli 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 qualche bella accelerazione.


11
2018-02-17 13:26



Queste sono tutte ottime risposte. Ecco un altro suggerimento. @ user621442 è corretto che il collo di bottiglia è in genere la scrittura dell'immagine, quindi se stai scrivendo file png sul tuo compressore video, sarà piuttosto lento (anche se li stai mandando attraverso una pipe invece di scrivere su disco). Ho trovato una soluzione usando ffmpeg puro, che personalmente trovo più facile da usare rispetto a matplotlib.animation o mencoder.

Inoltre, nel mio caso, volevo solo salvare l'immagine su un asse, invece di salvare tutte le etichette di graduazione, il titolo della figura, lo sfondo della figura, ecc. Fondamentalmente volevo realizzare un film / animazione usando il codice matplotlib, ma non ho "sembra un grafico". Ho incluso quel codice qui, ma puoi creare grafici standard e inserirli a ffmpeg, se lo desideri.

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
2018-04-08 21:17



È fantastico! Volevo fare lo stesso. Ma non potrei mai compilare il sorgente ffmpeg con patch (0.6.1) in Vista con il preamplificatore MingW32 + MSYS + ... png_parser.c ha prodotto Error1 durante la compilazione.

Quindi, ho trovato una soluzione jpeg per questo usando il PIL. Metti il ​​tuo ffmpeg.exe nella stessa cartella di questo script. Questo dovrebbe funzionare con ffmpeg senza la patch sotto Windows. Ho dovuto usare il metodo stdin.write piuttosto che il metodo di comunicazione che è raccomandato nella documentazione ufficiale sui sottoprocessi. Si noti che l'opzione 2nd -vcodec specifica il codec di codifica. La pipe viene 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()

5
2018-01-06 20:37