webentwicklung-frage-antwort-db.com.de

Erzeugen eines Films aus python ohne Speichern einzelner Bilder in Dateien

Ich möchte einen H264- oder DivX-Film aus Frames erstellen, die ich in einem python Skript in matplotlib generiere. In diesem Film befinden sich ca. 100.000 Frames.

In Beispielen im Web [z. 1] habe ich nur die Methode gesehen, jeden Frame als PNG zu speichern und dann Mencoder oder ffmpeg für diese Dateien auszuführen. In meinem Fall ist es unpraktisch, jeden Frame zu speichern. Gibt es eine Möglichkeit, einen aus matplotlib generierten Plot zu übernehmen und direkt an ffmpeg weiterzuleiten, ohne dass Zwischendateien generiert werden?

Das Programmieren mit der C-API von ffmpeg ist mir zu schwierig [z. 2]. Außerdem benötige ich eine Codierung mit guter Komprimierung wie x264, da die Filmdatei ansonsten für einen nachfolgenden Schritt zu groß ist. Es wäre also großartig, bei mencoder/ffmpeg/x264 zu bleiben.

Gibt es etwas, das mit Rohren gemacht werden kann [3]?

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

[2] Wie codiert man eine Reihe von Bildern mit der x264 C-API in H264?

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

65
Paul

Diese Funktionalität ist jetzt (mindestens ab 1.2.0, möglicherweise 1.1) über die Klasse MovieWriter und ihre Unterklassen im Modul animation in matplotlib eingebunden. Sie müssen auch ffmpeg im Voraus installieren.

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

Dokumentation für animation

50
tacaswell

Nachdem ich ffmpeg gepatcht hatte (siehe Joe Kingtons Kommentare zu meiner Frage), konnte ich Png's wie folgt an ffmpeg senden:

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

Ohne den Patch , der zwei Dateien trivial verändert und libavcodec/png_parser.c Hinzufügt, würde das nicht funktionieren. Ich musste den Patch manuell auf libavcodec/Makefile Anwenden. Zuletzt habe ich '-number' aus Makefile entfernt, um die Manpages zum Erstellen zu bringen. Mit Kompilierungsoptionen

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
Paul

Das Konvertieren in Bildformate ist sehr langsam und fügt Abhängigkeiten hinzu. Nachdem ich mir diese und andere Seiten angeschaut hatte, funktionierte sie unter Verwendung von unkodierten Puffern mit Mencoder (nach wie vor wollte ich eine ffmpeg-Lösung).

Details unter: 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()

Ich habe ein paar nette Beschleunigungen.

13
user621442

Das sind alles wirklich tolle Antworten. Hier ist ein weiterer Vorschlag. @ user621442 ist richtig, dass der Engpass in der Regel das Schreiben des Bildes ist. Wenn Sie also PNG-Dateien auf Ihren Videokomprimierer schreiben, ist dies ziemlich langsam (selbst wenn Sie sie über eine Pipe senden, anstatt auf die Festplatte zu schreiben). Ich habe eine Lösung mit reinem ffmpeg gefunden, die ich persönlich einfacher zu bedienen finde als matplotlib.animation oder mencoder.

Außerdem wollte ich in meinem Fall das Bild nur in einer Achse speichern, anstatt alle Teilstrichbeschriftungen, Figurentitel, Figurenhintergrund usw. zu speichern. Grundsätzlich wollte ich einen Film/eine Animation mit Matplotlib-Code erstellen, aber nicht mit Matplotlib-Code es "sieht aus wie ein Graph". Ich habe diesen Code hier eingefügt, aber Sie können Standardgraphen erstellen und stattdessen nach ffmpeg leiten, wenn Sie möchten.

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

Das ist toll! Ich wollte dasselbe tun. Aber ich könnte die gepatchte ffmpeg-Quelle (0.6.1) in Vista niemals mit MingW32 + MSYS + pr enviroment kompilieren ... png_parser.c erzeugte Fehler1 während der Kompilierung.

Also habe ich mit PIL eine JPEG-Lösung dafür gefunden. Legen Sie einfach Ihre ffmpeg.exe in den gleichen Ordner wie dieses Skript. Dies sollte mit ffmpeg ohne den Patch unter Windows funktionieren. Ich musste die stdin.write-Methode anstelle der in der offiziellen Dokumentation zu Subprozessen empfohlenen communicate-Methode verwenden. Beachten Sie, dass die Option 2nd -vcodec den Codec angibt. Die Pipe wird mit p.stdin.close () geschlossen.

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
otterb

Hier ist eine modifizierte Version von @tacaswells Antwort. Folgendes geändert:

  1. Benötigen Sie nicht die Abhängigkeit pylab
  2. Fix mehrere Plätze an t. Diese Funktion ist direkt ausführbar. (Das Original kann nicht direkt kopiert und ausgeführt werden und muss an mehreren Stellen repariert werden.)

Vielen Dank für die wundervolle Antwort von @tacaswell !!!

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
0
fzyzcjy