Ich möchte einen h264- oder divx-Film aus Frames erstellen, die ich in einem Python-Skript in matplotlib generiere. Es gibt ungefähr 100.000 Bilder in diesem Film.
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 das Speichern jedes Frames unpraktisch. Gibt es eine Möglichkeit, einen aus matplotlib generierten Plot direkt an ffmpeg weiterzuleiten und keine Zwischendateien zu generieren?
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?
buffer = fig.canvas.tostring_rgb()
und die Breite und Höhe der Figur in Pixel mitfig.canvas.get_width_height()
(oderfig.bbox.width
usw.)-f image2pipe
Option, dass eine Reihe von Bildern erwartet wird) oder von einem lokalen Socket (z. B.udp://localhost:some_port
) gelesen wird und in Python in den Socket schreibt ... Bisher nur Teilerfolg ... Ich fühle mich aber fast da ... Ich bin einfach nicht genug mit ffmpeg vertraut ...ffmpeg -f image2pipe -vcodec mjpeg -i - ouput.whatever
. Sie können ein öffnensubprocess.Popen(cmdstring.split(), stdin=subprocess.PIPE)
und jeden Frame in sein schreibenstdin
) Ich werde ein detaillierteres Beispiel veröffentlichen, wenn ich eine Chance bekomme ...Antworten:
Diese Funktionalität wird jetzt (mindestens ab 1.2.0, möglicherweise 1.1) über die
MovieWriter
Klasse und ihre Unterklassen imanimation
Modul in matplotlib eingebrannt . Sie müssen auchffmpeg
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
quelle
FFMpegFileWriter
?savefig
).Nachdem ich ffmpeg gepatcht hatte (siehe Kommentare von Joe Kington zu meiner Frage), konnte ich png's wie folgt an ffmpeg weiterleiten:
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 ändert und hinzufügt, würde dies nicht funktionieren
libavcodec/png_parser.c
. Ich musste den Patch manuell anwendenlibavcodec/Makefile
. Zuletzt habe ich '-number' entferntMakefile
, 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
quelle
Das Konvertieren in Bildformate ist recht langsam und fügt Abhängigkeiten hinzu. Nachdem ich mir diese und andere Seiten angesehen hatte, funktionierte es mit rohen, nicht codierten Puffern mit Mencoder (ffmpeg-Lösung immer noch erwünscht).
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.
quelle
Das sind alles wirklich gute Antworten. Hier ist ein weiterer Vorschlag. @ user621442 ist richtig, dass der Engpass normalerweise das Schreiben des Bildes ist. Wenn Sie also PNG-Dateien auf Ihren Videokompressor 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 finde als matplotlib.animation oder mencoder.
In meinem Fall wollte ich das Bild auch nur in einer Achse speichern, anstatt alle Tick-Beschriftungen, den Titel der Abbildung, den Hintergrund der Abbildung usw. zu speichern. Grundsätzlich wollte ich einen Film / eine Animation mit Matplotlib-Code erstellen, habe dies aber nicht getan es "sieht aus wie eine Grafik". Ich habe diesen Code hier eingefügt, aber Sie können Standarddiagramme erstellen und sie stattdessen an ffmpeg weiterleiten, 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 # /programming/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()
quelle
import matplotlib
Setzen Sie dann das Backend mit.matplotlib.use('agg', warn = False, force = True)
Der einzige andere Mod besteht darin,plt.draw()
den obigen Originalcode durchf.canvas.draw()
Diese zu ersetzen. Diese sind erforderlich, damit es in einem Skript funktioniert. Ansonsten ist der Code so wie er ist einfach dandy.Das ist toll! Ich wollte das gleiche tun. Aber ich konnte die gepatchte ffmpeg-Quelle (0.6.1) in Vista mit MingW32 + MSYS + pr enviroment nie 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 Methode stdin.write anstelle der Kommunikationsmethode verwenden, die in der offiziellen Dokumentation zum Unterprozess empfohlen wird. Beachten Sie, dass die 2. Option -vcodec den Codierungscodec 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()
quelle
Hier ist eine modifizierte Version von @tacaswells Antwort. Folgendes geändert:
pylab
AbhängigkeitVielen 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
quelle