pythonで散布図アニメーションを試してみた

最近pythonを触り始めたのですが、散布図をアニメーションさせる方法が分からなかったので調べてみました。

散布図はmatplotlib.plt.scatter(x,y)で作成する事が出来ます。
また、アニメーションをさせる方法は二通りのやり方があるようです。

  1. animation.ArtistAnimation
    • 事前に用意してあるデータを描画
  2. animation.FuncAnimation
    • 随時データを更新する

そこで円周上の点を一度ずつ移動させるというアニメーションをArtistAnimationとFuncAnimationの2つの方法で試してみました。 実行結果はどちらも次のようなものになります。

f:id:cflat-inc:20140219165841g:plain

animation.ArtistAnimationの場合


事前にplt.scatterの戻り値をlistに保存しておき、animation.ArtistAnimationの第二引数に渡すと 描画する事が出来ました。

# -*- coding: UTF-8 -*-
import math
import matplotlib.pyplot as plt
import matplotlib.animation as animation

fig = plt.figure()

# 360度分のデータを作成
ims = []
for i in range(360):
    rad = math.radians(i)
    im = plt.scatter(math.cos(rad), math.sin(rad))
    ims.append([im])

# アニメーション作成
ani = animation.ArtistAnimation(fig, ims, interval=1, repeat_delay=1000)

# 表示
plt.show()

animation.FuncAnimationの場合


更新のためのコールバック関数をanimation.FuncAnimationの第二引数に入れます。
また、コールバック関数の引数はfargs=(fig, im)のようにして渡す事が出来ます。

# -*- coding: UTF-8 -*-
import math
import matplotlib.pyplot as plt
import matplotlib.animation as animation

# 更新する内容
def _update_plot(i, fig, im):
    rad = math.radians(i)

    # 前回のフレーム内容を一旦削除
    if len(im) > 0:
        im[0].remove()
        im.pop()

    im.append(plt.scatter(math.cos(rad), math.sin(rad)))

fig =  plt.figure()

# グラフを中央に表示
ax = fig.add_subplot(1,1,1)

# グラフの目盛範囲設定
ax.set_xlim([-1.5, 1.5])
ax.set_ylim([-1.5, 1.5])

im = [] # フレーム更新の際に前回のプロットを削除するために用意

# アニメーション作成
ani = animation.FuncAnimation(fig, _update_plot, fargs = (fig, im), 
        frames = 360, interval = 1) 

# 表示
plt.show()

前フレームで描画された点を消しながらアニメーションさせる方法が分からず悩みましたが、plt.scatterの戻り値をremoveする事で解決出来ました。(本当はもっと良いやり方があるのかもしれません。)

おまけ


せっかくなので、某ゲームのプレイ動画からフレームを切り出して特徴量抽出したものをanimation.ArtistAnimationで表示してみました。

動画からフレームの切り出しはffmpegが便利です。
Macの場合は brew install ffmpeg --with-libvorbis --with-libvpx --with-schroedingerffmpegを導入した後、 ffmpeg -i input.mpeg -f image2 frame%d.jpg とする事でフレームの切り出しが出来ます。
また特徴量抽出にはopencvを使いました。詳しくはopencv, pythonで検索してみてください。

#coding: UTF-8
import cv2
from matplotlib import pyplot as plt
import matplotlib.animation as animation

fig = plt.figure()
ims = []
for i in range(1, 100):
    name = "frame" + str(i) + ".jpg"
    img = cv2.imread(name,0)
    width, height = img.shape

    # 検出器の初期化
    orb = cv2.FastFeatureDetector()

    # 特徴量の検出と出力用の計算
    kp = orb.detect(img, None)
    x = []
    y = []
    for p in kp:
        x.append(p.pt[0])
        y.append(height - p.pt[1]) # opencvの座標系は左上原点なので調整

    im = plt.scatter(x, y)
    ims.append([im])

# 動画保存の準備
Writer = animation.writers['ffmpeg']
writer = Writer(fps=15, metadata=dict(artist='Me'), bitrate=1800)

ani = animation.ArtistAnimation(fig, ims, interval=50, repeat_delay=1000)

#動画として保存
ani.save('im.mp4', writer=writer)

plt.show()

実行結果はこんな感じになります。

f:id:cflat-inc:20140219170651g:plain

たったこれだけの行数でここまで出来るのは嬉しいですね。 さすがpython!