Python PIL(Pillow)でアニメーション GIF を作成する方法

PythonでのアニメーションGIF作成方法解説ページのアイキャッチ

このページでは Python でアニメーション GIF を作成する方法について解説します。

ちょっとした動画をウェブサイトに載せる際などにアニメーション GIF は非常に便利です。

Python では PIL を利用すればアニメーション GIF を簡単に作成できます。このページではこのアニメーション GIF の作成方法について解説していきたいと思います。

GIFがアニメになっていない場合

GIFがアニメではなく静止画になっている場合、ページをスーパーリロードしていただくことでアニメ表示にできる可能性があります

スーパーリロードの仕方はブラウザ等によって異なりますが、「Shift キー」を押しながら更新ボタンをクリックすることで実行できることが多いです

動作確認環境

このページで紹介するサンプルスクリプトは下記環境で動作確認を行っています

  • OS:macOS Catalina
  • Python:3.8
  • Tkinter:8.6
  • NumPy:1.18.2
  • OpenCV2:4.2.0
  • Pillow:7.1.1

アニメーション GIF の作成方法

アニメーション GIF を Python で作成するための手順は大きく分けると下記の2つになります。

  • アニメーション用の複数の画像データを用意
  • PIL Image モジュールの Image クラスが提供する save メソッドを実行

複数の画像データを用意

まず前提として、アニメーション GIF はある時間おきに複数の画像データを連続して表示するファイルになります。

アニメーションGIFの構造

ですので、アニメーション GIF を作成するためには、事前に画像データを複数用意しておく必要があります(この辺りの実例はサンプルスクリプトで紹介します)。

スポンサーリンク

save メソッドによるアニメーション GIF の作成

さらに、それらの複数の画像データをつなげる形でファイルとして保存することでアニメーション GIF を作成することができます。

これは PIL Image モジュールの Image クラスが提供する save メソッドにより簡単に実現することができます。

save メソッドは他のフォーマットの画像ファイルを作成(保存)するためにも利用できますが、下記のように save メソッドに引数を指定することで、アニメーション GIF 形式のファイルとして作成することができます。

アニメーションGIFの作成
image1.save(
	"ファイル名",
	format="gif",
	save_all=True,
	append_images=image_list
)

save_allTrue として設定することで、ただの画像ではなく、アニメーション GIF を作成することができます。

この時、アニメーション GIF の先頭画像は、この save メソッドを実行したオブジェクト(PIL.Image.Image クラスのオブジェクト)の画像となります。

アニメーションの先頭画像の指定

また、アニメーション GIF の2枚目以降の画像は、append_images に指定したリストの要素が参照する画像オブジェクト(PIL.Image.Image クラスのオブジェクト)の画像となります。

アニメーションの先頭以外の画像の指定

ですので、save メソッド実行前に、2枚目以降の画像(のオブジェクト)をリストの各要素に参照させておく必要があります。

アニメーション GIF の作成時の save の引数

アニメーション GIF 作成時に save メソッドに指定する引数には下記のようなものがあります(他にも指定可能な引数はありますが、私も詳しくないので省略します)。

fp

ファイルパスもしくはファイルオブジェクトを指定します。第1引数として指定することが可能です。

format

保存する画像のフォーマットを指定します。アニメーション GIF を作成するためには "gif" を指定します。

fp で指定するファイル名の拡張子を .gif にしておけば、自動的に画像のフォーマットが GIF になるため、この場合は引数 format をわざわざ指定する必要はありません。

save_all

前述の通り、ファイルをアニメーションとして保存するかどうかを指定する引数になります。

アニメーション GIF を作成・保存する場合は save_all=True として指定します。

append_images

画像オブジェクトのリスト(参照のリスト)を指定します。

このリストの各要素が参照する画像オブジェクトがアニメーション GIF の2枚目以降の画像となります。

loop

引数 loop ではアニメーションを何回繰り返すかを設定します。

「引数 loop で指定した値+1」回分のループが行われます。

例えば loop=3 と指定すれば、アニメーションは4回ループして表示されることになります。

duration

引数 duration では、各画像を表示する時間を設定します。単位は ms です。

例えば duration=1000 と指定すれば、各画像が 1000ms、つまり1秒間表示され、その後に次の画像が表示されるようになります。

duration にはリストもしくはタプルを指定することも可能であり、各要素に時間を指定することで、画像ごとに表示時間を設定することができます。

例えば duration=[1000, 50, 50, 300] と指定すれば、画像の1枚目は 1000ms 、2枚目と3枚目は 50 ms 、4枚目は 300ms 表示されることになります。

アニメーション GIF 作成のサンプルスクリプト

アニメーション GIF を作成するサンプルスクリプトをいくつか紹介していきます。

