概要
Google ColabでGIF動画を作りインライン表示させるまでの3つの方法(pillow, imageio, imagemagickのconvert)を紹介する。Google Colabで作成したコードは、こちらにあります。
方法
- GIF動画にしたい画像ファイルをGoogle Colabにアップロード、またはGoogle Colab内で作成
- globで画像ファイル名を取得
- GIF動画を作り保存(pillow、imageio、imagemagickのconvert)(3種類紹介しますが、どれか1つ使うと良いです。)
- 保存したGIF動画をIPythonで開きインライン表示
1. GIF動画にしたい画像をGoogle Colabにアップロード、またはGoogle Colab内で作成
-
まず、Google Colabを立ち上げます。
-
下図の青枠からGIF動画にしたい画像をアップロードするか、Google Colab内でコードを書きその場で画像を作成します。JPG画像を用意した人はPNG画像に変換してください。pillowの公式サイトによると、JPG画像は対応していないためです。変換方法は、jpgをpngに変換しても透明度がいじれなかったからなんとかしたを参照してください。
GIF動画に使う画像は、01.png, 02.png, ..., 60.pngのようにGIF画像に使う枚数の最大桁数で0埋めした通し番号の名前を付けると良いです。
(組み込み関数sorted()
を用いたsorted(glob.glob('*.png'))
で順番を管理しやすくなります。0埋めにしないファイル名でsorted(glob.glob('*.png'))
を使うと、1.png, 10.png, 2.png, ...となってしまいます。回避する方法があるので、方法の0埋めにしていないファイル名を読み込みソートするで紹介します。)
-
ここで、画像が置いてあるか確認するため、セルに
!ls
と入力して実行してください。自分が用意した画像ファイル名が表示されて入ればOKです。(セルの先頭に「!
」を付与するとコマンドとして認識されます。)
用意した画像ファイル名が表示されない場合
画像が置いてあるパスと今いるパスが異なる場合があります。セルに``!pwd``を入力して実行してください。今いるパスが分かります。立ち上げた後パスの移動をしていないと``/content``と返ってくると思います。画像があるパスを確認して異なっていたら、``%cd /画像のパス``で画像のパスまで移動してください。(ここは、「``!``」を使った``!cd /画像のパス``では、パスの移動はできません。詳しくは、[!cd と %cd の違い](https://qiita.com/bear_montblanc/items/64d7efd9e4ea7caa98f9)を参照してください。)今回サンプルとして、時計の秒針をイメージしたも60枚のPNG画像を用意しています。
# サンプルの複数画像(時計の秒針をイメージしたもの)
import numpy as np
import matplotlib.pyplot as plt
def get_rotate_coord(angle, coord):
theta = np.radians(angle) # °からラジアンに変換
c, s = np.cos(theta), np.sin(theta)
R = np.array([[c, -s], [s, c]]) # 2次元の回転行列
rotate_coord = np.dot(R, coord) # 回転後の座標を取得
return rotate_coord
start_coord = np.array([0, 10]) # この座標の原点を軸に回転させる
for sec in range(60):
fig = plt.figure(figsize=(5, 5))
ax = fig.add_subplot(1, 1, 1)
rotate_coord = get_rotate_coord(-6*sec, start_coord)
ax.plot((0, rotate_coord[0]), (0, rotate_coord[1]), lw=3)
circle = plt.Circle((0, 0), 11, fill=False) # 円をプロット
ax.add_artist(circle)
ax.set_title(sec)
ax.set_xlim(-12, 12)
ax.set_ylim(-12, 12)
ax.axis('off')
plt.savefig('{:0>2}.png'.format(sec)) # 0埋め2桁のファイル名
plt.close()
2. globで画像ファイル名を取得
0埋めにしたファイル名を読み込みソートする
import glob
filenames = sorted(glob.glob('*.png'))
print(filenames)
とすると、['00.png', '01.png', '02.png',..., '60.png']
と画像ファイル名が読み込まれているのが分かります。
0埋めにしていないファイル名を読み込みソートする
0埋めにしていない通し番号の画像ファイル名を読み込む場合は、natsortのnatsorted()を使うと順番に読み込めます。
import glob
from natsort import natsorted
filenames = natsorted(glob.glob('*.png'))
3. GIF動画を作り保存(pillow、imageio、imagemagickのconvert)
3つの方法を紹介します。どれか1つ使えば良いです。
pillow編
# pillow編
import PIL
# GIF動画の作成
images = [PIL.Image.open(filename) for filename in filenames] # PNGファイルを開く
out_filename = 'image_pillow.gif' # GIF動画のファイル名
'''
パラメータ
--------
image[0] : GIF動画の先頭に使う画像
out_filename : 保存するGIF動画のファイル名
save_all : 画像を全て保存するか(GIF動画を作るので画像を全て保存するTrueを選択する。)
append_images : 2枚目以降のGIF動画に使う画像のリスト
(images[1:]とスライスを使うと、2枚目以降の画像のリストになる。)
duration : 表示間隔(単位は1/1000秒)
loop : ループ回数(0は無限ループ)
--------
'''
images[0].save(out_filename,
save_all=True, append_images=images[1:], duration=1000, loop=0)
imageio編
# imageio編
import imageio
# GIF動画の作成
images = [imageio.imread(filename) for filename in filenames] # PNGファイルを開く
out_filename = 'image_imageio.gif' # GIF動画のファイル名
'''
パラメータ
--------
out_filename : 保存するGIF動画のファイル名
images : GIF動画に使う画像のリスト
duration : 表示間隔(単位は秒)
loop : ループ回数(0は無限ループ)
--------
'''
imageio.mimsave(out_filename, images, duration=1.00, loop=0)
imagemagickのconvert編
# imagemagickのconvert編
!sudo apt-get install imagemagick # imagemagickをインストール
# GIF動画の作成
filenames_str = ' '.join(filenames) # 00.png 01.png 02.png…60.pngの文字列にする
out_filename = 'image_imagemagick.gif' # GIF動画のファイル名
'''
パラメータ
--------
delay : 表示間隔(単位は1/100秒)
loop : ループ回数(0は無限ループ)
filenames_str : GIF動画に使う画像の名前が入った文字列
out_filename : 保存するGIF動画のファイル名
--------
'''
!convert -delay 100 -loop 0 $filenames_str $out_filename
4. 保存したGIF動画をIPythonで開きインライン表示
# pillowで作成したGIF動画(3種類のどれでも良い)
import IPython
out_filename = 'image_pillow.gif'
IPython.display.Image(out_filename, format='png')
出力結果
時計の秒針みたいですね。Google Colabにインライン表示できていれば成功です。
途中で一定期間静止させたGIF動画の作成方法
一定期間静止させたい画像の表示枚数を増やすことで静止させることができます。つまり、方法の2. globで画像ファイル名を取得のコードのみを書き換え、その他は同じ手順です。
ファイル名と追加したい枚数を指定するための関数append_filename
を以下のように定義します。
def append_filename(filenames, filename, flame_num):
'''
パラメータ
--------
filenames : GIF画像用のファイル名が入ったリスト
filename : 枚数を追加したいファイル名
flame_num : 追加する枚数
--------
'''
if filename in filenames: # filenamesにfilenameが存在するか確認(次行のエラー対策)
index_filename = filenames.index(filename)
for _ in range(flame_num):
filenames.insert(index_filename, filename)
else:
print('ファイル名{}は、filenamesにはないです。'.format(filename))
return filenames
コードの説明です。
-
index_filename
で枚数を増やしたいファイル名のindexを取得します。 -
filenames.insert(index_filename, filename)
でそのindexの1つ前に枚数を増やしたいファイル名と同じファイル名を加えます。この同じファイル名の追加を複数回繰り返すとindexが変わってしまうと一見思いますが、最初に指定したindexのindex_filename
には追加したファイル名の1つ前に常に追加できるので問題ないです。
ここは、今回作ったコードで作るGIF動画が生きる場面です。例えば、filenames=['00.png', '01.png', '02.png', '03.png']
に対して、append_filename(filenames=filenames, filename='02.png', flame_num=3)
とするとどうなるでしょうか。
以下のように追加されていく過程が分かります。このように、3枚の02.pngが追加されて、filenames=['00.png', '01.png', '02.png', '02.png', '02.png', '02.png', '03.png']
となります。
早速、途中で一定期間静止したGIF動画を作っていきます。
2. globで画像ファイル名を取得のコードに一定期間静止させたいファイル名と追加する枚数を指定して、その他は同じ手順です。(例では、pillowを使いduaration=1000
からduaration=60
に変更してます。)
import glob
filenames = sorted(glob.glob('*.png')) # ファイル名を読み込みソートする
'''ここから一定期間静止させるために変更したコード'''
filenames = append_filename(filenames, '00.png', 20) # 00.pngを20枚追加
filenames = append_filename(filenames, '15.png', 5) # 15.pngを5枚追加
filenames = append_filename(filenames, '30.png', 20) # 30.pngを20枚追加
loopの最後に一定期間静止させたGIF動画の作成方法
ここで、loopの最後に一定期間静止させた例も紹介します。途中で一定期間静止させたGIF動画の作成方法と同様に、関数のappend_filename
の引数に最後のファイル名を入力してももちろん良いです。ですが、毎回ファイル名を確認して打ち込むと大変なので、リストfilenames
の最後ということで**filenames[-1]
を使うと汎用性が高い**です。(例では、pillowを使いduaration=1000
からduaration=60
に変更してます。)
import glob
filenames = sorted(glob.glob('*.png')) # ファイル名を読み込みソートする
'''ここから一定期間静止させるために変更したコード'''
# loopの最後のファイルを20枚追加(ファイル名を入力するよりリストのindexで最後を指定する方が楽)
filenames = append_filename(filenames, filenames[-1], 20)
3種類の方法の比較
3種類(pillow、imageio、imagemagickのconvert)の処理速度とデータサイズを比較します。比較には、サンプルで用意した時計の秒針をイメージした60枚のPNG画像を使います。それぞれオプションなしで比較した結果なので、解像度などのオプションを付けると結果が変わる可能性があります。
処理速度の比較
処理速度の比較は、方法の3. GIF動画を作り保存(pillow、imageio、imagemagickのconvert)の箇所で、それぞれのモジュールをimport後からします。
計測には%%timeitを使い、セルの中身を10回実行して計測します。
%%timeit -r10
images = [PIL.Image.open(filename) for filename in filenames] # ファイルを開く
out_filename = 'image_pillow.gif' # GIF動画のファイル名
images[0].save(out_filename,
save_all=True, append_images=images[1:], duration=1000, loop=0)
# 結果 : 1 loop, best of 10: 454 ms per loop
%%timeit -r10
images = [imageio.imread(filename) for filename in filenames] # ファイルを開く
out_filename = 'image_imageio.gif' # GIF動画のファイル名
imageio.mimsave(out_filename, images, duration=1.00, loop=0)
# 結果 : 1 loop, best of 10: 1.38 s per loop
%%timeit -r10
filenames_str = ' '.join(filenames) # 00.png 01.png 02.png…60.pngの文字列にする
out_filename = 'image_imagemagick.gif' # GIF動画のファイル名
!convert -delay 100 -loop 0 $filenames_str $out_filename
# 結果 : 1 loop, best of 10: 9.26 s per loop
```
処理速度は、imagemagick(遅い)< imageio < pillow(速い)結果となりました。
## データサイズの比較
データサイズは、[os.path.getsize()](https://docs.python.org/3/library/os.path.html#os.path.getsize)を使いバイト単位で取得します。
```Python:データサイズの比較
import os
print('pillow', os.path.getsize('image_pillow.gif'))
print('imageio', os.path.getsize('image_imageio.gif'))
print('imagemagick', os.path.getsize('image_imagemagick.gif'))
# 出力結果 : pillow 714429
# 出力結果 : imageio 932089
# 出力結果 : imagemagick 220164
```
データサイズは、imagemagick(小さい)< pillow < imageio(大きい)結果となりました。
# 補足
GIF動画の表示間隔の限界速度がブラウザごとに設けられているらしいです。詳しくは、[あなたは大丈夫?高速GIFアニメになってしまう症状](https://at.sachi-web.com/blog-entry-712.html)を参照してください。
# まとめ
Google ColabでGIF動画を作りインライン表示させる方法を3種類紹介しましたが、比較した結果も見つつ好きなものを使いましょう。