pythonで3Dアニメーション(スティックピクチャー)を描く
わりと汎用性の高いコードを書けたので公開しておく。
普段は世間的に全く需要のなさそうなコードばかり書いているので、「これは珍しく公開する価値があるのでは?」と嬉しくなってしまった。
前提
最新のanacondaをインストール済み。
任意個のマーカーの座標を、時系列に沿って(縦向きに)記録したデータがあると仮定。
(参考までにデータの読み込みから書いてある)
コード
モジュールと事前入力部
import csv #データによっては必要かも import numpy as np import matplotlib.pyplot as plt import matplotlib.animation as animation from mpl_toolkits.mplot3d import Axes3D ##事前入力部分 filename = ' '#読みたいファイル名.拡張子 を入力 fname = ' '#出力する動画名(fname+'stickpic.mp4'として出力される) t_start = #データ切り出しの頭 t_end = +1#データ切り出しのおしり+1 t_range =t_end-t_start num = #マーカーの数 マーカーの番号は控えておく! fps = #フレームレート sticks = np.array([[始点のマーカー番号1,終点のマーカー番号1],[始点のマーカー番号2,終点のマーカ番号2]…]) xmax = ;xmin = ymax = ;ymin = zmax = ;zmin = #マーカーのx~z座標が取りうる最大値、最小値
ファイルの読み込みから切り出し
###ファイルの読みこみ~切り出しまで ##読み込み file = np.genfromtxt(filename, delimiter="\t", skip_header=6, usecols=(range(0,152)), dtype='float', encoding="shift-jis") #header: はじめの任意の行数をとばせる #usecols: 読み込む列数を指定(データに抜けがある時とかに便利?) #delimiter: .csvなら','の方が良いかも ##データ格納先準備 f = np.zeros(file.shape[0]*num*3).reshape(file.shape[0], num, 3) #f[frame][marker][xyz] #file.shape[0] = frame数抽出 ##データ格納 for i in range(file.shape[0]): for j in range(num): f[i][j][0] = file[i][j*3+2+0] f[i][j][1] = file[i][j*3+2+1] f[i][j][2] = file[i][j*3+2+2] ##時間でデータを切り出す f = f[t_start:t_end, :, :]
アニメーション描画
###アニーメーション描画 figure = plt.figure(figsize=(9,9))#縦横比は揃える ax = figure.add_subplot(111,projection='3d') ##目盛り幅を揃える max_range = np.array([xmax-xmin, ymax-ymin, zmax-zmin]).max() * 0.5 mid_x = (xmax+xmin) * 0.5 mid_y = (ymax+ymin) * 0.5 mid_z = (zmax+zmin) * 0.5 ax.set_xlim(mid_x - max_range, mid_x + max_range) ax.set_ylim(mid_y - max_range, mid_y + max_range) ax.set_zlim(mid_z - max_range, mid_z + max_range) ##視点、向きの指定 ax.view_init(elev=90, azim=180) #elev:視点の高さ、仰角。90で真上から見た図になる(degrees) #azim:グラフをz軸方向に対し反時計回りさせる(degrees) ##軸ラベル 無い方が見やすいけど、試行錯誤中はあった方が便利 ax.set_xlabel("x", size = 14, color = "r") ax.set_ylabel("y", size = 14, color = "r") ax.set_zlabel("z", size = 14, color = "r") ##時刻毎プロット imss = [] for i in range(t_range): ims = [] for j in sticks: #スティックのループ ims += ax.plot ([f[i][j[0]-1][0], f[i][j[1]-1][0]],[f[i][j[0]-1][1], f[i][j[1]-1][1]],[f[i][j[0]-1][2], f[i][j[1]-1][2]],lw=0.6,color='K') #lw: 線の太さ。デフォルトは1 color: 色。Kは黒 imss.append(ims) ani = animation.ArtistAnimation(figure,imss,interval = 50) #interval: 画像が切り替わる間隔 plt.show() ani.save(fname+'stickpic.mp4')
まとめたコード(注記削除済み)
import csv import numpy as np import matplotlib.pyplot as plt import matplotlib.animation as animation from mpl_toolkits.mplot3d import Axes3D ##事前入力部分 filename = ' ' fname = ' ' t_start = t_end = +1 t_range =t_end-t_start num = fps = sticks = np.array([[ , ],[ , ]…]) xmax = ;xmin = ymax = ;ymin = zmax = ;zmin = ###ファイルの読みこみ~切り出しまで ##読み込み file = np.genfromtxt(filename, delimiter="\t", skip_header=6, usecols=(range(0,152)), dtype='float', encoding="shift-jis") ##データ格納先準備 f = np.zeros(file.shape[0]*num*3).reshape(file.shape[0], num, 3) #f[frame][marker][xyz] ##データ格納 for i in range(file.shape[0]): for j in range(num): f[i][j][0] = file[i][j*3+2+0] f[i][j][1] = file[i][j*3+2+1] f[i][j][2] = file[i][j*3+2+2] ##データを切り出す f = f[t_start:t_end, :, :] ###アニーメーション描画 figure = plt.figure(figsize=(9,9)) ax = figure.add_subplot(111,projection='3d') ##目盛り幅を揃える max_range = np.array([xmax-xmin, ymax-ymin, zmax-zmin]).max() * 0.5 mid_x = (xmax+xmin) * 0.5 mid_y = (ymax+ymin) * 0.5 mid_z = (zmax+zmin) * 0.5 ax.set_xlim(mid_x - max_range, mid_x + max_range) ax.set_ylim(mid_y - max_range, mid_y + max_range) ax.set_zlim(mid_z - max_range, mid_z + max_range) ##視点、向きの指定 ax.view_init(elev=90, azim=180) ##軸ラベル ax.set_xlabel("x", size = 14, color = "r") ax.set_ylabel("y", size = 14, color = "r") ax.set_zlabel("z", size = 14, color = "r") ##時刻毎プロット imss = [] for i in range(t_range): ims = [] for j in sticks: ims += ax.plot ([f[i][j[0]-1][0], f[i][j[1]-1][0]],[f[i][j[0]-1][1], f[i][j[1]-1][1]],[f[i][j[0]-1][2], f[i][j[1]-1][2]],lw=0.6,color='K') imss.append(ims) ani = animation.ArtistAnimation(figure,imss,interval = 50) plt.show() ani.save(fname+'stickpic.mp4')
参考文献や引用について
目盛り幅を揃えるところは、以下に大変お世話になりました。
matplotlibで3次元プロットする際に3軸のスケールを揃える - Qiita
こちらもとても勉強になりました。
Python 3次元グラフのテーマカラー,グラフの表示角度を変更 - Qiita
plotの理解にはこちらがわかりやすかったです。
matplotlibにおける2つのグラフ作成スタイル【Python】 | BioTech ラボ・ノート
ここで公開しているコードも、必要な方々に活用していただければ幸いです。
よもやま話
プロットのところ、一応オブジェクト指向で統一してみた。pyplotスタイルと違うことが今回わかったので、今後きちんと理解していきたい(figure内の構造とかがまだまだ)。
あと、割と時間がかかるプログラムなので、時短テクも取り入れていきたい。