正交投影与numpy

时间:2013-07-24 14:26:30

标签: python arrays numpy

我有一个3D点的列表,我通过numpy.linalg.lstsq - 方法计算了一个平面。但现在我想对每个点进行正交投影到这个平面,但我找不到我的错误:

from numpy.linalg import lstsq

def VecProduct(vek1, vek2):
    return (vek1[0]*vek2[0] + vek1[1]*vek2[1] + vek1[2]*vek2[2])

def CalcPlane(x, y, z):
    # x, y and z are given in lists
    n = len(x)
    sum_x = sum_y = sum_z = sum_xx = sum_yy = sum_xy = sum_xz = sum_yz = 0
    for i in range(n):
        sum_x += x[i] 
        sum_y += y[i]
        sum_z += z[i]
        sum_xx += x[i]*x[i]
        sum_yy += y[i]*y[i]
        sum_xy += x[i]*y[i]
        sum_xz += x[i]*z[i]
        sum_yz += y[i]*z[i]

    M = ([sum_xx, sum_xy, sum_x], [sum_xy, sum_yy, sum_y], [sum_x, sum_y, n])
    b = (sum_xz, sum_yz, sum_z)

    a,b,c = lstsq(M, b)[0]

    '''
    z = a*x + b*y + c
    a*x = z - b*y - c
    x = -(b/a)*y + (1/a)*z - c/a 
    '''

    r0 = [-c/a, 
          0, 
          0]

    u = [-b/a,
         1,
         0]

    v = [1/a,
         0,
         1]

    xn = []
    yn = []
    zn = []

    # orthogonalize u and v with Gram-Schmidt to get u and w

    uu = VecProduct(u, u)
    vu = VecProduct(v, u)
    fak0 = vu/uu
    erg0 = [val*fak0 for val in u]
    w = [v[0]-erg0[0], 
        v[1]-erg0[1], 
        v[2]-erg0[2]]
    ww = VecProduct(w, w)

    # P_new = ((x*u)/(u*u))*u + ((x*w)/(w*w))*w
    for i in range(len(x)):
        xu = VecProduct([x[i], y[i], z[i]], u)
        xw = VecProduct([x[i], y[i], z[i]], w)
        fak1 = xu/uu
        fak2 = xw/ww
        erg1 = [val*fak1 for val in u]
        erg2 = [val*fak2 for val in w]
        erg = [erg1[0]+erg2[0], erg1[1]+erg2[1], erg1[2]+erg2[2]]
        erg[0] += r0[0] 
        xn.append(erg[0])
        yn.append(erg[1])
        zn.append(erg[2])

    return (xn,yn,zn)

这会返回一个全部在平面上的点列表,但是当我显示它们时,它们不在它们应该的位置。 我相信已经有一种内置的方法来解决这个问题,但我找不到任何=(

3 个答案:

答案 0 :(得分:11)

你对np.lstsq的使用很差,因为你正在为它提供一个预先计算好的3x3矩阵,而不是让它完成这项工作。我会这样做:

import numpy as np

def calc_plane(x, y, z):
    a = np.column_stack((x, y, np.ones_like(x)))
    return np.linalg.lstsq(a, z)[0]

>>> x = np.random.rand(1000)
>>> y = np.random.rand(1000)
>>> z = 4*x + 5*y + 7 + np.random.rand(1000)*.1
>>> calc_plane(x, y, z)
array([ 3.99795126,  5.00233364,  7.05007326])

使用不依赖于z的系数的平面公式实际上更方便,即使用a*x + b*y + c*z = 1。您可以类似地计算abc

def calc_plane_bis(x, y, z):
    a = np.column_stack((x, y, z))
    return np.linalg.lstsq(a, np.ones_like(x))[0]
>>> calc_plane_bis(x, y, z)
array([-0.56732299, -0.70949543,  0.14185393])

要使用我的替代方程将点投影到平面上,向量(a, b, c)垂直于平面。很容易检查点(a, b, c) / (a**2+b**2+c**2)是否在平面上,因此可以通过参考平面上该点的所有点,将点投影到法线向量上,从点中减去该投影来完成投影,然后将它们引回原点。你可以这样做:

def project_points(x, y, z, a, b, c):
    """
    Projects the points with coordinates x, y, z onto the plane
    defined by a*x + b*y + c*z = 1
    """
    vector_norm = a*a + b*b + c*c
    normal_vector = np.array([a, b, c]) / np.sqrt(vector_norm)
    point_in_plane = np.array([a, b, c]) / vector_norm

    points = np.column_stack((x, y, z))
    points_from_point_in_plane = points - point_in_plane
    proj_onto_normal_vector = np.dot(points_from_point_in_plane,
                                     normal_vector)
    proj_onto_plane = (points_from_point_in_plane -
                       proj_onto_normal_vector[:, None]*normal_vector)

    return point_in_plane + proj_onto_plane

所以现在你可以这样做:

>>> project_points(x, y, z, *calc_plane_bis(x, y, z))
array([[  0.13138012,   0.76009389,  11.37555123],
       [  0.71096929,   0.68711773,  13.32843506],
       [  0.14889398,   0.74404116,  11.36534936],
       ..., 
       [  0.85975642,   0.4827624 ,  12.90197969],
       [  0.48364383,   0.2963717 ,  10.46636903],
       [  0.81596472,   0.45273681,  12.57679188]])

答案 1 :(得分:2)

你可以简单地完成矩阵中的所有操作。

如果您将点作为行向量添加到矩阵X,而y是向量,则最小二乘解的参数向量beta为:

import numpy as np

beta = np.linalg.inv(X.T.dot(X)).dot(X.T.dot(y))

但是如果我们想做投影,有一种更简单的方法:QR分解为我们提供了一个正交投影矩阵,Q.TQ本身就是标准正交基矢量的矩阵。因此,我们可以先形成QR,然后获取beta,然后使用Q.T投射点数。

QR:

Q, R = np.linalg.qr(X)

测试:

# use R to solve for beta
# R is upper triangular, so can use triangular solver:
beta = scipy.solve_triangular(R, Q.T.dot(y))

现在我们有了beta,我们可以非常简单地使用Q.T投射点数:

X_proj = Q.T.dot(X)

多数民众赞成!

如果你想要更多的信息和图形图片和内容,我做了一大堆笔记,同时做了类似的事情,在https://github.com/hughperkins/selfstudy-IBP/blob/9dedfbb93f4320ac1bfef60db089ae0dba5e79f6/test_bases.ipynb

(编辑:请注意,如果你想添加一个偏见词,那么最合适的不必通过原点,你可以简单地将一个额外的列,包括全1,添加到X,作为偏见词/特征)

答案 2 :(得分:0)

此网络page具有非常好的代码库。它很好地实现了Maple在# import numpy to perform operations on vector import numpy as np # vector u u = np.array([2, 5, 8]) # vector n: n is orthogonal vector to Plane P n = np.array([1, 1, 7]) # Task: Project vector u on Plane P # finding norm of the vector n n_norm = np.sqrt(sum(n**2)) # Apply the formula as mentioned above # for projecting a vector onto the orthogonal vector n # find dot product using np.dot() proj_of_u_on_n = (np.dot(u, n)/n_norm**2)*n # subtract proj_of_u_on_n from u: # this is the projection of u on Plane P print("Projection of Vector u on Plane P is: ", u - proj_of_u_on_n) 中阐述的theory,如下所示:

SELECT Vendor_Number, Vendor_Name FROM Vendors;