Toccata in Nowhere.

plt.plot_surface 绘制三维平面 feat. Matlab

2021.07.02

在MATLAB中可以通过使用 surf 函数绘制三维面,在 matplotlib 中则对应 plt.plot_surface 函数。

使用矩阵表示面

想象空间有中一个限大小的面,这个面上的每个点都有自己的三维空间坐标 $(x, y, z)$ 以及一个着色值 $c$。

那么如果需要绘制三维空间中的曲面,则描述这个曲面需要四个二维矩阵,分别表示面中每个点的三维空间坐标与着色值。这里的二维矩阵可以看做是在数组空间中的投影。

MATLAB 绘制面

在MATLAB中,可以通过 surf 函数绘制三维面,直接看例子:

x = 1:100;
x = sort(1./x);
x = ones(50,1) * x;

y = sin((1:100)/pi)/10;
y = ones(50,1) * y;

z = (1:50)' * ones(1,100);

color = (1:50)' * ones(1,100);
color(25:45, 75:95) = color(25:45, 75:95)';

surf(x, y ,z,color,'LineStyle', 'none', 'FaceColor', 'interp')

这里简单对$x,y,z$矩阵给定了值,并给定了变化的着色值$c$(color),在MATLAB中可以比较便捷地在surf 函数中使用 'FaceColor', 'interp' 对面中的着色进行插值,以达到较为平滑的着色过渡效果。

Python 使用 plt.plot_surface 绘制面

在 Python 中绘制,首先需要引入 matplotlib 并声明一个三维 ax

import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
import matplotlib.colors as colors
import matplotlib.cm as cmx

fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')

同样给定 $x, y, z$ 矩阵以及着色矩阵 $color$:

x = np.arange(100)+1
x = np.sort(1/x)
x = np.ones((50,100)) * x

y = np.sin(np.arange(100)+1)/10;
y = np.ones((50,100)) * y

z = [np.arange(50) for ii in range(100)]
z = np.array(z).T

color = z.copy()
color[25:45, 75:95] = color[25:45, 75:95].T

值得注意的是,这里需要将color映射到RGBA色彩空间,参考色条映射方案

colors_map = plt.get_cmap('jet_r')
cNorm = colors.Normalize(vmin=np.min(color), vmax=np.max(color))
scalarMap = cmx.ScalarMappable(norm=cNorm, cmap=colors_map)  
my_col = scalarMap.to_rgba(color)

之后对面进行绘制:

ax.plot_surface(x, y, z, facecolors=my_col, cmap=colors_map)

常用可选参数

antialiased

可以在绘图时添加 antialiased=True 对表面进行平滑。

ax.plot_surface(x, y, z, facecolors=my_col, cmap=colors_map, antialiased=True)

alpha

可以调整绘图面的透明度。

ax.plot_surface(x, y, z, facecolors=my_col, cmap=colors_map, alpha=False)

rstridecstride

定义二维矩阵两个方向的采样间隔,plt.plot_surface 默认采样间隔为 10,即如果给定 $1000\times1000$ 的数据矩阵,实际绘制仅为$100\times100$。因此如果需要高精度绘图,则需要指定rstride=1, cstride=1

ax.plot_surface(x, y, z, facecolors=my_col, cmap=colors_map, rstride=1, cstride=1)

插值方法

相比于MATLAB自带的插值功能,在分辨率较低的情况下,antialiased=True 往往并不能达到预期的效果。因此如果需要插值,应当在绘图前同时对四个矩阵进行插值,可以使用scipy.ndimage.interpolation.zoom 对数据进行插值:

from scipy.ndimage.interpolation import zoom
x = zoom(x,[10,4],order=5)
z = zoom(z,[10,4],order=5)
y = zoom(y,[10,4],order=5)
color = zoom(color,[10,4],order=1)

zoom 函数的第二个参数为插值放大的比例,以上代码两个方向分别插值放大了10倍与4倍。 同时可以通过order 参数对样条插值的阶数进行指定(0~5),默认为3。

Reference

mplot3d tutorial