画像としては、下記の neko_before.jpg と neko_after.jpg を使用します。

  • neko_before.jpg
    アニメーションの最初の画像
  • neko_after.jpg
    アニメーションの最後の画像
画像について

この猫の画像は「リズム727さんによる写真ACからの写真」です

めちゃめちゃ可愛いです…

スポンサーリンク

2枚の画像のアニメーション GIF の作成

まずはオーソドックスに2枚の画像のアニメーション GIF を作成するサンプルスクリプトを紹介していきます。

サンプルスクリプト

2枚の画像のアニメーション GIF を作成するサンプルスクリプトは下記になります。

2枚の画像のアニメGIF
# -*- coding:utf-8 -*-
from PIL import Image

# アニメーションの最初の画像のオブジェクト作成
before = Image.open("neko_before.jpg")

# "read of closed file" エラー回避用
before = before.copy()

# アニメーションの最後の画像のオブジェクト作成
after = Image.open("neko_after.jpg")

# "read of closed file" エラー回避用
after = after.copy()

# アニメーション GIF として保存
before.save(
	# ファイル名
	"neko_anime.gif",

	# アニメーションとして保存
	save_all=True,

	# アニメーションに含ませる画像のリスト
	append_images=[after,],

	# 画像の表示時間
	duration=1000,

	# 3回表示
	loop=2
)

生成されるアニメーション GIF

出来上がりは下のようになります。

二枚の画像をアニメーションGIFにした結果

サンプルスクリプトの説明

beforesave メソッドを実行させているので、アニメーションの最初の画像は neko_before.jpg になります。

また save メソッドの append_images に要素に after(への参照)のみを持つリストを指定していますので、2枚目の画像として neko_after.jpg が表示されます。

duration1000 を表示していますので、両方の画像が1秒ずつ表示されます。

copy メソッドの必要性

copy メソッドを実行しないと “ValueError: read of closed file” が発生することがあるため、無意味なようにも思える画像オブジェクトのコピーを行なっています

どうもファイルオブジェクトを保持している画像オブジェクト(Image.open で生成したオブジェクト)が save メソッドを実行すると、save メソッドの中でクローズ後のファイルにアクセスしてエラーになることがあるようです(JPEG では発生しなかったが、PNG では発生した)

copy メソッドではファイルオブジェクトまではコピーされないため、コピー後の画像オブジェクトで save メソッドを実行することで上記エラーを回避しています

徐々に画像が変化していくアニメーション GIF の作成

今度は1枚目の画像から2枚目の画像へ徐々に変化していくアニメーション GIF を作成するサンプルスクリプトを紹介します。

サンプルスクリプト

画像が徐々に変化するアニメーション GIF を作成するサンプルスクリプトは下記になります。

徐々に画像が変化するアニメGIF
# -*- coding:utf-8 -*-
from PIL import Image

# アニメーションの最初の画像のオブジェクト作成
before = Image.open("neko_before.jpg")

# アニメーションの最後の画像のオブジェクト作成
after = Image.open("neko_after.jpg")

# 画像を格納するリスト(空)を作成
frames = []

# beforeからafterへ徐々に変化させていく
for a in range(0, 101, 4):

	# beforeとafterの混ぜ具合を設定
	alpha = a / 100

	# beforeとafterをalphaの混ぜ具合でブレンド
	blended_image = Image.blend(before, after, alpha)

	# 作成した画像オブジェクトをリストに追加
	frames.append(blended_image)

# durationを格納するリスト(空)を作成
duration = []

# 各画像に対してdurationを設定
for i in range(len(frames)):
	if i == 0 or i == len(frames) - 1:
		# 1枚目と最後の画像だけ2000ms表示
		duration.append(2000)
	else:
		# その他の画像は50ms表示
		duration.append(50)


# アニメーション GIF として保存
frames[0].save(
	# ファイル名
	"neko_anime.gif",

	# アニメーションとして保存
	save_all=True,

	# アニメーションに含ませる画像のリスト
	append_images=frames[1:],

	# 画像の表示時間(リストで指定)
	duration=duration,

	# 3回表示
	loop=2
)

生成されるアニメーション GIF

出来上がりは下のようになります。

徐々に変化するアニメーションGIF

サンプルスクリプトの説明

スクリプト内で実行している Image.blend 関数は2つの画像(image1image2)を合成して1つの画像を生成する関数です。

画像の合成
image = Image.blend(image1, image2, alpha)

alpha(0.0 – 1.0 で指定)は、2つの画像のうちのどちらを強く合成するかを設定するものになります。

詳しい説明は省略しますが、alpha を徐々に増やしながら画像を順々に生成し、それらの画像からアニメーション GIF を作成すれば、1枚目の画像から2枚目の画像に徐々に変化していくアニメーション GIF になります。

