python - 3D 平面拟合方法(C++ 与 Python)

标签 python c++ opencv statistics linear-algebra

给定:

我在 csv 文件中有一组 3D 点。

   X,  Y,  Z
  x0, y0, z0
  x1, y1, z1
     ...
  xn, yn, zn

问题陈述: 目标是根据最小二乘误差拟合平面。获取点与平面之间的误差距离。 我可以自由选择Python或C++。我更喜欢 c++,但 python 也不错。

平面方程为:

                         Ax + By + Cz + D = 0                            -- Equation 1

选项 1:C++ 方式

我在网上找到了这个链接,描述了一种解决平面拟合的c++方法 http://www.janssenprecisionengineering.com/downloads/Fit-plane-through-data-points.pdf 但这里他们没有使用方程 1,而是使用方程 2

                         Z = A'x + B'y + C'                               -- Equation 2

                        where,  A' = -A/C
                                B' = -B/C
                                C' = -D/C

Calculation

一旦我知道了 A' B' C',我就可以根据方程 3 计算每个点到平面的距离(引用 -> Math Insight ):

                   Error = abs(A * x + B * y - z + C) / sqrt(pow(A, 2) + pow(B, 2) + 1);   -- Equation 3

我开始用C++实现它

    std::ofstream abc;
    abc.open("Logs\\abc.csv"); 

    cv::Mat Plane;
    double Xi = 0;
    double Yi = 0;
    double Zi = 0;

    double X2i = 0;
    double Y2i = 0;

    double XiYi = 0;
    double XiZi = 0;
    double YiZi = 0;

    for (int o = 0; o < GL.PointX.size(); ++o) {

        std::cout << "POINT X : " << GL.PointX[o] << std::endl;
        std::cout << "POINT Y : " << GL.PointY[o] << std::endl;
        std::cout << "POINT Z : " << GL.PointZ[o] << std::endl;

        Xi = Xi + GL.PointX[o];
        Yi = Yi + GL.PointY[o];
        Zi = Zi + GL.PointZ[o];

        X2i = X2i + (GL.PointX[o] * GL.PointX[o]);
        Y2i = Y2i + (GL.PointY[o] * GL.PointY[o]);

        XiYi = XiYi + (GL.PointX[o] * GL.PointY[o]);
        XiZi = XiZi + (GL.PointX[o] * GL.PointZ[o]);
        YiZi = YiZi + (GL.PointY[o] * GL.PointZ[o]);

    }

    cv::Mat PlaneA_Mat = (cv::Mat_<double>(3, 3) << X2i, XiYi, Xi, XiYi, Y2i, Yi, Xi, Yi, 1);
    cv::Mat PlaneB_Mat(3, 1, CV_64FC1);
    double R = 0, R2 = 0, FR2=0;
    int Observation = 70;
    PlaneB_Mat.at<double>(0, 0) = XiZi;
    PlaneB_Mat.at<double>(1, 0) = YiZi;
    PlaneB_Mat.at<double>(2, 0) = Zi;

    Plane = PlaneA_Mat.inv() * PlaneB_Mat;



    double A = Plane.at<double>(0, 0); //-A/C
    double B = Plane.at<double>(1, 0); //-B/C
    double C = Plane.at<double>(2, 0); //-D/C


    abc << A << "," << B << "," << "-1" << "," << C;
    abc <<"\n";


    double Dsum = 0;
    for (int o = 0; o < GL.PointX.size(); ++o) {
        double Error = abs(A * GL.PointX[o] + B * GL.PointY[o] - GL.PointZ[o] + C) / sqrt(pow(A, 2) + pow(B, 2) + 1);
        std::cout << "Error : " << Error << std::endl;
        Error_projection << Error ;
        Error_projection << "\n";
        Dsum = Dsum + Error;
        GL.D.push_back(Error);
    }
    R = (Observation * XiYi - Xi * Yi) / sqrt((Observation * X2i - Xi * Xi) * (Observation * Y2i - Yi * Yi));
R2 = pow(R, 2);

FR2 = pow(Zi - Dsum, 2) / pow(Zi - (1 / Observation)*Zi, 2);


std::cout << "PlaneA_Mat : " << PlaneA_Mat << std::endl;
std::cout << "PlaneB_Mat: " << PlaneB_Mat << std::endl;
std::cout << "FINAL PLANE: " << Plane << std::endl;
std::cout << "R: " << R << std::endl;
std::cout << " Correlation coefficient (R^2) : " << R2 << std::endl;
std::cout << " Final Correlation coefficient (FR^2) : " << FR2 << std::endl;

我得到的结果是:

  A : 12.36346708893272
  B : 0.07867292340114898
  C : -3.714791111490779

并且还得到了每个点相对于平面的误差。 错误值非常大

*选项 2:Python 方式*

然后我开始在网络上查找一些代码并发现了这个 --> 3D Plane Fit代码。我根据自己的需要进行了修改,效果非常好。

import numpy as np
import scipy.optimize

from mpl_toolkits.mplot3d import Axes3D
import matplotlib.pyplot as plt
import pandas



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

