動画を GIF に変換したいことってよくあると思います。(まだまだGIFの人気は根強いはず!)
私もよくあります。
数多ある、動画を GIF に変換する方法の中で、ここでは Python で動画を GIF アニメーションに変換する方法を紹介します!
変換の流れ
今回は OpenCV と Pillow というライブラリを使って Python で「動画 → GIF」の変換を行います。
どちらも pip でインストールできます!OpenCV も最近 (と言ってもけっこう前) になって pip でインストールできるようになりめちゃくちゃ便利になりました!
変換の大まかな流れは以下の通り!
- 動画を読み込む (OpenCV)
- 動画をフレーム毎に取り出して画像リストとする (OpenCV → Pillow)
- 取り出した画像リストを GIF として保存 (Pillow)
ソースコード
先に挙げた3つの流れを2つの関数に分けて実装します。
「1. 動画を読み込む」と「2. 動画をフレーム毎に取り出して画像リストとする」の処理を行う video2frames 関数。
「3. 取り出した画像リストを GIF として保存」を行う make_gif 関数。
import
まずは使うライブラリ (モジュール) をインポート!
import cv2
from PIL import Image
import tkinter as tk
import tkinter.filedialog
video2frames 関数
パラメータの path は変換対象の動画ファイルのパス
戻り値は images ( Pillow 画像リスト) と fps (元動画のフレームレート)
def video2frames(path):
cap = cv2.VideoCapture(path)
fps = cap.get(cv2.CAP_PROP_FPS)
if not cap.isOpened():
return None, None
# Pillowの画像リスト
images = []
while True:
ret, frame = cap.read()
if ret:
# BGR を RGB に変換
rgb_image = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
# Pillow画像リストにドンドン追加
pillow_image = Image.fromarray(rgb_image)
images.append(pillow_image)
else:
return images, fps
動画の読み込み
2 行目の
cap = cv2.VideoCapture(path)
で動画ファイルを開く。
5 行目の if 文で正しく開くことが出来ているかを判定しています。
無事に動画ファイルを開けたら、次にその動画を1フレームずつ読み出します。
ret, frame = cap.read()
で1フレームずつ順番に取り出しています。
ret にはフレームが読み出せたら TRUE 、読み出せなかったら FALSE が入ります。この ret を利用して処理の終了判定をしています。
frame にはそのフレームの画像情報 (ndarray) が入ってます。
Pillow画像リストに
12 行目で取り出したフレームは OpenCV のフォーマット (ndarray) なので Pillow のフォーマットに変換する必要があります。
そしてその前に、OpenCV では画像のピクセルデータが RGB ではなく BGR で保持されています。
rgb_image = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
無事 RGB にできたので、Pillow データに変換します。
pillow_image = Image.fromarray(rgb_image)
images.append(pillow_image)
で Pillow データにして、最終的な戻り値となる images (Pillow 画像リスト) に追加です!
make_gif 関数
そして今回のメイン!画像リストを GIF として保存!
パラメータについて、images は Pillow 画像リスト、save_path は保存ファイル名、fps はフレームレートです。
def make_gif(images, save_path, fps):
# フレーム毎の間隔(ミリ秒)
dur = int(1000.0 / fps)
images[0].save(save_path, save_all=True, append_images=images[1:], duration=dur, loop=0)
Pillow 画像リストを GIF として保存しているのは 5 行目!
images[0].save(save_path, save_all=True, append_images=images[1:], duration=dur, loop=0)
この save メソッドの各パラメータ (引数) について。
第一引数
保存ファイル名 (パス) です。“~.gif” としましょう。
save メソッドには保存する画像のタイプを指定する format というパラメータがあるのですが、省略した場合はファイル名の拡張子から決定されます。
save_all
GIF ファイルを作成する場合は True!これを True にすることで画像リストをすべてつなげて GIF として出力できます。
append_images
GIF にしたい画像のリストを渡します。
もうちょっと正確には、この save メソッドを呼び出した Pillow 画像 と つなげて GIF にする画像のリストです。
なので画像リストの先頭 images[0] で save メソッドを呼び出し、その後につなげる部分をスライスして images[1:] のように渡しています。
duration
GIF アニメーションの各画像の表示時間です。フレーム間の時間とも言えそう。単位はミリ秒。
今回は動画を GIF にするので元動画のフレームレート (frames per second) から計算しています。
フレームレートを \(f\) とすると、各フレームの表示時間 \(d\) (ミリ秒) は以下の式で求められます。
\[d = \frac{1000}{f}\]
loop
GIF アニメーションの繰り返し回数です。無限ループにしたい場合は 0 を指定!
省略した場合は繰り返されません。
optimize
これは今は使っていないのですが、出力された GIF の結果がおかしくなる場合は False にして実行してみると改善することがある。
このパラメータが True の場合、出力 GIF の最適化をしてくれるようです。未使用の色を取り除くことでカラーパレットサイズを小さくしてくれるっぽい。
その他のパラメータ
GIF を保存するときの save メソッドには上で使ったもの以外にいくつかパラメータがあります。
詳しくは以下公式ドキュメントを参照してください。
ソースコード全体
上で登場した2つの関数とそれらを呼び出す main() 関数を書いて完成です!
import cv2
from PIL import Image
import tkinter as tk
import tkinter.filedialog
def video2frames(path):
"""
動画をフレームに分解し、それをPillow画像リストとして返す。
@ path: 動画のパス
return: Pillow画像リスト、フレームレート
"""
cap = cv2.VideoCapture(path)
fps = cap.get(cv2.CAP_PROP_FPS)
if not cap.isOpened():
return None, None
# Pillowの画像リスト
images = []
while True:
ret, frame = cap.read()
if ret:
# BGR を RGB に変換
rgb_image = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
# Pillow画像リストにドンドン追加
pillow_image = Image.fromarray(rgb_image)
images.append(pillow_image)
else:
return images, fps
def make_gif(images, save_path, fps):
"""
Pillow画像リストと保存先パスを受け取りGIFファイルを生成。
@ images : Pillow画像リスト
@ save_path: 保存GIFファイル名
@ fps : フレームレート
"""
# 各フレームの表示時間(ミリ秒)
dur = int(1000.0 / fps)
images[0].save(save_path, save_all=True, append_images=images[1:], duration=dur, loop=0)
print("frame rate = ", fps)
print("duration = ", dur)
def main():
# ダイアログを開いてファイルを選択
root = tk.Tk()
root.withdraw()
target_file = tkinter.filedialog.askopenfilename(filetypes=[("動画", "*.mp4")])
# 出力されるGIFの保存先
save_path = target_file.rsplit(".", 1)[-2] + ".gif"
# 変換開始
images, fps = video2frames(target_file)
make_gif(images, save_path, fps)
if __name__ == "__main__":
main()
58 行目で読み込んだ元動画のファイル名の拡張子を除いた部分に .gif を付けて保存ファイル名としています。これで元動画と同じディレクトリにGIF変換された動画が保存されます。
例えば元動画が「kiyosumi.mp4」の場合、同じディレクトリに「kiyosumi.gif」が作成されます!
コメント