techno_memo

個人用の技術メモ。python・ROS・AI系のソフトウェア・ツールなどの情報を記載

matplotlibによる3Dグラフ描画

この記事の目的

matplotlibを用いて3Dグラフを描画する方法についてまとめる

matplotlibを用いた3D線グラフ・散布図の描画

matplotlibで3Dグラフを描画する場合は下記のように実装する。

  • (1) 3D描画用モジュールのインポート

matplotlibのインポート時に3次元描画に関するライブラリ(mpl_toolkitsのAxes3D)をインポートする。

import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
  • (2) 3D軸設定

描画する軸の設定時引数projection='3d'を付与することで3次元軸を追加する。

ax = fig.add_subplot(1,1,1,projection='3d')     #subplotの追加 (行/列/描画対象インデックス,3次元軸の追加)
  • (3) plot,scatter関数を3次元データを引数として呼び出す・

描画用関数(線グラフであればplot、散布図であればscatter)を呼び出す時にxyzの各座標を引数として呼び出す。 その際、xyzのデータ数は一致させる必要がある。

# xy平面にsin関数を描画する
xs = np.linspace(-np.pi, np.pi, 100)
ys = np.sin(xs)
ax.plot(xs, ys, zs=0, zdir='z', label='sin (x, y)')

上記を組み合わせて3次元軸にsin・cosを描画するサンプルコードを下記に示す。

import matplotlib.pyplot as plt
import numpy as np
from mpl_toolkits.mplot3d import Axes3D

fig = plt.figure()
ax = fig.add_subplot(1,1,1,projection='3d')     #subplotの追加 (行/列/描画対象インデックス,3次元軸の追加)

# xy平面にsin関数を描画する
xs = np.linspace(-np.pi, np.pi, 100)
ys = np.sin(xs)
ax.plot(xs, ys, zs=0, zdir='z', label='sin (x, y)')

# xz平面にcos関数を描画する
xs = np.linspace(-np.pi, np.pi, 100)
ys= np.zeros([100])
zs = np.cos(xs)
ax.scatter(xs, ys, zs, zdir='z', label='cos (x, y)',color="red")

ax.legend()
ax.set_xlim(-np.pi, np.pi)
ax.set_ylim(-np.pi, np.pi)
ax.set_zlim(-np.pi, np.pi)
ax.set_xlabel('X')
ax.set_ylabel('Y')
ax.set_zlabel('Z')

plt.show()

f:id:sd08419ttic:20211017171516p:plain

3次元グラフの底面に画像を描画する

3次元でxyzを描画する際、グラフの底面に地図などの平面画像を描画したい場合がある。 その場合、画像をsurfaceとして読み込んで描画することで平面画像とグラフを同時に描画することが可能になる。

画像の読み込みは下記のように行う。

from PIL import Image
from pylab import ogrid

img = Image.open('mapImage.png')
testImg = np.zeros([256,256,4])
testImg[:,:,0:3]= np.array(img.resize((256, 256)))/255
testImg[:,:,3]= 0.5
X1, Y1 = ogrid[0:testImg.shape[0], 0:testImg.shape[1]]
X1 = X1 - 128
Y1 = Y1 - 128
ax.plot_surface(X1, Y1, np.atleast_2d(-50.0), rstride=5, cstride=5, facecolors=testImg)

上記例ではPILライブラリで画像を読み込んでから256×256に初期化する。 ここで、画像を0-1のfloatにするため全体を255で除算している。 また、画像の4次元目は透明度なので、0-1で任意の値に指定する。(0が透明、1が不透明) その後、pylabのogrid関数でsurface用のXY座標を取得する。ここで画像の中心を0にするために-128を引いて調整している。 最後にplot_surface関数で画像を描画している。np.atleast_2dの引数はz軸の値として任意に指定する。

サンプルコードと結果を下記に示す。


import matplotlib.pyplot as plt
import numpy as np
from mpl_toolkits.mplot3d import Axes3D
from PIL import Image
from pylab import ogrid

fig = plt.figure()
ax = fig.add_subplot(1,1,1,projection='3d')     #subplotの追加 (行/列/描画対象インデックス,3次元軸の追加)

#画像の読み込み
img = Image.open('mapImage.png')
testImg = np.zeros([256,256,4])
testImg[:,:,0:3]= np.array(img.resize((256, 256)))/255
testImg[:,:,3]= 0.5

# 画像を挿入(0など任意の値にどうぞ)
X1, Y1 = ogrid[0:testImg.shape[0], 0:testImg.shape[1]]
X1 = X1 - 128
Y1 = Y1 - 128
ax.plot_surface(X1, Y1, np.atleast_2d(-50.0), rstride=5, cstride=5, facecolors=testImg)

# xy平面にsin関数を描画する
xs = np.linspace(-np.pi, np.pi, 100)
ys = np.sin(xs)
ax.plot(xs*50, ys*50, zs=0, zdir='z', label='sin (x, y)')

# xz平面にcos関数を描画する
xs = np.linspace(-np.pi, np.pi, 100)
ys= np.zeros([100])
zs = np.cos(xs)
ax.scatter(xs*50, ys, zs*50, zdir='z', label='cos (x, y)',color="red")

ax.legend()
ax.set_xlim(-150, 150)
ax.set_ylim(-150, 150)
ax.set_zlim(-150, 150)
ax.set_xlabel('X')
ax.set_ylabel('Y')
ax.set_zlabel('Z')

plt.show()

f:id:sd08419ttic:20211017171707p:plain

参考サイト

sabopy.com

qiita.com