def fitPlaneLTSQ(XYZ):
    (rows, cols) = XYZ.shape
    print(rows, cols)
    G = np.ones((rows, 3))
    print(XYZ)
    print(XYZ[0])
    G[:, 0] = XYZ[0]  # X
    G[:, 1] = XYZ[1]  # Y
    print(G)
    Z = XYZ[2]
    (a, b, c), resid, rank, s = np.linalg.lstsq(G, Z)
    print("a : ", a )
    print("b : ", b )
    print("c : ", c)
    print("Residual : ", resid)
    print("Rank : ", rank)
    print("Singular Value", s)

    normal = (a, b, -1)                                             # I Don't Know WHY ?
    print("normal : ", normal)

    '''
    normala = abs(pow(a,2)+pow(b,2)+pow(-1,2))
    np.sqrt(normala)
    abb=normal/normala
    print("Normala : ",normala)
    print("Normala : ", abb)

    '''

    nn = np.linalg.norm(normal)                                       # I Don't Know WHY ?
    print("nn : ", nn)
    normal = normal/nn                                                # I Don't Know WHY ?

    print("Normal : ", normal)

    return (c, normal)


#Import Data from CSV
result = pandas.read_csv("C:/Users/Logs/points_L.csv", header=None) # , names=['X', 'Y','Z']
#result =result.head(5)
print(result)

normal1 = pandas.read_csv("C:/Users/Logs/pose_left.csv", header=None) # , names=['X', 'Y','Z']
print(normal1)

abc = pandas.read_csv("C:/Users/Logs/abc.csv", header=None) # , names=['X', 'Y','Z']
print(abc)

#standard normal distribution / Bell.
#np.random.seed(seed=1)

data = result
#print(data)
print("NEW : ")
print(data)

c, normal = fitPlaneLTSQ(data)
print(c, normal)

# plot fitted plane
maxx = np.max(data[0])
maxy = np.max(data[1])
minx = np.min(data[0])
miny = np.min(data[1])
print(maxx,maxy, minx, miny)

point = np.array([0.0, 0.0, c])                                             # I Don't Know WHY ?
print("Point : ", point)
d = -point.dot(normal)                                                      # I Don't Know WHY ?
print("D : ",  d)

# plot original points
ax.scatter(data[0], data[1], data[2])
ax.quiver(data[0], data[1], data[2], normal1[0], normal1[1], normal1[2], length=0.2)
# compute needed points for plane plotting
xx, yy = np.meshgrid([minx, maxx], [miny, maxy])                           


print(xx)
print(yy)

print("minx : ", minx)
print("maxx : ", maxx)
print("miny : ", miny)
print("maxy : ", maxy)


print("xx : ", xx)
print("yy : ", yy)
z = (-normal[0]*xx - normal[1]*yy - d)*1. / normal[2]                   # I Don't Know WHY ?


unit1 = np.sqrt(pow(normal[0], 2) + pow(normal[1],2) + pow(normal[2],2))
print("Unit 1 : ", unit1)
Error = abs(normal[0]*data[0] + normal[1]*data[1] + normal[2]*data[2] + d)/unit1
print("Error", Error)
Error_F = pandas.DataFrame(Error)
print("Print : ", Error_F)
Error_F.to_csv("C:/Users/Logs/Py_Error.csv")


print("Z : ", z)
# plot plane
ax.plot_surface(xx, yy, z, alpha=0.2)
ax.set_xlim(-1, 1)
ax.set_ylim(-1,1)
ax.set_zlim(1,2)
ax.set_xlabel('x')
ax.set_ylabel('y')
ax.set_zlabel('z')
plt.show()

3D Plot

如果您能回答以下问题,将会非常有帮助:

  1. 在选项 2 python 代码中,我评论了# I Dont Know WHY ?您能否提供他们使用它背后的数学推理。请小心地解释它,就像你向菜鸟解释一样。
  2. 我的 C++ 代码有什么问题?我怎样才能让它产生与Python代码相同的结果?
  3. 为什么我的最终相关系数 R^2 提供奇怪的结果?

                                    Thank You Very Much !
    

最佳答案

嗯,这不是一个答案,但对于评论来说太长了。 “点与平面之间的距离”的另一种解释是,对于点 p

n.p + d

其中平面有方程

n.p + d = 0

我们假设

|n| = l

因此,如果我们有 N 个点 p[] 的集合,我们会寻找实数 d 和单位 vector n 来最小化

S = Sum{ i | (n.P[i] + d)*(n.P[i] + d) }

一些代数派生出以下计算方法:

a/通过计算 Pbar(均值点)和 Q(P[] 的“协方差”)

Pbar = Sum{ i | P[i]}/N
Q = Sum{ i | (P[i]-Pbar)*(P[i]-Pbar)' } / N

b/计算 n 和对应于最小特征值的 Q 特征向量。

c/计算

d = -n'*Pbar

关于python - 3D 平面拟合方法(C++ 与 Python),我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/59881691/

相关文章:

python - 在 tkinter 中显示帧内的视频流

java - 在Python中解码java对象

c++ - 将成对 vector 放入 unordered_map 与 map

python - flask 重定向多条路线

c++ - 使用 VB 进行人工智能

c++ - 最低共同祖先( boost 图)

python - 如何根据openCV中的时间戳选择特定的帧?

opencv - 比较扫描图像并检测非常小的可见变化

python - 使用 python 将 xml 数据转储到 csv 文件中的单元格中

python - 将科学计数法转换为人类可读的 float