将通过plot_trisurf生成的3D表面投影到xy平面,并在2D中绘制轮廓。

3

我有一组点云,格式如下:

x = [1.2, 1.3, .....]
y = [2.1, 1.2, .....]
z = [0.5, 0.8, .....]

我使用plot_trisurf绘制(通过Delauney三角化)表示点云的3D表面。我的问题是:是否有一种快速/体面的方法将plot_trisurf生成的曲面投影到xy平面,并仅将其轮廓作为2D图绘制出来? 例如,假设我点云中所有的点都在球面表面上,那么plot_trisurf将为我绘制一个(不是非常完美的)球体。然后我的目标是将这个球体“投影”到xy平面,然后将其轮廓作为2D图绘制出来(它是一个圆)。 请注意,这个2D图是一个2D曲线(可能是闭合曲线)。
2个回答

4
您可以使用 trimesh 或类似的模块来快速实现您的目标,而不需要重复造轮子,因为这些库已经实现了处理网格的方法。
下面是将曲面投射到由其法向量和原点定义的任意平面的快速实现。
import numpy as np
from mpl_toolkits.mplot3d import Axes3D
import matplotlib.pyplot as plt
import trimesh

mesh = trimesh.load('./teapot.obj')
triangles = mesh.faces
nodes = mesh.vertices

x = nodes[:,0]
y = nodes[:,2]
z = nodes[:,1]

# Section mesh by an arbitrary plane defined by its normal vector and origin
section = mesh.section([1,0,1], [0,0,0])

fig = plt.figure()    
ax = fig.gca(projection='3d')
ax.plot_trisurf(x, y, z, triangles=triangles, color=(0,1,0,0.3), alpha=0.3)
ax.scatter(section.vertices[:,0], section.vertices[:,2], section.vertices[:,1])

plt.axis([-3, 3, -3, 3])
plt.show()

结果如下: enter image description here 希望对你有所帮助!

1
这非常优雅,我以前从未听说过这个库。 - William Miller

3

对于任意的表面,投影是很简单的,只需将所有的z值设置为0即可。

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

fig = plt.figure()
ax1 = fig.add_subplot(121, projection='3d')
ax2 = fig.add_subplot(122, projection='3d')

n=100
x = np.linspace(0, np.pi*4, n)
y = np.sin(x)+np.cos(x)
z = y*y

ax1.plot_trisurf(x, y, z)
ax1.set_title(r"$z=y^2$")
ax2.plot_trisurf(x, y, np.zeros_like(x))
ax2.set_title(r"$z=0$")
plt.show()

enter image description here

对于已知的常规曲面,如球体,可以通过与给定方向相对应的最大横截面来简单地处理。即对于以原点为中心的圆,仅使用xy对,使得z==0abs(z) < threshold,或使垂直于yx平面的竖直线上的z最小。如果球体已经被“压扁”,则该方法不适用。例如,使用后一种方法(但使用plot_surface,因为我已经有了它的代码,并使用上述相同的导言语句),

n = 100
r = 5
theta = np.linspace(0, np.pi*2, n)
phi = np.linspace(0, np.pi, n)
x = r * np.outer(np.cos(theta), np.sin(phi))
y = r * np.outer(np.sin(theta), np.sin(phi))
z = r * np.outer(np.ones_like(theta), np.cos(phi))

x_out = list()
y_out = list()
for t in theta:
    zm = r
    idx = 0
    for ii in range(len(phi)):
        if abs(r * np.cos(phi[ii])) < zm:
            zm = r * np.cos(phi[ii])
            idx = ii
    x_out.append(r * np.cos(t) * np.sin(phi[idx]))
    y_out.append(r * np.sin(t) * np.sin(phi[idx]))

ax1.plot_surface(x, y, z)
ax1.set_title("Sphere")
ax2.plot(x_out, y_out, np.zeros_like(x_out), linestyle='-')
ax2.set_title("Maximum Cross Section Outline")
plt.show()

在这里输入图片描述

有些不规则表面也可以使用此方法,但如果点的极坐标分布不均匀,则可能需要插值。更可靠且计算密集的方法是使用cascaded_union创建shapely。要推广此方法,必须进行一些过滤以删除shapely认为无效的多边形,即具有自交的多边形。您可以使用以下方式完成此操作:

import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
import numpy as np
from matplotlib import rcParams
from math import cos, sin
from shapely.ops import cascaded_union
from shapely import geometry
from matplotlib import patches

n=100
t = np.linspace(0, np.pi*2, n)
r = np.linspace(0, 1.0, n)

x = r * np.cos(t)
y = r * np.sin(t)

z = np.sin(-x*y)
fig = plt.figure()
ax1 = fig.add_subplot(121, projection='3d')
ax2 = fig.add_subplot(122, projection='3d')

polygons = list()
# Create a set of valid polygons spanning every combination of
# four xy pairs
for k in range(1, len(x)):
    for j in range(1, len(x)):
        try:
            polygons.append(geometry.Polygon([(x[k], y[k]), (x[k-1], y[k-1]), 
                                              (x[j], y[j]), (x[j-1], y[j-1])]))
        except (ValueError, Exception):
            pass

# Check for self intersection while building up the cascaded union
union = geometry.Polygon([])
for polygon in polygons:
    try:
        union = cascaded_union([polygon, union])
    except ValueError:
        pass

xp, yp = union.exterior.xy

ax1.plot_trisurf(x, y, z)
ax1.set_title(r"$z=sin(-x*y)$")
ax2.plot_trisurf(x, y, np.zeros_like(x))
ax2.set_title(r"$z=0$")
plt.show()   # Show surface and projection

fig, ax = plt.subplots(1, figsize=(8, 6))
ax.add_patch(patches.Polygon(np.stack([xp, yp], 1), alpha=0.6))
ax.plot(xp, yp, '-', linewidth=1.5)
plt.show()   # Show outline 

enter image description here

enter image description here


谢谢。这真的很有帮助。一个快速的问题:是否可以以2D格式绘制轮廓线,而不是3D? - CodingNow
1
@CodingNow 是的,在上面的例子中,如果投影“ax2”没有设置为3D,并且您使用“ax2.plot(x_out,y_out)”,则会得到一个2D子图。 - William Miller
感谢您的快速回复。我更感兴趣的是第一种方法,因为我的表面是不规则的(我的点的极坐标分布不均匀)。在第一种方法中是否可以绘制2D而不是3D?我尝试过ax2.plot(x, y),但只得到了一个正弦波。 - CodingNow
1
@CodingNow,恐怕需要更复杂的方法...我会在明天发布一个包含该方法的编辑。 - William Miller
1
@CodingNow 发现这比我预想的更加复杂,仍在努力中..... - William Miller

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