連番画像からパラパラ漫画風のmp4動画を作成する

目的

AviUtlにて,連番BMP出力(または連番PNG出力)した画像から特定のfps(秒間フレーム数)のパラパラ漫画風のmp4動画ファイルを作成する.

Python3は既にインストールされている前提で,PythonOpenCVライブラリーを使用した方法を紹介する.

名称 規格 製造会社 備考
オペレーティングシステム Windows 10 Pro 64ビット,22H2 マイクロソフト
Python3 3.12.2 Python Software Foundation python3 --versionにて確認
package installer for Python(pip) 24.0 Python Software Foundation python3 -m pip listにて確認
opencv-python 4.9.0.80 OpenCV Team
Python Image Library(Pillow,PIL) 10.2.0 gif出力は採用せず(後述)

結論

インストール~mp4動画生成までを順番に記載する

インストール

D:\>python3 -m pip list
Package Version
------- -------
pillow  10.2.0
pip     24.0

D:\>python3 -m pip install opencv-python
Defaulting to user installation because normal site-packages is not writeable
Collecting opencv-python
  Using cached opencv_python-4.9.0.80-cp37-abi3-win_amd64.whl.metadata (20 kB)
Collecting numpy>=1.21.2 (from opencv-python)
  Downloading numpy-1.26.4-cp312-cp312-win_amd64.whl.metadata (61 kB)
     ---------------------------------------- 61.0/61.0 kB 819.4 kB/s eta 0:00:00
Using cached opencv_python-4.9.0.80-cp37-abi3-win_amd64.whl (38.6 MB)
Downloading numpy-1.26.4-cp312-cp312-win_amd64.whl (15.5 MB)
   ---------------------------------------- 15.5/15.5 MB 46.7 MB/s eta 0:00:00
Installing collected packages: numpy, opencv-python
  WARNING: The script f2py.exe is installed in 'C:\Users\so2akt\AppData\Local\Packages\PythonSoftwareFoundation.Python.3.12_qbz5n2kfra8p0\LocalCache\local-packages\Python312\Scripts' which is not on PATH.
  Consider adding this directory to PATH or, if you prefer to suppress this warning, use --no-warn-script-location.
Successfully installed numpy-1.26.4 opencv-python-4.9.0.80

D:\>python3 -m pip list
Package       Version
------------- --------
numpy         1.26.4
opencv-python 4.9.0.80
pillow        10.2.0
pip           24.0

f2py.exeが既にインストールされているがPATHに登録されていないという警告が出ているが,今回の手順では問題なかったため,無視して進めている.

連番画像からmp4動画作成

以下のPythonコードを,連番画像と同一のディレクトリーで実行する.以下の例では,連番画像が5_00000.png5_00001.png5_00002.png,……,5_01113.pngの合計1114枚あり,それら画像を約0.618(≒2200 / 3555)fps*15.mp4として動画にするように処理している.

import numpy as np
import cv2

filnam = '5'
delim = '_'
ext_in = '.png'
ext_out = '.mp4'
zfill_num = 5      # 連番部分が00001という形式のように5ケタなので5
fps = 2200 / 3555 # 計算値の小数点以下の精度は不明……
frm_num = 1113     # フレーム数(≒結合する画像の連番番号最大値)

# 1枚目の画像を読み込んで,幅と高さを確定させる
img = cv2.imread(filnam + delim + str(0).zfill(zfill_num) + ext_in)
w = img.shape[1]
h = img.shape[0]
codec = cv2.VideoWriter_fourcc(*'mp4v')
video = cv2.VideoWriter(filnam + ext_out, codec, fps, (w, h))

for i in range(frm_num):
    img = cv2.imread(filnam + delim + str(i).zfill(zfill_num) + ext_in)
    video.write(img)
video.release()

画像ファイル名が連番ではない場合

連番画像ではあるが,5_00000.png5_00029.png5_00059.png,……というように数字で言う30ごとに飛び飛びのファイル名の場合,上記のPythonコードを一部変更する必要がある:

