Генерация фильма из питона без сохранения отдельных кадров в файлы - PullRequest
65 голосов
/ 04 ноября 2010

Я хотел бы создать фильм h264 или divx из кадров, которые я генерирую в скрипте python в matplotlib.В этом фильме около 100 тыс. Кадров.

В примерах в Интернете [например,1], я видел только способ сохранения каждого кадра в формате png и последующего запуска mencoder или ffmpeg для этих файлов.В моем случае сохранение каждого кадра нецелесообразно.Есть ли способ взять график, сгенерированный из matplotlib, и направить его напрямую в ffmpeg, не создавая промежуточных файлов?

Программирование на C-api в ffmpeg слишком сложно для меня [например,2].Кроме того, мне нужна кодировка с хорошим сжатием, такая как x264, поскольку файл фильма в противном случае будет слишком большим для следующего шага.Так что было бы здорово придерживаться mencoder / ffmpeg / x264.

Есть ли что-то, что можно сделать с трубами [3]?

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

[2] Как можно кодировать серию изображений в H264 с помощью API x264 C?

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

Ответы [ 6 ]

50 голосов
/ 21 декабря 2012

Эта функциональность теперь (по крайней мере, с 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

20 голосов
/ 07 ноября 2010

После исправления 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')

Это не будет работать без patch , который тривиально изменяет два файла и добавляет libavcodec/png_parser.c.Мне пришлось вручную применить патч к libavcodec/Makefile.Наконец, я удалил '-number' из 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
13 голосов
/ 17 февраля 2011

Преобразование в графические форматы происходит довольно медленно и добавляет зависимости.Посмотрев эти и другие страницы, я начал работать с необработанными незакодированными буферами, используя mencoder (решение ffmpeg все еще требуется).

8 голосов
/ 09 апреля 2015

Это действительно отличные ответы. Вот еще одно предложение. @ 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()
5 голосов
/ 06 января 2011

Это здорово!Я хотел сделать то же самое.Но я так и не смог скомпилировать исправленный исходный код ffmpeg (0.6.1) в Vista с помощью MingW32 + MSYS + pr enviroment ... png_parser.c выдал ошибку Error1 во время компиляции.

Итак, я нашел решение jpegк этому с помощью PIL.Просто поместите ваш ffmpeg.exe в ту же папку, что и этот скрипт.Это должно работать с ffmpeg без патча под Windows.Мне пришлось использовать метод stdin.write, а не метод связи, который рекомендуется в официальной документации о подпроцессе.Обратите внимание, что опция 2 -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()
0 голосов
/ 03 июля 2019

Вот модифицированная версия ответа @tacaswell.Изменено следующее:

  1. Не требуется pylab зависимость
  2. Исправлено несколько мест, где эта функция напрямую запускается.(Оригинал не может быть скопирован и вставлен и запущен напрямую и должен исправить несколько мест.)

Большое спасибо за прекрасный ответ @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
...