Python で動画を GIF に変換

プログラミング

動画を GIF に変換したいことってよくあると思います。(まだまだGIFの人気は根強いはず!)

私もよくあります。

数多ある、動画を GIF に変換する方法の中で、ここでは Python で動画を GIF アニメーションに変換する方法を紹介します!

変換の流れ

今回は OpenCVPillow というライブラリを使って Python で「動画 → GIF」の変換を行います。

どちらも pip でインストールできます!OpenCV も最近 (と言ってもけっこう前) になって pip でインストールできるようになりめちゃくちゃ便利になりました!

変換の大まかな流れは以下の通り!

  1. 動画を読み込む (OpenCV)
  2. 動画をフレーム毎に取り出して画像リストとする (OpenCV → Pillow)
  3. 取り出した画像リストを GIF として保存 (Pillow)

ソースコード

先に挙げた3つの流れを2つの関数に分けて実装します。

「1. 動画を読み込む」と「2. 動画をフレーム毎に取り出して画像リストとする」の処理を行う video2frames 関数

「3. 取り出した画像リストを GIF として保存」を行う make_gif 関数

import

まずは使うライブラリ (モジュール) をインポート!

python
import cv2
from PIL import Image
import tkinter as tk
import tkinter.filedialog

video2frames 関数

パラメータの path は変換対象の動画ファイルのパス

戻り値は images ( Pillow 画像リスト) と fps (元動画のフレームレート)

python
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


# 以下 web 上での表示のハイライトのために書いてるだけです!
# 書いていないとなぜか文字色がおかしくなります
def main():
    pass

動画の読み込み

2 行目

cap = cv2.VideoCapture(path)

で動画ファイルを開く。

5 行目の if 文で正しく開くことが出来ているかを判定しています。

無事に動画ファイルを開けたら、次にその動画を1フレームずつ読み出します。

12 行目

ret, frame = cap.read()

で1フレームずつ順番に取り出しています。

ret にはフレームが読み出せたら TRUE 、読み出せなかったら FALSE が入ります。この ret を利用して処理の終了判定をしています。

frame にはそのフレームの画像情報 (ndarray) が入ってます。

Pillow画像リストに

12 行目で取り出したフレームは OpenCV のフォーマット (ndarray) なので Pillow のフォーマットに変換する必要があります。

そしてその前に、OpenCV では画像のピクセルデータが RGB ではなく BGR で保持されています。

なので 15 行目RGB の順にしています。

rgb_image = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)

無事 RGB にできたので、Pillow データに変換します。

18 行目

pillow_image = Image.fromarray(rgb_image)
images.append(pillow_image)

で Pillow データにして、最終的な戻り値となる images (Pillow 画像リスト) に追加です!

make_gif 関数

そして今回のメイン!画像リストを GIF として保存!

パラメータについて、images は Pillow 画像リスト、save_path は保存ファイル名、fps はフレームレートです。

ソースコードは「Shift」を押しながらスクロールすることで “横スクロール” できます

python
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)


# 以下 web 上での表示のハイライトのために書いてるだけです!
# 書いていないとなぜか文字色がおかしくなります
def main():
    pass

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() 関数を書いて完成です!

ソースコードは「Shift」を押しながらスクロールすることで “横スクロール” できます

python
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」が作成されます!

コメント

タイトルとURLをコピーしました