soa*_*gem 23 algorithm math statistics
在给定一组3D数据点的情况下,在(x,y,z)空间中计算最小二乘平面的算法是什么?换句话说,如果我有一堆像(1,2,3),(4,5,6),(7,8,9)等点,那么如何计算最佳拟合平面f (x,y)= ax + by + c?从一组3D点中获取a,b和c的算法是什么?
Ste*_*non 39
如果您有n个数据点(x [i],y [i],z [i]),请计算3x3对称矩阵A,其条目为:
sum_i x[i]*x[i], sum_i x[i]*y[i], sum_i x[i]
sum_i x[i]*y[i], sum_i y[i]*y[i], sum_i y[i]
sum_i x[i], sum_i y[i], n
Run Code Online (Sandbox Code Playgroud)
还计算3元素向量b:
{sum_i x[i]*z[i], sum_i y[i]*z[i], sum_i z[i]}
Run Code Online (Sandbox Code Playgroud)
然后求解给定A和b的Ax = b.解向量的三个分量是最小二乘拟合平面{a,b,c}的系数.
注意,这是"普通最小二乘"拟合,仅当预期z是x和y的线性函数时才适合.如果你更普遍地寻找3空间中的"最佳飞机",你可能想要了解"几何"最小二乘法.
另请注意,如果您的点在一条线上,则会失败,如您的示例点所示.
平面的方程为:ax + by + c = z。因此,使用您的所有数据设置这样的矩阵:
x_0 y_0 1
A = x_1 y_1 1
...
x_n y_n 1
Run Code Online (Sandbox Code Playgroud)
和
a
x = b
c
Run Code Online (Sandbox Code Playgroud)
和
z_0
B = z_1
...
z_n
Run Code Online (Sandbox Code Playgroud)
换句话说:Ax = B。现在求解 x 是你的系数。但是因为(我假设)你有超过 3 个点,系统是过度确定的,所以你需要使用左伪逆。所以答案是:
a
b = (A^T A)^-1 A^T B
c
Run Code Online (Sandbox Code Playgroud)
这是一些带有示例的简单 Python 代码:
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
import numpy as np
N_POINTS = 10
TARGET_X_SLOPE = 2
TARGET_y_SLOPE = 3
TARGET_OFFSET = 5
EXTENTS = 5
NOISE = 5
# create random data
xs = [np.random.uniform(2*EXTENTS)-EXTENTS for i in range(N_POINTS)]
ys = [np.random.uniform(2*EXTENTS)-EXTENTS for i in range(N_POINTS)]
zs = []
for i in range(N_POINTS):
zs.append(xs[i]*TARGET_X_SLOPE + \
ys[i]*TARGET_y_SLOPE + \
TARGET_OFFSET + np.random.normal(scale=NOISE))
# plot raw data
plt.figure()
ax = plt.subplot(111, projection='3d')
ax.scatter(xs, ys, zs, color='b')
# do fit
tmp_A = []
tmp_b = []
for i in range(len(xs)):
tmp_A.append([xs[i], ys[i], 1])
tmp_b.append(zs[i])
b = np.matrix(tmp_b).T
A = np.matrix(tmp_A)
fit = (A.T * A).I * A.T * b
errors = b - A * fit
residual = np.linalg.norm(errors)
print "solution:"
print "%f x + %f y + %f = z" % (fit[0], fit[1], fit[2])
print "errors:"
print errors
print "residual:"
print residual
# plot plane
xlim = ax.get_xlim()
ylim = ax.get_ylim()
X,Y = np.meshgrid(np.arange(xlim[0], xlim[1]),
np.arange(ylim[0], ylim[1]))
Z = np.zeros(X.shape)
for r in range(X.shape[0]):
for c in range(X.shape[1]):
Z[r,c] = fit[0] * X[r,c] + fit[1] * Y[r,c] + fit[2]
ax.plot_wireframe(X,Y,Z, color='k')
ax.set_xlabel('x')
ax.set_ylabel('y')
ax.set_zlabel('z')
plt.show()
Run Code Online (Sandbox Code Playgroud)
除非有人告诉我如何在这里输入方程式,让我写下你必须做的最终计算:
首先,给定点r_i \n\R,i = 1..N,计算所有点的质心:
r_G = \frac{\sum_{i=1}^N r_i}{N}
Run Code Online (Sandbox Code Playgroud)
然后,计算法向量n,它与基矢量r_G一起通过计算3×3矩阵A作为平面来定义平面
A = \sum_{i=1}^N (r_i - r_G)(r_i - r_G)^T
Run Code Online (Sandbox Code Playgroud)
利用该矩阵,法线向量n现在由A的特征向量给出,对应于A的最小特征值.
要了解特征向量/特征值对,请使用您选择的任何线性代数库.
该解决方案基于Hermitian矩阵A的Rayleight-Ritz定理.
请参阅 David Eberly 的“数据的最小二乘拟合”,了解我如何想出这一方法来最小化几何拟合(从点到平面的正交距离)。
bool Geom_utils::Fit_plane_direct(const arma::mat& pts_in, Plane& plane_out)
{
bool success(false);
int K(pts_in.n_cols);
if(pts_in.n_rows == 3 && K > 2) // check for bad sizing and indeterminate case
{
plane_out._p_3 = (1.0/static_cast<double>(K))*arma::sum(pts_in,1);
arma::mat A(pts_in);
A.each_col() -= plane_out._p_3; //[x1-p, x2-p, ..., xk-p]
arma::mat33 M(A*A.t());
arma::vec3 D;
arma::mat33 V;
if(arma::eig_sym(D,V,M))
{
// diagonalization succeeded
plane_out._n_3 = V.col(0); // in ascending order by default
if(plane_out._n_3(2) < 0)
{
plane_out._n_3 = -plane_out._n_3; // upward pointing
}
success = true;
}
}
return success;
}
Run Code Online (Sandbox Code Playgroud)
将平面拟合到 1000 个点的时间为 37微秒(Windows 7、i7、32 位程序)

使用 OpenCV 的 C++ 代码:
float fitPlaneToSetOfPoints(const std::vector<cv::Point3f> &pts, cv::Point3f &p0, cv::Vec3f &nml) {
const int SCALAR_TYPE = CV_32F;
typedef float ScalarType;
// Calculate centroid
p0 = cv::Point3f(0,0,0);
for (int i = 0; i < pts.size(); ++i)
p0 = p0 + conv<cv::Vec3f>(pts[i]);
p0 *= 1.0/pts.size();
// Compose data matrix subtracting the centroid from each point
cv::Mat Q(pts.size(), 3, SCALAR_TYPE);
for (int i = 0; i < pts.size(); ++i) {
Q.at<ScalarType>(i,0) = pts[i].x - p0.x;
Q.at<ScalarType>(i,1) = pts[i].y - p0.y;
Q.at<ScalarType>(i,2) = pts[i].z - p0.z;
}
// Compute SVD decomposition and the Total Least Squares solution, which is the eigenvector corresponding to the least eigenvalue
cv::SVD svd(Q, cv::SVD::MODIFY_A|cv::SVD::FULL_UV);
nml = svd.vt.row(2);
// Calculate the actual RMS error
float err = 0;
for (int i = 0; i < pts.size(); ++i)
err += powf(nml.dot(pts[i] - p0), 2);
err = sqrtf(err / pts.size());
return err;
}
Run Code Online (Sandbox Code Playgroud)