توليد فيلم من Python دون حفظ إطارات فردية إلى الملفات

StackOverflow https://stackoverflow.com/questions/4092927

سؤال

أرغب في إنشاء فيلم 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؟

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

هل كانت مفيدة؟

المحلول

هذه الوظيفة الآن (على الأقل اعتبارًا من 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

وثائق ل animation

نصائح أخرى

بعد تصحيح 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. عدل ما يلي:

  1. لا تتطلب pylab الاعتماد
  2. إصلاح عدة أماكن 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
مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top