Генерация фильма из Python без сохранения отдельных кадров к файлам
-
28-09-2019 - |
Вопрос
Я хотел бы создать фильм 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/example/animation/movie_demo.html.
[2] Как можно кодировать серию изображений в H264, используя API 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 (см. Джо Кингтон комментарии к моим вопросам), я смог получить Tobing 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-файлы на свой видео компрессор, он будет довольно медленным (даже если вы отправляете их через трубу вместо записи на диск). Я нашел решение, используя Pure Pure 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 Encirement ... PNG_Parser.creation Errume1 во время компиляции.
Итак, я придумал решение JPEG для этого, используя Pil. Просто поставьте свой ffmpeg.exe в той же папке, что и этот скрипт. Это должно работать с FFMPEG без патча под окнами. Мне пришлось использовать метод 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 Эта функция напрямую Runnable. (Оригинал не может быть скопирован и вставлять и запущен напрямую и должен исправить несколько мест.)
Большое спасибо за прекрасный ответ @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