有一系列的數據點
今天就先將這樣的。
我們知道圓方程能夠寫為:
通常的最小二乘擬合要求距離的平方和最小。也就是
最小。
這個算起來會非常麻煩。
也得不到解析解。
所以我們退而求其次。
這個式子要簡單的多。我們定義一個輔助函數:
那么上面的式子能夠表示為:
依照最小二乘法的通常的步驟,可知
先來化簡
我們知道半徑
這是個非常實用的結論。剩下的兩個式子:
也就是以下兩個等式:
這里設:
當中:
那么簡單的推導一下,就會發現:
事實上都不須要推導,這個變量替換僅僅只是是改動了坐標原點的位置。而我們的等式根本就與坐標原點的詳細位置無關。
所以必定成立。
這兩個式子展開寫是這樣的:
進一步展開:
我們知道
為了簡化式子,我們定義幾個參數:
那么上面的式子能夠寫為:
至此,就能夠解出
那么:
還剩下個
所以:
好了。
以下給出個代碼,這個代碼的詳細公式和我這里給出的有一點小差異。可是原理是同樣的。
/** * 最小二乘法擬合圓 * 擬合出的圓以圓心坐標和半徑的形式表示 * 此代碼改編自 newsmth.net 的 jingxing 在 Graphics 版貼出的代碼。 * 版權歸 jingxing, 我僅僅是搬運工外加一些簡單的改動工作。 */ typedef complex<int> POINT; bool circleLeastFit(const std::vector<POINT> &points, double ¢er_x, double ¢er_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% 是干擾數據。
擬合出的圓就明顯被拽偏了。
之所以出現這個問題就是由於最小二乘擬合的平方項對離群點非常敏感。解決問題就要用其它的擬合算法,比方用距離之和作為擬合判據。等我有空了再寫一篇博客介紹其它幾種方法。
