本文讨论并实现的算法主要(99%)基于这篇文章,更多参考请阅读这里。
大部分能搜索到的关于直线最小二乘拟合的资料是基于2维空间的\[y=ax+ b\]
而从三维空间的角度上来看,这只是一种特例。本文主要讨论三维空间上的直线拟合。
参数化
三维直线的表示如下。\[\left(x,y,z\right)=\left(x_0,y_0,z_0\right) + t\left(a,b,c\right)\]
其中\(\left(x_0,y_0,z_0\right)\)为直线上任一已知点,\(\left(a,b,c\right)\)为向量,所以所有直线都可以使用一个点坐标及一个向量来表示。
对于测量点\(P_i\left(x_i,y_i,z_i\right)\)与直线的距离为\[d_i=\sqrt{{u_i}^2+{v_i}^2+{w_i}^2}\]
其中:\[u_i=c\left(y_i-y_0\right)-b\left(z_i-z_0\right)\]\[v_i=a\left(z_i-z_0\right)-c\left(x_i-x_0\right)\]\[w_i=b\left(x_i-x_0\right)-a\left(y_i-y_0\right)\]
算法
根据原文:拟合的直线过所有点的平均值,没有证明……
所以未知量其实是3个,构建一个矩阵\[A=\begin{bmatrix}x_1-x_0&y_1-y_0&z_1-z_0\\x_2-x_0&y_2-y_0&z_2-z_0\\\vdots & \vdots & \vdots \\x_i-x_0&y_i-y_0&z_i-z_0\end{bmatrix}\]
将矩阵A做奇异值分解(SVD),令:\[A=U_1SV^T\]
矩阵\(V\)的第一列(Column)即是直线向量\(\left(a,b,c\right)\)。
代码实现:
public override void Estimate(List<Point3D> datas)
{
double sum_x = 0;
double sum_y = 0;
double sum_z = 0;
foreach (Point3D temp in datas)
{
sum_x += temp.x;
sum_y += temp.y;
sum_z += temp.z;
}
sum_x /= datas.Count;
sum_y /= datas.Count;
sum_z /= datas.Count;
DenseMatrix jacobian = new DenseMatrix(datas.Count, 3);
foreach (Point3D temp in datas)
{
Vector<double> gradient = new DenseVector(3);
gradient[0] = temp.x - sum_x;
gradient[1] = temp.y - sum_y;
gradient[2] = temp.z - sum_z;
jacobian.SetRow(datas.IndexOf(temp), gradient);
}
Svd svd = jacobian.Svd(true);
// get matrix of left singular vectors with first n columns of U
Matrix<double> U1 = svd.U().SubMatrix(0, datas.Count, 0, 3);
// get matrix of singular values
Matrix<double> S = new DiagonalMatrix(3, 3, svd.S().ToArray());
// get matrix of right singular vectors
Matrix<double> V = svd.VT().Transpose();
Vector<double> parameters = new DenseVector(3);
parameters = V.Column(0);
x = sum_x;
y = sum_y;
z = sum_z;
i = parameters[0];
j = parameters[1];
k = parameters[2];
}
平面的最小二乘拟合
放在一起的原因是这两种元素的算法是完全一样的,直线拟合最后一步得到的矩阵V第一列是直线的向量,而第三列就是平面的向量。
参数化
平面同样可以用一个点\(\left(x_0,y_0,z_0\right)\)和一个向量\(\left(a,b,c\right)\)来表示,只是垂直与该向量。
平面的公式为\[a\left(x-x_0\right)+b\left(y-y_0\right)+c\left(z-z_0\right)=0\)
对于测量点\(P_i\left(x_i,y_i,z_i\right)\)直接的距离为\[d_i=a\left(x-x_0\right)+b\left(y-y_0\right)+c\left(z-z_0\right)\]
算法
与直线的算法完全相同。