【问题标题】:Plot a plane based on a normal vector and a point in Matlab or matplotlib基于法线向量和 Matlab 或 matplotlib 中的点绘制平面
【发布时间】:2011-03-28 13:45:40
【问题描述】:

如何在 matlab 或 matplotlib 中从法线向量和点绘制平面?

【问题讨论】:

    标签: matlab matplotlib plot scipy


    【解决方案1】:

    对于 Matlab:

    point = [1,2,3];
    normal = [1,1,2];
    
    %# a plane is a*x+b*y+c*z+d=0
    %# [a,b,c] is the normal. Thus, we have to calculate
    %# d and we're set
    d = -point*normal'; %'# dot product for less typing
    
    %# create x,y
    [xx,yy]=ndgrid(1:10,1:10);
    
    %# calculate corresponding z
    z = (-normal(1)*xx - normal(2)*yy - d)/normal(3);
    
    %# plot the surface
    figure
    surf(xx,yy,z)
    

    注意:此解决方案仅在 normal(3) 不为 0 时有效。如果平面平行于 z 轴,您可以旋转尺寸以保持相同的方法:

    z = (-normal(3)*xx - normal(1)*yy - d)/normal(2); %% assuming normal(3)==0 and normal(2)~=0
    
    %% plot the surface
    figure
    surf(xx,yy,z)
    
    %% label the axis to avoid confusion
    xlabel('z')
    ylabel('x')
    zlabel('y')
    

    【讨论】:

    • 哦,哇,我从来不知道有一个 ndgrid 函数。在这里,我一直在使用 repmat 和索引来快速创建它们,哈哈。谢谢! 编辑: 顺便说一句,z = -normal(1)*xx - normal(2)*yy - d;代替?
    • 也除以 normal(3) ;)。以防其他人看到这个问题感到困惑
    • @Xyhsh:好吧,看起来你不是唯一一个大脑不工作的人:)
    • 如果normal[3] == 0?
    • @Jonas:一点也不真实。 surf 没有做出这些假设 - 当您将 ndgrid 传递为 xy 时,您做了这些假设。没有什么可以阻止你传递其他东西。
    【解决方案2】:

    对于所有的复制/粘贴,这里是 Python 使用 matplotlib 的类似代码:

    import numpy as np
    import matplotlib.pyplot as plt
    from mpl_toolkits.mplot3d import Axes3D
    
    point  = np.array([1, 2, 3])
    normal = np.array([1, 1, 2])
    
    # a plane is a*x+b*y+c*z+d=0
    # [a,b,c] is the normal. Thus, we have to calculate
    # d and we're set
    d = -point.dot(normal)
    
    # create x,y
    xx, yy = np.meshgrid(range(10), range(10))
    
    # calculate corresponding z
    z = (-normal[0] * xx - normal[1] * yy - d) * 1. /normal[2]
    
    # plot the surface
    plt3d = plt.figure().gca(projection='3d')
    plt3d.plot_surface(xx, yy, z)
    plt.show()
    

    【讨论】:

    • 请注意,z 在原始 sn-p 中属于 int 类型,它会创建一个摆动的表面。我会使用z = (-normal[0]*xx - normal[1]*yy - d) * 1. /normal[2] 将z 转换为real
    • 非常感谢 Falcon,在您发表评论之前,我实际上认为这是 matplotlib 的限制。我试图通过使用 100 个元素 -> range(100) 进行网格化来进行补偿,而 Matlab 示例仅使用了 10 -> 1:10。我适当地编辑了我的解决方案。
    • 如果想要使输出与@Jonas matlab 示例更具可比性,请执行以下操作:a) 将range(10) 替换为np.arange(1,11)。 b) 在plt.show() 之前添加plt3d.azim=-135.0 行(因为Matlab 和matplotlib 似乎有不同的默认旋转)。 c) 吹毛求疵:xlim([0,10])ylim([0, 10])。最后,添加轴标签将有助于首先看到主要区别,因此为了清楚起见,我将添加 xlabel('x')ylabel('y'),并相应地添加到 Matlab 示例中。
    • 如果您的数据已经浮动,则无需执行*1.。不要使用range(10),而是使用np.arange(10.0)np.linspace(-5.0, 5.0, 11)
    • *1。来自编写此答案的 Python 2 天。随意编辑和升级这个答案进入新时代
    【解决方案3】:

    对于想要在表面上渐变的复制粘贴:

    from mpl_toolkits.mplot3d import Axes3D
    from matplotlib import cm
    import numpy as np
    import matplotlib.pyplot as plt
    
    point = np.array([1, 2, 3])
    normal = np.array([1, 1, 2])
    
    # a plane is a*x+b*y+c*z+d=0
    # [a,b,c] is the normal. Thus, we have to calculate
    # d and we're set
    d = -point.dot(normal)
    
    # create x,y
    xx, yy = np.meshgrid(range(10), range(10))
    
    # calculate corresponding z
    z = (-normal[0] * xx - normal[1] * yy - d) * 1. / normal[2]
    
    # plot the surface
    plt3d = plt.figure().gca(projection='3d')
    
    Gx, Gy = np.gradient(xx * yy)  # gradients with respect to x and y
    G = (Gx ** 2 + Gy ** 2) ** .5  # gradient magnitude
    N = G / G.max()  # normalize 0..1
    
    plt3d.plot_surface(xx, yy, z, rstride=1, cstride=1,
                       facecolors=cm.jet(N),
                       linewidth=0, antialiased=False, shade=False
    )
    plt.show()
    

    【讨论】:

      【解决方案4】:

      以上答案已经足够好了。值得一提的是,他们使用相同的方法来计算给定 (x,y) 的 z 值。缺点是他们对平面进行网格网格化,并且空间中的平面可能会有所不同(仅保持其投影相同)。例如,您无法在 3D 空间中得到一个正方形(而是一个扭曲的)。

      为避免这种情况,使用旋转有另一种方法。如果您首先在 x-y 平面中生成数据(可以是任何形状),然后将其旋转等量([0 0 1] 到您的向量),那么您将得到您想要的。只需运行以下代码供您参考。

      point = [1,2,3];
      normal = [1,2,2];
      t=(0:10:360)';
      circle0=[cosd(t) sind(t) zeros(length(t),1)];
      r=vrrotvec2mat(vrrotvec([0 0 1],normal));
      circle=circle0*r'+repmat(point,length(circle0),1);
      patch(circle(:,1),circle(:,2),circle(:,3),.5);
      axis square; grid on;
      %add line
      line=[point;point+normr(normal)]
      hold on;plot3(line(:,1),line(:,2),line(:,3),'LineWidth',5)
      

      它得到一个 3D 圆:

      【讨论】:

        【解决方案5】:

        一个更简洁的 Python 示例,也适用于棘手的 $z,y,z$ 情况,

        from mpl_toolkits.mplot3d import axes3d
        from matplotlib.patches import Circle, PathPatch
        import matplotlib.pyplot as plt
        from matplotlib.transforms import Affine2D
        from mpl_toolkits.mplot3d import art3d
        import numpy as np
        
        def plot_vector(fig, orig, v, color='blue'):
           ax = fig.gca(projection='3d')
           orig = np.array(orig); v=np.array(v)
           ax.quiver(orig[0], orig[1], orig[2], v[0], v[1], v[2],color=color)
           ax.set_xlim(0,10);ax.set_ylim(0,10);ax.set_zlim(0,10)
           ax = fig.gca(projection='3d')  
           return fig
        
        def rotation_matrix(d):
            sin_angle = np.linalg.norm(d)
            if sin_angle == 0:return np.identity(3)
            d /= sin_angle
            eye = np.eye(3)
            ddt = np.outer(d, d)
            skew = np.array([[    0,  d[2],  -d[1]],
                          [-d[2],     0,  d[0]],
                          [d[1], -d[0],    0]], dtype=np.float64)
        
            M = ddt + np.sqrt(1 - sin_angle**2) * (eye - ddt) + sin_angle * skew
            return M
        
        def pathpatch_2d_to_3d(pathpatch, z, normal):
            if type(normal) is str: #Translate strings to normal vectors
                index = "xyz".index(normal)
                normal = np.roll((1.0,0,0), index)
        
            normal /= np.linalg.norm(normal) #Make sure the vector is normalised
            path = pathpatch.get_path() #Get the path and the associated transform
            trans = pathpatch.get_patch_transform()
        
            path = trans.transform_path(path) #Apply the transform
        
            pathpatch.__class__ = art3d.PathPatch3D #Change the class
            pathpatch._code3d = path.codes #Copy the codes
            pathpatch._facecolor3d = pathpatch.get_facecolor #Get the face color    
        
            verts = path.vertices #Get the vertices in 2D
        
            d = np.cross(normal, (0, 0, 1)) #Obtain the rotation vector    
            M = rotation_matrix(d) #Get the rotation matrix
        
            pathpatch._segment3d = np.array([np.dot(M, (x, y, 0)) + (0, 0, z) for x, y in verts])
        
        def pathpatch_translate(pathpatch, delta):
            pathpatch._segment3d += delta
        
        def plot_plane(ax, point, normal, size=10, color='y'):    
            p = Circle((0, 0), size, facecolor = color, alpha = .2)
            ax.add_patch(p)
            pathpatch_2d_to_3d(p, z=0, normal=normal)
            pathpatch_translate(p, (point[0], point[1], point[2]))
        
        
        o = np.array([5,5,5])
        v = np.array([3,3,3])
        n = [0.5, 0.5, 0.5]
        
        from mpl_toolkits.mplot3d import Axes3D
        fig = plt.figure()
        ax = fig.gca(projection='3d')  
        plot_plane(ax, o, n, size=3)    
        ax.set_xlim(0,10);ax.set_ylim(0,10);ax.set_zlim(0,10)
        plt.show()
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2016-07-03
          • 2013-07-23
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2011-06-23
          相关资源
          最近更新 更多