import numpy as np
import cv2

filnam = '5'
delim = '_'
ext_in = '.png'
ext_out = '.mp4'
zfill_num = 5      # 連番部分が00001という形式のように5ケタなので5
fps = 2200 / 3555 # 計算値の小数点以下の精度は不明……
frm_sta = 0        # 結合する画像の連番番号開始値
frm_end = 1113     # 結合する画像の連番番号最大値
frm_delta = 30     # 結合する画像の連番番号増分

# 1枚目の画像を読み込んで,幅と高さを確定させる
img = cv2.imread(filnam + delim + str(0).zfill(zfill_num) + ext_in)
w = img.shape[1]
h = img.shape[0]
codec = cv2.VideoWriter_fourcc(*'mp4v')
video = cv2.VideoWriter(filnam + ext_out, codec, fps, (w, h))

# 00000番から始める場合は,0→29→59→……としたいが,増分30では,0→30→60→……となってしまうため,-1から繰り返す
# それ以外の,たとえば,29→59→89のように00029番から始める場合は29から繰り返せばOK
if frm_sta == 0:
    frm_sta = -1
for i in range(frm_sta, frm_end, frm_delta):
    # iが-1のときは00000番を意味するのでif文で分岐している
    img = cv2.imread(filnam + delim + str(0 if i < 0 else i).zfill(zfill_num) + ext_in)
    video.write(img)
video.release()

その他

感想

当初はmp4ではなく,Pillowライブラリーを使ったgifファイルとして生成し,AviUtlでイロイロ結合してmp4出力させようとしていた.しかしながら,gifをAviUtlで複数回読み込ませるとAviUtlで例外が発生してAviUtlが強制終了してしまっていたため,OpenCVを使用してmp4ファイル生成に至った.

OpenCVのインストールに手間・時間がかかると思い込んでいたが,Pythonライブラリーとしてのインストールは数秒であり,使い方例も豊富に存在したので手軽に実行できた.

なお,Pillowライブラリーを使ったgifファイル生成のソースコードは以下のとおりだが*2

from PIL import Image

filnam = '5'
delim = '_'
ext_in = '.png'
ext_out = '.gif'
zfill_num = 5      # ファイル_00001.pngなので5ケタ
dur = 3555 / 2200 # フレーム間隔ms
frm_num = 1113     # フレーム数
pictures = []

for i in range(frm_num):
    pic_name = filnam + delim + str(i).zfill(zfill_num) + ext_in
    img = Image.open(pic_name)
    pictures.append(img)
pictures[0].save(filnam + ext_out, save_all = True, append_images = pictures[1:], optimize = True, duration = dur, loop = 0)

今までは……

以下の画像のように,1枚ごとにAviUtlに読み込ませる→長さを変更,を画像の枚数ごとに繰り返していた.さすがにもっとよい方法があるだろうと思い,今回の対策大作にたどり着いた.

従来はなんと1枚ごとに画像読み込み・長さ変更

改訂履歴

# 日付 内容
1 2024/02/11 Pillowライブラリーを使用したgifファイル出力について追記
2 2024/02/17 画像ファイル名が連番ではない場合のPythonコードを追記

参考サイト

  1. OpenCVで動画作成 VideoWriter - Python徹底解説 · Daizyu.com(2024/02/10現在)
  2. Python(Google Colab)で.pngを.mp4にする(2024/02/10現在)
  3. 【python】複数画像からパラパラ漫画(gifアニメ)を作成する方法! - ヒガサラblog(2024/02/11現在)
  4. [解決!Python]条件式(三項演算子)を使ってif文を1行で書くには:解決!Python - @IT(2024/02/17現在)

*1:今回作成時のfpsなので任意の値にすればOK.秒間30フレームなら30.

*2:所望の動画は作成されるが,画質が劣化していたのでそれも併せて採用を見送っている.