この Image.blend 関数で生成した画像をリスト frames の末尾に追加し、frames[0]save メソッドを実行することで、アニメーション GIF の先頭画像が frames[0] の参照する画像になります(つまり neko_before.jpg)。

また save メソッドの引数 append_imagesframes[1:] を指定することで、リスト frames の第1要素(先頭から2番目の要素)から後ろの画像が追加される形でアニメーション GIF が作成されます。

さらに、このサンプルスクリプトでは save メソッドの引数 duration にリストを指定することで、画像によって表示する時間を変更しています(最初と最後の画像だけ 2000 ms、他の画像は 50ms 表示)。

アニメーション GIF をキレイに作成する方法

作成したアニメーション GIF によっては下のようにチラつくことがあります。

チラつくアニメーションGIFの例

このチラつきを解消する方法を紹介します。

スポンサーリンク

ディザを無くしてチラつきを解消

結論を言うと、このチラつきは「ディザを無くす」ことで解消できる場合があります。

ディザとは

JPEG などの画像ファイルは一般に「16777216色」を表現できますが、GIF では「256色」しか表現することができません。

したがって、JPEG などから GIF に変換する際に画像の劣化が発生します。特にグラデーション部分はノッペリした画像になってしまいます。

「ディザ」はこのノッペリ感を無くすために、ノイズを加えることで、より見栄えが元の画像に近いように施す処理のことを言います。

このディザが施された画像を拡大してみると、下のように斑模様に様々な色の点があることが確認できます。これがディザです。

ディザの例

「ディザ」については Wikipedia が図付きで解説していて分かりやすいです。詳しく知りたい方は読んでみると良いと思います。

参考 ディザWikipedia

ディザは前述の通り、より元画像に近い見栄えにすることができますが、このディザによりアニメーションがチラついて見える場合があります。

特に下のように、画像の平坦部にディザが施されると(もともと背景はグレー1色なのに下のようにピクセルに様々な色がついている)、アニメーション表示時にチラついて見えてしまう傾向にあります。

平坦部へのディザ

ディザを無くす方法

そして、このディザはアニメーション GIF 作成時の save メソッド内で自動的に実行されます。より具体的には save メソッドの中で convert("P") が実行され、この中でディザが施されるようです。

ただし、このディザは元々の画像のモードが "RGB" の時のみに施されるようになっているようです。

したがって、元々の画像のモードを "RGB" 以外に変換しておくことで、ディザによるアニメーションのチラつきを抑えることができます。

オススメなのは Image クラスの quantize メソッドを利用すことです。

quantize メソッドは指定した色数(デフォルトでは 256 色)に画像の色数を減らして画像のモードを "P" (パレットモード)に変換するメソッドになります。このメソッド実行後の画像オブジェクトでは、save メソッドでアニメーション GIF を作成した場合でもディザ処理は行われないようです。

例えば徐々に画像が変化していくアニメーション GIF の作成で紹介したスクリプトの、frames に画像オブジェクトを追加していく部分を下記のように書き換えれば、ディザを行わずにアニメーション GIF を作成することができます。

quantizeメソッドによる量子化
# 画像を格納するリスト(空)を作成
frames = []

# beforeからafterへ徐々に変化させていく
for a in range(0, 101, 4):

	# beforeとafterの混ぜ具合を設定
	alpha = a / 100

	# beforeとafterをalphaの混ぜ具合でブレンド
	blended_image = Image.blend(before, after, alpha)
	
	# ディザを避けるために事前にPモードに変換
	quantized_image = blended_image.quantize(method=0)

	# 作成した画像オブジェクトをリストに追加
	frames.append(quantized_image)

先ほどチラついていたアニメーション GIF も、ディザを無くすことで下のような結果になります。

ディザを無くした結果

quantize メソッドには引数 method を指定することができ、これにより出来上がりの画像の画質や処理速度が異なるようです。

試した感じだと method=0 だと画質がキレイだけど処理が遅く、method=2 だと画質がイマイチだけど処理速度が速い印象を受けました。

前述の通り、ディザ自体は画像の見栄えをよくするためのものです。特に自然画などのグラデーションが多い場合はディザを有効にしたほうがキレイに見えることも多いです。

基本はディザを有効にしておき、作成したアニメーションが気に入らない場合に、ディザを無くしてみるのが良いと思います。

まとめ

このページでは Python でアニメーション GIF を作成する方法について解説しました。

アニメーション GIF は PIL を利用すれば save メソッドを実行するだけで簡単に作成することができます。

ただし事前に複数の画像データを用意することと、save メソッドの引数に save_all=Trueappend_images を指定することを忘れないようにしましょう。

アニメーションのチラつきが気になる場合はディザを無くすことで改善するケースもありますので、こちらもぜひ試してみてください!

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です