توليد فيلم من Python دون حفظ إطارات فردية إلى الملفات
-
28-09-2019 - |
سؤال
أرغب في إنشاء فيلم H264 أو Divx من إطارات أقوم بإنشائها في نص Python في Matplotlib. هناك حوالي 100 ألف إطار في هذا الفيلم.
في أمثلة على الويب [على سبيل المثال. 1] ، لقد رأيت فقط طريقة حفظ كل إطار كـ PNG ثم تشغيل mencoder أو FFMPEG على هذه الملفات. في حالتي ، فإن حفظ كل إطار غير عملي. هل هناك طريقة لاتخاذ قطعة مؤامرة تم إنشاؤها من matplotlib وأنبوبها مباشرة إلى FFMPEG ، ولا توليد أي ملفات وسيطة؟
البرمجة مع FFMPEG's C-API أمر صعب للغاية بالنسبة لي [على سبيل المثال. 2]. أيضًا ، أحتاج إلى تشفير يحتوي على ضغط جيد مثل X264 لأن ملف الفيلم سيكون كبيرًا جدًا بالنسبة لخطوة لاحقة. لذلك سيكون من الرائع التمسك بـ Mencoder/FFMPEG/X264.
هل هناك شيء يمكن القيام به مع الأنابيب [3]؟
[1] http://matplotlib.sourceforge.net/examples/animation/movie_demo.html
[2] كيف يمكن للمرء أن يشفر سلسلة من الصور في H264 باستخدام واجهة برمجة تطبيقات X264 C؟
المحلول
هذه الوظيفة الآن (على الأقل اعتبارًا من 1.2.0 ، ربما 1.1) مخبوز في matplotlib عبر MovieWriter
الفصل وهو فئات فرعية في animation
وحدة. تحتاج أيضًا إلى التثبيت ffmpeg
مقدما.
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
نصائح أخرى
بعد تصحيح FFMPEG (انظر تعليقات جو كينغتون على سؤالي) ، تمكنت من الحصول على الأنابيب PNG إلى FFMPEG على النحو التالي:
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')
لن يعمل بدون تصحيح, ، الذي يعدل بشكل تافه ملفين ويضيف libavcodec/png_parser.c
. اضطررت إلى تطبيق التصحيح يدويًا على libavcodec/Makefile
. أخيرًا ، أزلت "-عدد" من Makefile
للحصول على صفحات الرجل لبناء. مع خيارات التجميع ،
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
التحويل إلى تنسيقات الصور بطيئة للغاية ويضيف تبعيات. بعد النظر إلى هذه الصفحة وغيرها ، حصلت على العمل باستخدام مخازن المؤقتة غير المشفرة الخام باستخدام Mencoder (لا يزال حل FFMPEG مطلوبًا).
التفاصيل في: 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()
حصلت على بعض السرعة اللطيفة.
هذه كلها إجابات رائعة حقًا. هذا اقتراح آخر. user621442 صحيح أن عنق الزجاجة هو عادة كتابة الصورة ، لذلك إذا كنت تكتب ملفات PNG إلى ضاغط الفيديو الخاص بك ، فسيكون بطيئًا جدًا (حتى لو كنت ترسلها عبر أنبوب بدلاً من الكتابة إلى القرص). لقد وجدت حلاً باستخدام FFMPEG النقي ، والذي أجده شخصياً أسهل في الاستخدام من matplotlib.animation أو mencoder.
أيضًا ، في حالتي ، أردت فقط حفظ الصورة في محور ، بدلاً من حفظ جميع ملصقات القراد ، وعنوان الشكل ، وخلفية الشكل ، وما إلى ذلك. أردت أن أقوم بعمل فيلم/رسوم متحركة باستخدام رمز Matplotlib ، ولكن ليس لدي إنه "يشبه الرسم البياني". لقد أدرجت هذا الرمز هنا ، ولكن يمكنك عمل الرسوم البيانية القياسية وأن تنشرها إلى FFMPEG بدلاً من ذلك إذا كنت تريد.
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()
هذا عظيم! أردت أن أفعل الشيء نفسه. لكن ، لم أتمكن من تجميع مصدر FFMPEG المصحح (0.6.1) في VISTA مع MINGW32+MSYS+PR ENVIROMENT ... png_parser.c Error 1 أثناء التجميع.
لذلك ، توصلت إلى حل JPEG لهذا باستخدام PIL. فقط ضع ffmpeg.exe في نفس المجلد مثل هذا البرنامج النصي. يجب أن يعمل هذا مع FFMPEG بدون التصحيح أسفل Windows. اضطررت إلى استخدام طريقة stdin.write بدلاً من طريقة التواصل الموصى بها في الوثائق الرسمية حول العملية الفرعية. لاحظ أن خيار 2nd -vCodec يحدد ترميز الترميز. يتم إغلاق الأنبوب بواسطة 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()
فيما يلي نسخة معدلة من إجابة Tacaswell. عدل ما يلي:
- لا تتطلب
pylab
الاعتماد - إصلاح عدة أماكن ST هذه الوظيفة يمكن تشغيلها مباشرة. (لا يمكن للنسخ الأصلي نسخًا وملصقًا مباشرة ويجب إصلاح عدة أماكن.)
شكرا جزيلا لإجابة 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