やりたいこと
Pythonで画像列を作って動画にしたいことはよくある。よく転がっているコードは以下の2つ。
- OpenCVのVideoWriterを使う → 保存形式を細かく設定する方法がわかりにくい。
- 一旦ファイルに書き出してffmpegを使う → 先にすべての画像を作っておく必要がある。また,ファイルに書き出してまた読む余分な時間が無駄。
今回は,
- 動的に生成されるnumpy.ndarray画像列を
- ffmpegのオプションを使って
- 余分なファイル入出力無しで
- 動画ファイルとして保存したい
できたもの(Python 3.5)
ffmpegには入力画像列をパイプで受け取る機能があるので,Popenして順番に渡してやればいい。
def save_images_as_movie(filename, images, fps, ffmpeg_args='-y', ffmpeg_executable='ffmpeg'):
"""画像列を動画ファイルとして保存する.
Parameters
----------
filename: str
保存先ファイル名.
images: iterable of array-like
動画として保存する画像列.
fps: int
動画のフレームレート.
ffmpeg_args: str or iterable of str
ffmpegの出力ファイル設定.
例: PowerPointで再生可能かつ無圧縮にしたいなら '-vcodec rawvideo -pix_fmt yuv420p'
ffmpeg_executable: str
ffmpegの実行ファイルパス.
Return
------
completed_process: subprocess.CompletedProcess
ffmpegの実行結果.
Examples
--------
>>> import numpy as np
>>> images = [np.full(shape=(128, 128), fill_value=v, dtype=np.uint8) for v in np.linspace(0, 255, 60)]
>>> result = save_images_as_movie('example.mp4', images, fps=60)
>>> result.check_returncode()
"""
from subprocess import Popen, PIPE, CompletedProcess
from cv2 import imencode
if isinstance(ffmpeg_args, str):
ffmpeg_args = ffmpeg_args.split()
ffmpeg = Popen(
args=[
ffmpeg_executable,
'-r', str(fps), '-f', 'image2pipe', '-i', '-',
'-r', str(fps), *ffmpeg_args, filename,
],
stdin=PIPE,
stdout=PIPE,
stderr=PIPE,
)
try:
for image in images:
success, buffer = imencode('.bmp', image)
if not success:
raise ValueError('imencode failed')
ffmpeg.stdin.write(buffer)
finally:
out, err = ffmpeg.communicate()
return CompletedProcess(
args=ffmpeg.args,
returncode=ffmpeg.returncode,
stdout=out,
stderr=err,
)