Python Tips: アニメーション GIF から静止画をまとめて抽出したい

Python でアニメーション GIF ( animated GIF )からフレーム画像を抽出する方法をご紹介します。

早速結論ですが、 Python の画像処理用ライブラリ Pillow を使うのが比較的かんたんです。他にも方法は無数にあるかと思いますが、私は Pillow でやるのがスムーズでした。

Pillow は画像処理ライブラリ PIL のフォークで、 PIL が対応していない Python 3.x に対応しているのがポイントです。 Pillow は pip でインストールするときには名称 Pillow でインストールしますが、スクリプトの中で import するときは PIL と同じ PIL という名前を使用します。

$ pip install Pillow
from PIL import Image

Pillow のドキュメントサイトはこちらです。

実際に、 Pillow を使ってアニメーション GIF を分解する方法を見てみましょう。

# coding: utf-8

'''アニメーション GIF のフレーム画像(静止画)を抽出する
'''

from pathlib import Path
from PIL import Image, ImageSequence

# 分割したいアニメーション GIF 画像
IMAGE_PATH = 'target.gif'
# 分割した画像の出力先ディレクトリ
DESTINATION = 'splitted'
# 現在の状況を標準出力に表示するかどうか
DEBUG_MODE = True

def main():
    frames = get_frames(IMAGE_PATH)
    write_frames(frames, IMAGE_PATH, DESTINATION)

def get_frames(path):
    '''パスで指定されたファイルのフレーム一覧を取得する
    '''
    im = Image.open(path)
    return (frame.copy() for frame in ImageSequence.Iterator(im))

def write_frames(frames, name_original, destination):
    '''フレームを別個の画像ファイルとして保存する
    '''
    path = Path(name_original)

    stem = path.stem
    extension = path.suffix

    # 出力先のディレクトリが存在しなければ作成しておく
    dir_dest = Path(destination)
    if not dir_dest.is_dir():
        dir_dest.mkdir(0o700)
        if DEBUG_MODE:
            print('Destionation directory is created: "{}".'.format(destination))

    for i, f in enumerate(frames):
        name = '{}/{}-{}{}'.format(destination, stem, i + 1, extension)
        f.save(name)
        if DEBUG_MODE:
            print('A frame is saved as "{}".'.format(name))

if __name__ == '__main__':
    main()

このスクリプトを実行すると、アニメーション GIF ファイル target.gif の静止画をすべて抽出して splitted というフォルダに書き出してくれます。元の GIF 画像には変更を加えることなく、抽出した画像を、末尾に連番をつけた形でファイルとして切り出します。

ポイントは PIL.ImageSequence.Iterator クラスです。 PIL.Image で開いた画像をこのコンストラクタに渡すと、アニメーション GIF 内の各フレーム(静止画)を返すイテレータオブジェクトを生成してくれます。

便利ですねー。

参考

逆に、静止画を組み合わせてアニメーション GIF を作る方法については、次の Stack Overflow ページでやりとりされています。興味のある方はこちらもよろしければ。