夜風のMixedReality

xRと出会って変わった人生と出会った技術を書き残すためのGeekなHoloRangerの居場所

Pythonで画像からGIF画像を作成する

本日はPython枠です。

GIF画像はWebページなどで頻繁に使用される短いアニメーションを記録した画像ファイルです。

シーケンス画像とも呼べ動画よりも軽量でありながら画像にくらべ情報量が多い便利なフォーマットです。

本ブログでも頻繁に使用していますが、筆者の体感ですが外部ツールに頼らざる負えず非常に作りにくいと感じています。

また多くの外部ツールでは秒数や画像サイズなど制約が生じることが一般的です。

 筆者の場合はMicrosoft Clipchampをよく使用していますが、この場合は最大15の画像になります。 

Clipchamp自体はオンラインツールに比べ手軽に使いやすいのですが、最大の欠点と筆者が感じている点としてMicrosoftアカウントが定期的にログアウトされ起動時にログインを求められるがオフライン環境下などでログインできないことがあるということです。

 不定期にログアウトされるため野外での作業が多い筆者にとってこの点が最大の問題だと感じています。

今回はPythonで画像からGIF画像を作成していきます。

〇環境

・Windows11 PC

・Anaconda Prompt (Anaconda3)

Python 3.8

〇コード

 PythonでGIF形式を扱う上でimageioというライブラリが便利でよく使用されているようです。

 今回はあるディレクトリにあるシーケンス画像を読み込みGIFを作成することにトライしていきます。

〇仮想環境の構築

①Anaconda Promptでcdコマンドを使用して任意のディレクトリに移動します。

Pythonの仮想環境を作成します。

conda create --name gif-creator python=3.8

③仮想環境を有効化します。

conda activate gif-creator

(Base)の部分が仮想環境名に変われば正常です。

imageioライブラリを導入します。

conda install -c conda-forge imageio

エクスプローラーでディレクトリを開きPythonスクリプトを作成します。

VSCodeなどで開きスクリプト内部を編集していきます。

まずはディレクトリ内の画像を取得していきます。

import os


def create_gif(directory, output_filename):
    # ディレクトリ内のすべてのファイル名を取得
    filenames = os.listdir(directory)

    # 画像ファイルのみをフィルタリング
    image_filenames = [fn for fn in filenames if fn.endswith('.png') or fn.endswith('.jpg')]

    # 画像を読み込む
   image_filenames = [fn for fn in filenames if fn.endswith('.png') or fn.endswith('.jpg')]
   images = [imageio.v3.imread(os.path.join(directory, fn)) for fn in image_filenames]
    print(len(images))#画像数を出力

create_gif('path_to_your_directory', 'output.gif')#画像を読み込むディレクトリパスを指定

⑦任意のディレクトリにフォルダを作成します。

フォルダ内部にはGIFにしたいシーケンス画像を配置します。

⑧create_gif()の引数にディレクトリのパスを指定してAnacondaでPythonを実行します。

python  (スクリプト名).py

コンソールに画像の数が出力されていれば正常に画像を読み込んで動いていると判断できます。

⑨次にGIFの作成ですが、作成したGIF画像をエクスポートするディレクトリを作成します。

⑩作成した出力先のパスを使用してGIFを作成します。

    with imageio.get_writer(output_filename, mode='I') as writer:
        for filename in image_filenames:
            print(f"ファイルネーム:{filename}")
            image = imageio.v3.imread(filename)
            print(f"テスト:{image}")
            writer.append_data(image)

Anacondaで実行すると次のようにGIFが作成されます。

しかしこの状態では最初の1度のみアニメーションが実行されループされません。

多くのGIF画像はループ処理が行われています。

デフォルトでGIFはループ処理がされますがおそらく画像をアペンドしてGIFを作ったはいいものの再生時間などの定義が行っていないために生じていると思われます。

次に再生時間を定義します。

これはGIFを作る際の引数で定義することができます。

GIFではループ回数及びフレームの表示時間を設定することができます。

まずは生成された画像から現在の設定を確認します。

今回はチェックするための関数を作成しました。

from PIL import Image
・・・
def check_gif():
    # GIFファイルを開く
    gif_image = Image.open(r'任意のパス\output.gif')

    # フレーム数と各フレームの表示時間を取得
    metadata = []
    for i in range(gif_image.n_frames):
        gif_image.seek(i)
        duration = gif_image.info.get("duration", 0)
        metadata.append(duration)

    # 結果を表示
    print(f"フレーム数: {gif_image.n_frames}")
    print(f"各フレームの表示時間(ミリ秒): {metadata}")

これを実行すると次のようなコンソールログを取得できました。

フレーム数: 20 各フレームの表示時間(ミリ秒): [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]

各フレームの表示時間が0となっており、これが問題となっています。

これを改善するためにGIFの作成処理を変更します。

   images = []
    for filename in image_filenames:
        print(filename)
        image = Image.open(filename)
        images.append(image)

    images[0].save(
        output_filename,
        save_all=True,
        append_images=images[1:],
        duration=[1500] * len(images),  # すべてのフレームに同じdurationを指定
        loop=0
    )

こちらでは明示的に画像に情報を埋め込んでいます。

 なお、durationはミリ秒であらわさえており、1000で1秒=1秒ごとに画像が切り替わるアニメーションとなります。

これによってループ込みスピード調整込みのGIFができました。

duration = 1500(1.5秒)

duration = 100(0.1秒)

なお実験としてdurationに負の値を指定することで逆再生できるかと思ったのですが、GIF画像の定義で正の値のみのようでファイルが壊れてしまいました。

duration×frame数で動画全体の長さを定義できます。

また、動画全体の長さ(秒)/furame数をdueationに指定することで任意の動画長を作ることも可能です。

〇コード全文

import os
import imageio
from PIL import Image, ImageSequence

def create_gif(directory, output_filename):
    # ディレクトリ内のすべてのファイル名を取得
    filenames = os.listdir(directory)

    # 画像ファイルのみをフィルタリング
    image_filenames = [os.path.join(directory, fn) for fn in filenames if fn.endswith('.png') or fn.endswith('.jpg')]

    # 画像数を出力
    print(len(image_filenames))

    # GIFを作成
    images = []
    for filename in image_filenames:
        print(filename)
        image = Image.open(filename)
        images.append(image)

    images[0].save(
        output_filename,
        save_all=True,
        append_images=images[1:],
        duration=[1500] * len(images),  # すべてのフレームに同じdurationを指定
        loop=0
    )

def check_gif():
    # GIFファイルを開く
    gif_image = Image.open(r'C:\Users\seiri\Documents\PythonStudy\GIFCreator\output\output.gif')

    # フレーム数と各フレームの表示時間を取得
    metadata = []
    for i in range(gif_image.n_frames):
        gif_image.seek(i)
        duration = gif_image.info.get("duration", 0)
        metadata.append(duration)

    # 結果を表示
    print(f"フレーム数: {gif_image.n_frames}")
    print(f"各フレームの表示時間(ミリ秒): {metadata}")

# 使用例
create_gif(r'C:\Users\seiri\Documents\PythonStudy\GIFCreator\Gifinput', r'C:\Users\seiri\Documents\PythonStudy\GIFCreator\output\output.gif')
check_gif()