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?
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
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
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.
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()
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()
Hier ist eine modifizierte Version von @tacaswells Antwort. Folgendes geändert:
pylab
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