最小二乘法拟合一些基本空间元素的C#实现 —— (一)术语、基本算法与思路

本系列的主要目的是将拟合算法用C#实现,相关的操作用到了Math.NET数值运算库,所以将一些基本概念,算法和对应的C#代码放到前面。

对于线性模型,例如直线和平面,算法大约分三步:

  1. 参数化:将模型用参数化方式表达出来。
  2. 构造Design(非线性中的Jacobian)矩阵:根据参数公式求所有参数的偏导数作为列(Column),将实测值作为行(Row)。将偏差值作为等式右值。
  3. 求解。

对于非线性模型,例如圆柱,Gauss-Newton算法类似:

  1. 参数化:
  2. 求(或猜)初始解。
  3. 找到Jacobian矩阵及右值
  4. 求解
  5. 通过4计算逼近值,重复3和4
  6. 迭代结束

线性模型的解法是基础,详细的分析请看这篇文章,这里只解释代码部分。在Math.Net

对于矩阵\(A=\begin{bmatrix}A_{11} & A_{12}\\A_{21} & A_{22}\end{bmatrix}\)
A.Transpose() 代表A的转置矩阵\(A^T=\begin{bmatrix}A_{11} & A_{21}\\A_{12} & A_{22}\end{bmatrix}\)

这在坐标运算中是非常重要的,因为坐标运算中坐标系都表示为正交矩阵(抱歉,没找到证明),当计算一个点(用二维举例)\(P=\left(x,y\right)\)在新坐标系A中的坐标\(P^{‘}=\left(x^{‘},y^{‘}\right)\)时。\[P^{‘}=PA\]

而已知坐标系A中的坐标点\(P^{‘}\)求原坐标值可用\[P=P^{‘}A^T\]

一些类似的代码为:

A.Inverse()代表A的逆矩阵\(A^{-1}\)

A.Multiply(B)代表矩阵乘法\(A*B\)或者矩阵乘以向量\(A*b\)

对于常用的算法:

线性模型

已知Jacobian矩阵\(J\)及等式右值(偏差值向量)\(y\),另\[Ax=y\]求解向量x

  1. 正规方程(normal equations)
    \[A^TAx=A^Ty\]
    逆矩阵解法
    \[x=\left(A^TA\right)^{-1}A^Ty\]
    实现代码 
    x = A.Transpose().Multiply(A).Inverse().Multiply(A.Transpose().Multiply(y));

    更快和更稳定的方法将是对左侧的矩阵进行乔姆斯基分解(Cholesky decomposition),实现代码

    x = A.Transpose().Multiply(A).Cholesky().Solve(A.Transpose().Multiply(y));

    这两个在一些限定条件下是等价的,而这些限定条件在点数远大于参数的情况下是满足的。

  2. QR分解
    推论见原文,代码更简单: 
    x=A.QR().Solve(y);
  3. 奇异值分解(Singular Value Decomposition, SVD)
    A是方阵的情况下(即点数等于参数)代码为 
    x = A.Svd(true).Solve(y);

    A不是方阵的情况下代码为

// compute the SVD 
Svd svd = A.Svd(true);
// get matrix of left singular vectors with first n columns of U 
Matrix U1 = svd.U().SubMatrix(0, m,0, n); 
// get matrix of singular values 
Matrix S = new DiagonalMatrix(n, n, svd.S().ToArray()); 
// get matrix of right singular vectors 
Matrix V = svd.VT().Transpose(); 
x = V.Multiply(S.Inverse()).Multiply(U1.Transpose().Multiply(y));

非线性模型

这部分对于代码并没有什么区别,只是需要迭代(循环)及判断终止条件。

最小二乘法拟合一些基本空间元素的C#实现 —— (零)前言和参考资料

写这些(为了避免单个过长)文章的目的有两个,一是记录下最近工作的过程和参考的档案,避免以后忘记。二是万一有人和我有一样的问题,也许能搜到这篇文章,避免像我一样走这么多的歪路。

