目录php
这篇博客的目的是介绍移动最小二乘法(moving least squares)在点云平滑和重采样的应用。开头先简要介绍最小二乘法,用两种不一样的方法来求解最小二乘法,并给出一个具体的算例、代码、数据。目前关于最小二乘法的博客和网上的讨论有不少,我不必重复作这个工做(实际上是我太菜不能形象的讲出来哈),我想作的是提供一个简要的归纳,以及给出一个具体的例子帮助读者理解。接下来,介绍加权最小二乘法,在一样的数据上面,跑加权最小二乘法,看看拟合的结果又如何。以后,简略分析一下PCL是如何实现的点云平滑和重采样。参考的文献列在最后,比较有价值的是[1][2],基本上是看这两个理解的移动最小二乘法。因为我学力尚浅,自知这篇文章有不少的不足、错误,但愿读者不吝指出。欢迎理性的讨论和交流。html
对于方程组\(A\mathbf{x}=b\),咱们要去寻找一个\(Ax'\)最佳逼近b,即有
\[|b - Ax'| \le |b - Ax|\]
抽象点说,在\(A\)的列空间中找到最逼近b的那个点。python
将向量\(b\)投影到\(A\)的列空间获得\(b'\),\((b - b')\)是正交于A的列空间,即有
\[A^T*(b - Ax') = 0\]
若是\((A^T*A)\)可逆:
\[x' = (A^T*A)^{-1}*A^T*b\]数组
这组数据,来自于[2]。画出散点图。dom
x = [0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0] y = [0, 4, 5, 14, 15, 14.5, 14, 12, 10, 5, 4]
首先考虑使用\(y = mx + n\)来拟合(这里用m,n而不用a,b是怕读者混淆)。须要解决的问题是数据如何装填。A矩阵放什么,b放什么,x又是什么。
欲求的是\(m,n\)。\(x = [m \quad n]^T\),\(b\)放入的是每个因变量。欲求\(Ax = b\),那么每一行A乘以x,会等于对应的每一行b:
\[A = \begin{bmatrix}x_1&1\\x_2&1\\...&...\\x_n&1\\\end{bmatrix}\]
\[b = \begin{bmatrix}y_1\\y_2\\...\\y_n\end{bmatrix}\]函数
A = np.empty((0, 2)) B = np.empty((0, 1)) for (a, b) in zip(x, y): row1 = np.array([a, 1]) row2 = np.array([b]) A = np.vstack([A, row1]) B = np.vstack([B, row2]) ans = np.linalg.inv(np.mat(A.transpose()) * np.mat(A)) * np.mat(A.transpose()) * np.mat(B) print(ans) xx = np.arange(0, 1, 0.01) yy = ans[0, 0] * xx + ans[1, 0] plt.plot(xx, yy) plt.scatter(x, y, c='r') plt.show()
拟合结果:
spa
但是这个拟合的结果太差劲了吧!咱们观察数据,以为这个能够用二次函数去拟合,那么可使用新的拟合函数:\(y = ax^2 + bx + c\)3d
\[A = \begin{bmatrix}x_1^2&x_1&1\\x_2^2&x_2&1\\...&...\\x_n^2&x_n&1\\\end{bmatrix}\]
\[x = [a\quad b\quad c]^T\]code
再拟合一次:此次拟合的还行。\(a = -53.96270396, b = 57.05361305, c = -0.77622378\)orm
A = np.empty((0, 3)) B = np.empty((0, 1)) for (a, b) in zip(x, y): row1 = np.array([a*a, a, 1]) row2 = np.array([b]) A = np.vstack([A, row1]) B = np.vstack([B, row2]) print(A) print(B) ans = np.linalg.inv(np.mat(A.transpose()) * np.mat(A)) * np.mat(A.transpose()) * np.mat(B) print(ans) xx = np.arange(0, 1, 0.01) yy = ans[0, 0] * xx**2 + ans[1, 0] * xx + ans[2, 0] plt.plot(xx, yy) plt.scatter(x, y, c='r') plt.show()
另外一种理解最小二乘法的方法是,找到使因变量的偏差平方和最小的参数来求解。假设咱们有二维数据点集\((x_i, y_i)\),对他拟合的函数为\(f(x)\)。
\[J_{LS} = \sum^{k}_{n=1}(f(x_i) - y_i)^2\]
因而问题化解为求\(min(J_{LS})\)。
为何使用偏差平方和呢?我以为沟通这个方法和上一个方法的关键在于距离!偏差平方和,实际上是因变量的向量\((y_1, y_2, ... , y_n)\)和拟合函数对应函数值的向量\((f(x_1), f(x_2), ... , f(x_n))\)的距离平方。要使到这个距离最小,那就是投影!其实这两种方法本质上都是利用投影来使到误差最小。不一样之处在于一种使用了内积为0的方式来求,一种使用求偏导算最小值的方式来求。
[1][2]都是用了一个系数向量\(\alpha\),和基函数\(p(x)\)来表示\(f(x) = p(x)^T * \alpha\)。
若是有一个自变量x,最高阶为3,那么\(p(x) = [1,x,x^2,x^3]^T\)
若是有两个自变量x、y,最高阶为2,那么\(p(x) = [1, x, y, x^2, xy, y^2]^T\)
系数向量是咱们想要求的向量, \(\alpha = [c_1, c_2, ..., c_n]\)。
对\(J_{LS}\)分别求\(c_1, c_2, ..., c_n\)的偏导,具体过程参考[1]
\[\frac{\partial J_{LS}}{\partial \alpha} = \sum^n_{i=1} 2*p(x_i)*(p(x_i)^T * \alpha - y_i) \]
令偏导为0
\[\alpha = [\sum^n_{i=1}(p(x_i) * p(x_i)^T)]^{-1} * \sum^n_{i=1}p(x_i)*y_i\]
注意到\(p(x_i)\)是一个\(n\times 1\)的向量,他的转置则是\(1\times n\),因而它和它的转置相乘是一个\(n\times n\)的矩阵。
使用例子2的函数去拟合:\(y = a + bx + cx^2\)
基函数\(p(x) = [1, x, x^2]^T\), 系数向量\(\alpha = [a, b, c]\)
\[p(x) * p(x)^T = \begin{bmatrix}1&x&x^2\\x&x^2&x^3\\x^2&x^3&x^4\\\end{bmatrix}\]
拟合出来的结果为:\(a=-0.77622378,b=57.05361305,c=-53.96270396\)。异曲同工啊!
sumx = sumxx = sumxxx = sumxxxx = sumf = sumxf = sumxxf = 0 for (a, b) in zip(x, y): sumx += a sumxx += a ** 2 sumxxx += a ** 3 sumxxxx += a ** 4 sumf += b sumxf += a * b sumxxf += a * a * b A = np.array([[len(x), sumx, sumxx], [sumx, sumxx, sumxxx], [sumxx, sumxxx, sumxxxx]]) B = np.array([sumf, sumxf, sumxxf]) ans = np.linalg.solve(A, B) print(ans) xx = np.arange(0, 1, 0.01) yy = ans[0] + ans[1] * xx + ans[2] * xx**2 plt.plot(xx, yy) plt.scatter(x, y, c='r') plt.show()
[2]论文中:
引入紧支( Compact Support)概念,认为点 x 处的值 y 只受 x 附近子域内节点影响
因变量只受到自变量某一邻域影响,引入一个权重函数,给重要的地方加权,不重要的地方削弱它对因变量的影响。
\[J_{WLS} = \sum^n_{i=1}\theta(s) (f(x_i) - y_i)^2\]
其中\(s\)为想要求的自变量\(x\)到附近样本自变量的欧几里得距离。
若是咱们想根据一组数据去估计某一个自变量\(x\)的函数值,咱们就得计算一次!这种方法计算量相对而言大不少。
一样这是一个最小化问题\(min(J_{WLS})\),对它求偏导,令偏导为0。
\[\alpha = [\sum^n_{i=1}\theta(s)(p(x_i) * p(x_i)^T)]^{-1} * \sum^n_{i=1}\theta(s)p(x_i)*y_i\]
仍然是那一组数据。使用[2]建议的加权函数来求解。只不过此次使用的是一个一次函数来拟合局部。\(p(x) = [1,x]^T\)
所谓的移动最小二乘法,找到一个函数,这个函数有一系列的局部的函数组成\(f(x) \in f_X(x)\),本质上就是WLS。
def w(dis): dis = dis / 0.3 if dis < 0: return 0 elif dis <= 0.5: return 2/3 - 4 * dis**2 + 4 * dis**3 elif dis <= 1: return 4/3 - 4 * dis + 4 * dis**2 - 4/3 * dis**3 else: return 0 def mls(x_): sumxx = sumx = sumxf = sumf = sumw = 0 for (a, b) in zip(x, y): weight = w(abs(x_ - a)) sumw += weight sumx += a * weight sumxx += a * a * weight sumf += b * weight sumxf += a * b * weight A = np.array([[sumw, sumx], [sumx, sumxx]]) B = np.array([sumf, sumxf]) ans = np.linalg.solve(A, B) return ans[0] + ans[1] * x_ x = [0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0] y = [0, 4, 5, 14, 15, 14.5, 14, 12, 10, 5, 4] xx = np.arange(0, 1, 0.01) yy = [mls(xi) for xi in xx] plt.plot(xx, yy) plt.scatter(x, y, c='r') plt.show()
简单来讲就是对每一个样本计算一次加权最小二乘法,而后对该样本的自变量\(x_i\)求函数值\(f_X(x_i)\),算出来的\((x, f_X(x_i))\)就是平滑的结果。
在点的某个范围内,若是有足够的点,就不进行重采样。若是不够,那么就随机添加点到这个范围内,投影到计算出来的曲面上,直到达到足够的点数。
这个方法更加直接,按照必定的步长,一个一个点,整齐的添加点。
[3]中,使用一种在Voronoi图上重采样的方法。
思路:从输入的点云里面选点,在局部作一个平面的拟合,将这些点投影到平面上。计算这些点的Voronoi图,每次选择Voronoi节点中到它临近点(多于三个点)最大的那个节点,做为一个新的点,将它投影到拟合出来的mls曲面上。重复这个过程,直到Voronoi到它的临近点的半径小于某个阈值为止。
完。第一篇博客,欢迎理性的讨论。不足、错误之处,但愿读者不吝指出。
[1] http://www.nealen.com/projects/mls/asapmls.pdf
[2] https://wenku.baidu.com/view/fe7a74976f1aff00bed51eb1.html
[3] http://www.sci.utah.edu/~shachar/Publications/crpss.pdf