概要
複数の画像ファイルとmatplotlibで描いたグラフを組み合わせて、以下のようなgifアニメーションを作る方法を整理しました。
matplotlibでアニメーションを作る
まずは、matplotlibで描いたグラフを繋ぎ合わせて、単純なアニメーションを作ってみます。
何はともあれまず作る
とりあえず作ってみるだけなら以下で作ることができます。
import matplotlib.pyplot as plt import matplotlib.animation as animation if __name__ == "__main__": #figオブジェクトを作る fig = plt.figure() #空のリストを作る ims = [] #10個の画像を繋げたアニメーションにする for i in range(10): #1枚1枚のグラフを描き、appendしていく im = plt.plot([0, i], [0, i]) ims.append(im) #アニメーション作成 ani = animation.ArtistAnimation(fig, ims, interval=200, repeat_delay=1000)
Spyder等を使っている人は、画像出力をinlineではなくautoにしておきましょう。上記を実行すると、以下のようなアニメーションが表示されるはずです。
なんだ簡単じゃん。と思ったら大間違いで、保存が手間なのです。
アニメーションの保存方法
保存コマンドは以下ですが、実行するとエラーが出るはずです。
ani.save("test.gif")
ValueError: Cannot save animation: no writers are available. Please install ffmpeg to save animations.
エラーの内容はwriterがない、ということですので、内容通りffmepgをダウンロードしましょう。
インストールは特に不要で、C直下等に解凍したフォルダを置き、合わせて実行ファイルのパス(例:C:\\ffmpeg\bin)を環境変数に登録すればOKです。
しかしながら、導入後、再び保存を実行すると、また同じエラーが出てしまうはずです。なんでだ!とイライラする気持ちを抑えて調べ進めると、image magickなるソフトがさらに必要であることが分かるので、imagemagickのサイトからダウンロード・インストールしましょう。また、インストールした後、matplotlibのmatplotlibricファイルに以下の内容(実際にmagick.exeがあるパス名を使ってください)を忘れずに追加してください。
animation.convert_path: C:\Program Files\ImageMagick-xxx\magick.exe
上記ははしくれエンジニアもどきのメモ様のサイトを参考にさせていただきました。
導入後、再び保存を実行すると、なにやらwarningが出ますがgifファイルが出来ていて、開くとちゃんとアニメーションが表示できることを確認できるはずです。
ani.save("test.gif")
読み込んだ画像ファイルからアニメーションを作る
次に、画像ファイルを繋ぎ合わせてアニメーションを作ってみます。例題としてフェデラーのフォアハンドの連続写真を使ってみます。カッコよかったのでこのサイト様の画像を失敬しています。
何はともあれまず作る
先ほどと同様のコードの一部を置き換え、作ってみましょう。画像ファイルの読み込みはPILのImageを、読み込んだImageの書き込みはplt.imshowで実施します。
import glob from PIL import Image import matplotlib.pyplot as plt import matplotlib.animation as animation if __name__ == "__main__": folderName = "hogehoge" #画像ファイルの一覧を取得 picList = glob.glob(folderName + "\*.png") #figオブジェクトを作る fig = plt.figure() #空のリストを作る ims = [] #画像ファイルを順々に読み込んでいく for i in range(len(picList)): #1枚1枚のグラフを描き、appendしていく tmp = Image.open(picList[i]) ims.append(plt.imshow(tmp)) #アニメーション作成 ani = animation.ArtistAnimation(fig, ims, interval=200, repeat_delay=1000) ani.save("test.gif")
が、これは以下のようなエラーが出ます。
TypeError: ‘AxesImage’ object is not iterable
どうやら、plt.imshow(tmp)をそのまま入れていたのがまずかったようで、該当部分をims.append([plt.imshow(tmp)])と、リストで渡すようにしたら解決しました。出来上がったのがこちら。
ちょっと不満はありますが、フェデラー先生バッチリと動いてますね。
軸を消す
上記は軸がうっとおしいので削除します。軸を消すためには例えば以下のコードを追加します。
ax = plt.subplot(1, 1, 1) ax.spines['right'].set_color('None') ax.spines['top'].set_color('None') ax.spines['left'].set_color('None') ax.spines['bottom'].set_color('None') ax.tick_params(axis='x', which='both', top='off', bottom='off', labelbottom='off') ax.tick_params(axis='y', which='both', left='off', right='off', labelleft='off')
エイリアシングに対処する
また、目のいい人なら気付くかもしれませんが、画像の拡大・縮小をするとエイリアシング(ギザギザ的なノイズ)が発生します。これらは直線等の単純な図形ほど顕著です。対応するため、線形補完を実施しましょう。
plt.imshow(tmp, interpolation="spline36")
線形補完の種類と効果に関しては、stackoverflowに分かりやすく書かれていますので参考にしてみてください。
最終的なコードと結果は以下になります。これで画像ファイルから描く方法もOKですね。
ソースコード全文。
import glob from PIL import Image import numpy as np import matplotlib.pyplot as plt import matplotlib.animation as animation if __name__ == "__main__": folderName = "hoge" #画像ファイルの一覧を取得 picList = glob.glob(folderName + "\*.png") #figオブジェクトを作る fig = plt.figure() #軸を消す ax = plt.subplot(1, 1, 1) ax.spines['right'].set_color('None') ax.spines['top'].set_color('None') ax.spines['left'].set_color('None') ax.spines['bottom'].set_color('None') ax.tick_params(axis='x', which='both', top='off', bottom='off', labelbottom='off') ax.tick_params(axis='y', which='both', left='off', right='off', labelleft='off') #空のリストを作る ims = [] #画像ファイルを順々に読み込んでいく for i in range(len(picList)): #1枚1枚のグラフを描き、appendしていく tmp = Image.open(picList[i]) #エイリアシングを防ぐため、線形補完 ims.append([plt.imshow(tmp, interpolation="spline36")]) #アニメーション作成 ani = animation.ArtistAnimation(fig, ims, interval=200, repeat_delay=1000) ani.save("test.gif")
複数の画像を組み合わせてアニメーションを作る
最後に、subplotを作った複数の画像からアニメーションを作ってみます。subplotを使ったグラフを描く場合、subplotを切り替えながらひとつのオブジェクト(im)の上に2つのグラフを描画し、それらをまとめてims追加するという手順で実施します。
ax = plt.subplot(1, 2, 2) #1枚1枚のグラフを描き、appendしていく im=plt.plot([0, i], [0, i]) ax = plt.subplot(1, 2, 1) #1枚1枚のグラフを描き、appendしていく tmp = Image.open(picList[i]) #エイリアシングを防ぐため、線形補完 im.append(plt.imshow(tmp, interpolation="spline36")) ims.append(im)
ソースコード全文は以下。下記により冒頭のアニメーションが得られます。
import glob from PIL import Image import numpy as np import matplotlib.pyplot as plt import matplotlib.animation as animation if __name__ == "__main__": folderName = "hoge" #画像ファイルの一覧を取得 picList = glob.glob(folderName + "\*.png") #figオブジェクトを作る fig = plt.figure() #軸を消す ax = plt.subplot(1, 2, 1) ax.spines['right'].set_color('None') ax.spines['top'].set_color('None') ax.spines['left'].set_color('None') ax.spines['bottom'].set_color('None') ax.tick_params(axis='x', which='both', top='off', bottom='off', labelbottom='off') ax.tick_params(axis='y', which='both', left='off', right='off', labelleft='off') #空のリストを作る ims = [] #画像ファイルを順々に読み込んでいく for i in range(len(picList)): ax = plt.subplot(1, 2, 2) #1枚1枚のグラフを描き、appendしていく im=plt.plot([0, i], [0, i]) ax = plt.subplot(1, 2, 1) #1枚1枚のグラフを描き、appendしていく tmp = Image.open(picList[i]) #エイリアシングを防ぐため、線形補完 im.append(plt.imshow(tmp, interpolation="spline36")) ims.append(im) #アニメーション作成 ani = animation.ArtistAnimation(fig, ims, interval=200, repeat_delay=1000) ani.save("test.gif")
まとめ
- アニメーションを保存するまで
- -ffmepgをダウンロードし、環境変数追加
- -Image magickをダウンロード・インストールし、matplotlibrcを修正
- 画像ファイルからアニメーション作成
- -imshowをリストで渡す
- -interpolationでエイリアシングに対策
- 複数画像を組み合わせてアニメーション作成
- -subplotを切り替えながらひとつのオブジェクト(im)の上に複数グラフを描画
- -それらをまとめてims追加する
エラー対策
以下のようなエラーが出る場合があります。
OSError: Error saving animation to file (cause: [Errno 22] Invalid argument) Stdout: b'' StdError: b''. It may help to re-run with --verbose-debug.
この場合は、以下のようにwriterがimagemagickであること明記しましょう。
ani.save("hoge.gif", writer="imagemagick")
matplotlibでのgif化調べたらまさかこのブログが出てくるとは!!
参考にさせていただきました。
コメントありがとうございます。当時だいぶ苦戦した記憶があります。。。
ご活用いただければ幸いです!