最近半因为工作,半因为兴趣,想实现一些笛卡尔三维坐标内的点云拟合基本元素(直线、平面、圆柱等),虽然很多软件都有非常强的功能,但是基本上都是重量级的收费工业软件,我买不起,而像我们公司的虽然我不用付费,API又几乎没有,所以也借这个机会来学习一下。

先要吐一下槽,中文的资料几乎找不到,不是一些算法课的作业和解答,就是论文数据库中付费才能看,而且不知道质量好坏的论文。不过也是这些论文拯救了我,虽然论文不免费,但是参考文档直接列在那,看参考文档往往比看这些翻译+“优化”的论文要好。

另外一个比较有用的工具是google 学术搜索,可以看到一个论文被引用的次数,后面附的一篇论文被引用了150多次,基本上是这个话题里最早的几篇论文,也是讲的最基础的论文。

另外还要感谢维基百科,一些基本已经忘记的数学概念都是通过这个想起来的,还可以当专业词典用,一篇文章切换中英文,可以极大的帮助理解。

以下是参考资料,会不断更新。

C#类:

math.net库是一个C#实现的数值计算库,在算法实现中大量实用了这个库提供的线代矩阵操作。如果使用C++,一个更有名的库是boost

msdn有任何问题和错误来msdn肯定能查到,比csdn要靠谱太多了。

stackoverflow无数次把我从绝望深渊拯救上来的人,什么问题都可以问,什么问题都有人问,几乎都会得到正确的解释和参考信息。

算法及原理类:

使用Math.NET进行线性与非线性最小二乘拟合,我最早找到的文章之一,我几乎以为单凭这篇文章就可以帮助我搞定问题。
这篇文章的优点是罗列了从基本到常用的几种线性与非线性算法,提供了比较详细的推导过程(不包括线代的定理),和所有的C#代码实现。(下载二进制源代码
不过这篇文章里只有单变量二维空间上的拟合。
使用这篇文章及代码可以了解如何使用Math.NET进行矩阵操作,帮助极大,因为其他的文章偏向于论文,往往推导出解法后连计算步骤都不给,使用这里提供的算法可以得到最后一步的结果。
我在代码中找到了一个小BUG,于是给作者写了信,作者很热心的回复并且告诉我有个中国朋友进行了翻译,这是赵毅力同学的翻译。更推荐先阅读这个版本,因为所有的基本概念都做了介绍,不用自己去查教材或者维基百科了。

无名文章,这是我正在使用而且帮助排名第二的文章,看格式像论文或者会议上的技术报告,但是没找到标题,这篇文章提供了笛卡尔空间上的线,圆柱,圆锥,球等的拟合算法,由于没找到引用信息,无法判断这篇文章的正确或者权威性,我目前看到空间直线部分,和我们公司的软件的计算结果取得一致,未来两天会继续验证。(1月23日添加,已验证直线平面和圆柱,算法正确)
缺点是这篇文章里只有基本数据的计算方法,和算法思路,稍微具体点的推导都没有。例如——矩阵A是每个点减平均算出来的,然后使用SVD对A进行计算就可以得到空间向量的解。
如果没有线代基础或者相关的算法概念会觉得莫名其妙,结合第一篇文章看就好很多了。

更原理性的一篇,这篇文章有两个版本,这篇稍老,新的没仔细看,和这个几乎一样Faithful版本,要赞一下这个Prof.论文都放到了个人网站上,省得去各个黑心网站上付费,我觉得论文这种东西写出来就是为了让大家看的,卖几十刀实在是太黑了啊。
不过对应用层面帮助不大,不过如果想对算法实现改进(例如从最小二乘至最小元素法),应该会有帮助的,等我有需求时再看吧。

另外还有几篇是针对圆柱的,因为我个人工作的需求主要针对圆柱的拟合,所以也列在这里了。

5点计算圆柱的算法,给出了详细的推导和分析,产生多种结果的原因,以及Mathematica下的代码实现。

圆柱拟合的代数算法实现

最小误差(条件)及最小二乘拟合圆柱的实现应该是一个图形库的文档,应该是基于C++和OpenGL的库,也提供了代码实现。而且库中直接实现了基本元素的最小二乘拟合实现,圆柱C++实现