最小二乘法擬合圓


有一系列的數據點 {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

進一步展開:

(u3i2u2iuc+uiu2c+uiv2i2uivivc+uiv2cuiR2)=0(u2ivi2uiviuc+viu2c+v3i2v2ivc+viv2cviR2)=0

我們知道 ui=0 , vi=0 。所以上面兩個式子是能夠化簡的。

(u3i2u2iuc+uiv2i2uivivc)=0(u2ivi2uiviuc+v3i2v2ivc)=0

為了簡化式子,我們定義幾個參數:

Suuu=u3iSvvv=v3iSuu=u2iSvv=v2iSuv=uiviSuuv=u2iviSuvv=uiv2i

那么上面的式子能夠寫為:

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

至此,就能夠解出 uc vc 了。

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

那么:

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

還剩下個 R 沒求出來。 也非常easy。

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; }

下圖是個實際測試的結果。對這樣的均勻分布的噪聲數據計算的結果還是非常准確的。

這里寫圖片描寫敘述

可是當數據中有部分偏向某一個方向的干擾數據時。結果就不那么樂觀了。下圖就非常說明問題。

這里寫圖片描寫敘述

數據點中有 20% 是干擾數據。

擬合出的圓就明顯被拽偏了。

之所以出現這個問題就是由於最小二乘擬合的平方項對離群點非常敏感。解決問題就要用其它的擬合算法,比方用距離之和作為擬合判據。等我有空了再寫一篇博客介紹其它幾種方法。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM