如何使用Python/Matplotlib设置3D图绘制的“相机位置”?

188
我正在学习如何使用mplot3d来生成漂亮的三维数据图,并且目前为止我感到非常满意。我现在想做的是一个旋转表面的小动画。为此,我需要为3D投影设置相机位置。我猜这一定是可能的,因为当使用matplotlib进行交互时,可以使用鼠标旋转表面。但是我该如何从脚本中实现呢?
我在mpl_toolkits.mplot3d.proj3d中找到了很多变换,但我无法弄清楚如何将其用于我的目的,也没有找到任何关于我所尝试做的事情的示例。

4
对于那些想要在Jupyter笔记本中进行交互式旋转的人,需要注意:您可以使用%matplotlib notebook - YvesgereY
同时,按住鼠标右键拖动可以改变摄像机的距离。 - LoMaPh
对于这种可视化,我建议尝试使用mayavi。 - user2821
5个回答

223
通过“相机位置”,听起来你想要调整用于查看3D图的仰角和方位角。你可以使用ax.view_init来设置这个。我使用下面的脚本首先创建了图形,然后确定了一个良好的仰角elev来查看我的图形。然后,我调整了方位角azim,使其在我的图形周围变化360度,并在每个实例中保存了图像(并记录了保存图形时的方位角)。对于更复杂的相机平移,你可以同时调整仰角和角度以达到所需的效果。
    from mpl_toolkits.mplot3d import Axes3D
    ax = Axes3D(fig)
    ax.scatter(xx,yy,zz, marker='o', s=20, c="goldenrod", alpha=0.6)
    for ii in xrange(0,360,1):
        ax.view_init(elev=10., azim=ii)
        savefig("movie%d.png" % ii)

42
抢前了!顺便提一下,这些可以作为“ax.elev”和“ax.azim”属性使用。你也可以只写“ax.azim = ii”,甚至是“ax.azim +=1”来达到同样的效果。 - Joe Kington
1
抱歉,我赢了,但你说得很对。这只是我的一段代码摘录,for循环里面除了view_init和savefig还有更多的内容。=) - cosmosis
4
谢谢cosmosis和Joe,这正是我想要的。既然我现在知道该寻找什么,我也发现了ax.dist,它与ax.azim和ax.elev一起允许在极坐标下设置相机位置。 - Andreas Bleuler
14
你可以通过ax.dist=15(默认值为10)设置相机和物体点之间的距离。 - Tim
我正在寻找一种获取当前方位角和仰角的方法(类似于ax.get_azim不存在),但是ax.azim却奏效了! - otterb
显示剩余7条评论

22

很方便的一个功能是将摄像机位置应用于新的图。这样,我可以绘制图形,然后通过鼠标移动图形并更改距离来改变图形的位置。然后,我想在另一个图中复制相同的视角和距离。我发现axx.ax.get_axes()可以获取具有旧的.azim和.elev属性的对象。

使用Python...

axx=ax1.get_axes()
azm=axx.azim
ele=axx.elev
dst=axx.dist       # ALWAYS GIVES 10
#dst=ax1.axes.dist # ALWAYS GIVES 10
#dst=ax1.dist      # ALWAYS GIVES 10

稍后将展示 3D 图表...

ax2.view_init(elev=ele, azim=azm) #Works!
ax2.dist=dst                       # works but always 10 from axx

编辑1...

好的,相机位置是关于.dist值错误的想法。它作为一种hackey标量倍增器,顶在整个图表的顶部。

这适用于视图的放大/缩小:

xlm=ax1.get_xlim3d() #These are two tupples
ylm=ax1.get_ylim3d() #we use them in the next
zlm=ax1.get_zlim3d() #graph to reproduce the magnification from mousing
axx=ax1.get_axes()
azm=axx.azim
ele=axx.elev

后来的图表显示,该公司的销售额在过去一年中呈现稳步增长。

ax2.view_init(elev=ele, azim=azm) #Reproduce view
ax2.set_xlim3d(xlm[0],xlm[1])     #Reproduce magnification
ax2.set_ylim3d(ylm[0],ylm[1])     #...
ax2.set_zlim3d(zlm[0],zlm[1])     #...

2
+1 对于指出粗糙的标量乘法表示赞同。如果你希望得到透视,这非常令人恼火。 - user5920660

13

通过改变 azimdistelev 来实现简单的示例

为了对之前在https://dev59.com/FWcs5IYBdhLWcg3wXi5E#12905458解释的内容添加一些简单的示例图像。

以下是我的测试程序:

#!/usr/bin/env python3

import sys

import matplotlib.pyplot as plt
from matplotlib import cm
from matplotlib.ticker import LinearLocator, FormatStrFormatter
import numpy as np

fig = plt.figure()
ax = fig.gca(projection='3d')

if len(sys.argv) > 1:
    azim = int(sys.argv[1])
else:
    azim = None
if len(sys.argv) > 2:
    dist = int(sys.argv[2])
else:
    dist = None
if len(sys.argv) > 3:
    elev = int(sys.argv[3])
else:
    elev = None

# Make data.
X = np.arange(-5, 6, 1)
Y = np.arange(-5, 6, 1)
X, Y = np.meshgrid(X, Y)
Z = X**2

# Plot the surface.
surf = ax.plot_surface(X, Y, Z, linewidth=0, antialiased=False)

# Labels.
ax.set_xlabel('x')
ax.set_ylabel('y')
ax.set_zlabel('z')

if azim is not None:
    ax.azim = azim
if dist is not None:
    ax.dist = dist
if elev is not None:
    ax.elev = elev

print('ax.azim = {}'.format(ax.azim))
print('ax.dist = {}'.format(ax.dist))
print('ax.elev = {}'.format(ax.elev))

plt.savefig(
    'main_{}_{}_{}.png'.format(ax.azim, ax.dist, ax.elev),
    format='png',
    bbox_inches='tight'
)

不带参数运行将给出默认值:

ax.azim = -60
ax.dist = 10
ax.elev = 30

main_-60_10_30.png

图片描述

变化的参数:azim

方位角是绕z轴旋转,例如:

  • 0表示“从+x看”
  • 90表示“从+y看”

main_-60_10_30.png

图片描述

main_0_10_30.png

图片描述

main_60_10_30.png

图片描述

变化的参数:dist

dist似乎是数据坐标中可见点到中心点的距离。

main_-60_10_30.png

图片描述

main_-60_5_30.png

图片描述

main_-60_20_-30.png

图片描述

变化的参数:elev

从这里我们可以了解到elev是眼睛与xy平面之间的角度。

main_-60_10_60.png

图片描述

main_-60_10_30.png

图片描述

main_-60_10_0.png

图片描述

main_-60_10_-30.png

图片描述

在matplotlib==3.2.2上进行测试。


3

尝试使用以下代码寻找最佳摄像机位置

使用if语句中提到的键盘按键移动绘图的视角

使用print获取摄像机位置

def move_view(event):
    ax.autoscale(enable=False, axis='both') 
    koef = 8
    zkoef = (ax.get_zbound()[0] - ax.get_zbound()[1]) / koef
    xkoef = (ax.get_xbound()[0] - ax.get_xbound()[1]) / koef
    ykoef = (ax.get_ybound()[0] - ax.get_ybound()[1]) / koef
    ## Map an motion to keyboard shortcuts
    if event.key == "ctrl+down":
        ax.set_ybound(ax.get_ybound()[0] + xkoef, ax.get_ybound()[1] + xkoef)
    if event.key == "ctrl+up":
        ax.set_ybound(ax.get_ybound()[0] - xkoef, ax.get_ybound()[1] - xkoef)
    if event.key == "ctrl+right":
        ax.set_xbound(ax.get_xbound()[0] + ykoef, ax.get_xbound()[1] + ykoef)
    if event.key == "ctrl+left":
        ax.set_xbound(ax.get_xbound()[0] - ykoef, ax.get_xbound()[1] - ykoef)
    if event.key == "down":
        ax.set_zbound(ax.get_zbound()[0] - zkoef, ax.get_zbound()[1] - zkoef)
    if event.key == "up":
        ax.set_zbound(ax.get_zbound()[0] + zkoef, ax.get_zbound()[1] + zkoef)
    # zoom option
    if event.key == "alt+up":
        ax.set_xbound(ax.get_xbound()[0]*0.90, ax.get_xbound()[1]*0.90)
        ax.set_ybound(ax.get_ybound()[0]*0.90, ax.get_ybound()[1]*0.90)
        ax.set_zbound(ax.get_zbound()[0]*0.90, ax.get_zbound()[1]*0.90)
    if event.key == "alt+down":
        ax.set_xbound(ax.get_xbound()[0]*1.10, ax.get_xbound()[1]*1.10)
        ax.set_ybound(ax.get_ybound()[0]*1.10, ax.get_ybound()[1]*1.10)
        ax.set_zbound(ax.get_zbound()[0]*1.10, ax.get_zbound()[1]*1.10)
    
    # Rotational movement
    elev=ax.elev
    azim=ax.azim
    if event.key == "shift+up":
        elev+=10
    if event.key == "shift+down":
        elev-=10
    if event.key == "shift+right":
        azim+=10
    if event.key == "shift+left":
        azim-=10

    ax.view_init(elev= elev, azim = azim)

    # print which ever variable you want 

    ax.figure.canvas.draw()

fig.canvas.mpl_connect("key_press_event", move_view)

plt.show()


1

问题:如何在matplotlib中设置视图?

对于一个三维图,如何固定视角?

回答:通过设置属性ax.azimax.level

ax.elev = 0
ax.azim = 270  # xz view

ax.elev = 0
ax.azim = 0    # yz view

ax.elev = 0
ax.azim = -90  # xy view

请引用另一个答案(通过链接,而不是“上面”或类似方式),并解释您提供的额外见解。 - Yunnosch

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接