개별 프레임을 파일에 저장하지 않고 Python에서 동영상 생성
matplotlib의 Python 스크립트에서 생성 한 프레임에서 h264 또는 divx 동영상을 만들고 싶습니다. 이 영화에는 약 10 만 프레임이 있습니다.
웹의 예에서 [예 : 1], 저는 각 프레임을 png로 저장 한 다음이 파일에 대해 mencoder 또는 ffmpeg를 실행하는 방법 만 보았습니다. 제 경우에는 각 프레임을 저장하는 것이 비현실적입니다. matplotlib에서 생성 된 플롯을 가져 와서 ffmpeg로 직접 파이프하여 중간 파일을 생성하지 않는 방법이 있습니까?
ffmpeg의 C-api로 프로그래밍하는 것은 나에게 너무 어렵습니다. 2]. 또한 x264와 같이 압축률이 좋은 인코딩이 필요합니다. 그렇지 않으면 동영상 파일이 후속 단계에 비해 너무 커집니다. 따라서 mencoder / ffmpeg / x264를 고수하는 것이 좋습니다.
파이프 [3]로 할 수있는 일이 있습니까?
[1] http://matplotlib.sourceforge.net/examples/animation/movie_demo.html
[2] x264 C API를 사용하여 일련의 이미지를 H264로 인코딩하는 방법은 무엇입니까?
[3] http://www.ffmpeg.org/ffmpeg-doc.html#SEC41
이 기능은 이제 MovieWriter
클래스 를 통해 matplotlib에 구워진 (적어도 1.2.0, 어쩌면 1.1 일 수도 있음) 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를 패치 한 후 (Joe Kington이 내 질문에 대한 의견을 참조하십시오) 다음과 같이 파이핑 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
빌드 할 매뉴얼 페이지를 가져 오기 위해 에서 '-number'를 제거했습니다 . 컴파일 옵션을 사용하면
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()
This is great! I wanted to do the same. But, I could never compile the patched ffmpeg source (0.6.1) in Vista with MingW32+MSYS+pr enviroment... png_parser.c produced Error1 during compilation.
So, I came up with a jpeg solution to this using PIL. Just put your ffmpeg.exe in the same folder as this script. This should work with ffmpeg without the patch under Windows. I had to use stdin.write method rather than the communicate method which is recommended in the official documentation about subprocess. Note that the 2nd -vcodec option specifies the encoding codec. The pipe is closed by 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()
Here is a modified version of @tacaswell 's answer. Modified the following:
- Do not require the
pylab
dependency - Fix several places s.t. this function is directly runnable. (The original one cannot be copy-and-paste-and-run directly and have to fix several places.)
Thanks so much for @tacaswell 's wonderful answer!!!
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
'IT Share you' 카테고리의 다른 글
MEF에 대해 어디서 배울 수 있습니까? (0) | 2020.11.11 |
---|---|
로드 후 평가 대 모드 후크 (0) | 2020.11.11 |
Mono https 웹 요청이 "인증 또는 암호 해독에 실패했습니다."와 함께 실패 (0) | 2020.11.11 |
Git 분기 액세스를 제한하는 방법? (0) | 2020.11.11 |
craigslist.org 용 개발자 API가 있습니까? (0) | 2020.11.11 |