打开APP
userphoto
未登录

开通VIP,畅享免费电子书等14项超值服

开通VIP
最小二乘法拟合圆

有一系列的数据点 {xi,yi},我们知道这些数据点近似的落在一个圆上,根据这些数据估计这个圆的参数就是一个很有意义的问题。今天就来讲讲如何来做圆的拟合。圆拟合的方法有很多种,最小二乘法属于比较简单的一种。今天就先将这种。

我们知道圆方程可以写为:

(xxc)2+(yyc)2=R2

通常的最小二乘拟合要求距离的平方和最小。也就是

f=((xixc)2+(yiyc)2R)2

最小。这个算起来会很麻烦。也得不到解析解。所以我们退而求其次。

f=((xixc)2+(yiyc)2R2)2

这个式子要简单的多。我们定义一个辅助函数:

g(x,y)=(xxc)2+(yyc)2R2

那么上面的式子可以表示为:

f=g(xi,yi)2

按照最小二乘法的通常的步骤,可知f 取极值时对应下面的条件。

fxc=0fyc=0fR=0

先来化简 fR=0

fR=2R×((xixc)2+(yiyc)2R2)=2R×g(xi,yi)=0

我们知道半径 R 是不能为 0 的。所以必然有:

g(xi,yi)=0

这是个非常有用的结论。剩下的两个式子:

fxc=4((xixc)2+(yiyc)2R2)(xixc)=4g(xi,yi)(xixc)=4xig(xi,yi)=0fyc=4((xixc)2+(yiyc)2R2)(yiyc)=4g(xi,yi)(yiyc)=4yig(xi,yi)=0

也就是下面两个等式:
xig(xi,yi)=0yig(xi,yi)=0

这里设:

ui=xix¯uc=xcx¯vi=yiy¯vc=ycy¯

其中:
x¯=xi/Ny¯=yi/N

那么简单的推导一下,就会发现:
uig(ui,vi)=0vig(ui,vi)=0

其实都不需要推导,这个变量替换只不过是修改了坐标原点的位置。而我们的等式根本就与坐标原点的具体位置无关。所以必然成立。

这两个式子展开写是这样的:

((uiuc)2+(vivc)2R2)ui=0((uiuc)2+(vivc)2R2)vi=0

进一步展开:

(ui32ui2uc+uiuc2+uivi22uivivc+uivc2uiR2)=0(ui2vi2uiviuc+viuc2+vi32vi2vc+vivc2viR2)=0

我们知道 ui=0, vi=0。所以上面两个式子是可以化简的。

(ui32ui2uc+uivi22uivivc)=0(ui2vi2uiviuc+vi32vi2vc)=0

为了简化式子,我们定义几个参数:

Suuu=ui3Svvv=vi3Suu=ui2Svv=vi2Suv=uiviSuuv=ui2viSuvv=uivi2

那么上面的式子可以写为:

Suuuc+Suvvc=Suuu+Suvv2Suvuc+Svvvc=Suuv+Svvv2

至此,就可以解出ucvc 了。

uc=SuuvSuvSuuuSvvSuvvSvv+SuvSvvv2(Suv2SuuSvv)vc=SuuSuuv+SuuuSuv+SuvSuvvSuuSvvv2(Suv2SuuSvv)

那么:

xc=uc+x¯yc=vc+y¯

还剩下个 R 没求出来, 也很简单。

g(xi,yi)=0((xixc)2+(yiyc)2R2)=0

所以:

R2=((xixc)2+(yiyc)2)

好了。下面给出个代码,这个代码的具体公式和我这里给出的有一点小差异,但是原理是相同的。

/** * 最小二乘法拟合圆 * 拟合出的圆以圆心坐标和半径的形式表示 * 此代码改编自 newsmth.net 的 jingxing 在 Graphics 版贴出的代码。 * 版权归 jingxing, 我只是搬运工外加一些简单的修改工作。 */typedef complex<int> POINT;bool circleLeastFit(const std::vector<POINT> &points, double &center_x, double &center_y, double &radius){     center_x = 0.0f;     center_y = 0.0f;     radius = 0.0f;     if (points.size() < 3)     {         return false;     }     double sum_x = 0.0f, sum_y = 0.0f;     double sum_x2 = 0.0f, sum_y2 = 0.0f;     double sum_x3 = 0.0f, sum_y3 = 0.0f;     double sum_xy = 0.0f, sum_x1y2 = 0.0f, sum_x2y1 = 0.0f;     int N = points.size();     for (int i = 0; i < N; i++)     {         double x = points[i].real();         double y = points[i].imag();         double x2 = x * x;         double y2 = y * y;         sum_x += x;         sum_y += y;         sum_x2 += x2;         sum_y2 += y2;         sum_x3 += x2 * x;         sum_y3 += y2 * y;         sum_xy += x * y;         sum_x1y2 += x * y2;         sum_x2y1 += x2 * y;     }     double C, D, E, G, H;     double a, b, c;     C = N * sum_x2 - sum_x * sum_x;     D = N * sum_xy - sum_x * sum_y;     E = N * sum_x3 + N * sum_x1y2 - (sum_x2 + sum_y2) * sum_x;     G = N * sum_y2 - sum_y * sum_y;     H = N * sum_x2y1 + N * sum_y3 - (sum_x2 + sum_y2) * sum_y;     a = (H * D - E * G) / (C * G - D * D);     b = (H * C - E * D) / (D * D - G * C);     c = -(a * sum_x + b * sum_y + sum_x2 + sum_y2) / N;     center_x = a / (-2);     center_y = b / (-2);     radius = sqrt(a * a + b * b - 4 * c) / 2;     return true;}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57

下图是个实际测试的结果。对这种均匀分布的噪声数据计算的结果还是很准确的。

但是当数据中有部分偏向某一个方向的干扰数据时。结果就不那么乐观了。下图就很说明问题。

数据点中有 20% 是干扰数据。拟合出的圆就明显被拽偏了。

之所以出现这个问题就是因为最小二乘拟合的平方项对离群点非常敏感。解决这个问题就要用其他的拟合算法,比如用距离之和作为拟合判据。等我有空了再写一篇博客介绍其他几种方法。

本站仅提供存储服务,所有内容均由用户发布,如发现有害或侵权内容,请点击举报
打开APP,阅读全文并永久保存 查看更多类似文章
猜你喜欢
类似文章
【热】打开小程序,算一算2024你的财运
矩阵、向量求导法则
泊松分布的期望和方差推导
闭区间套定理(Nested intervals theorem)讲解2
循环群的例子
最小二乘法 来龙去脉
角动量与角动量守恒
更多类似文章 >>
生活服务
热点新闻
分享 收藏 导长图 关注 下载文章
绑定账号成功
后续可登录账号畅享VIP特权!
如果VIP功能使用有故障,
可点击这里联系客服!

联系客服