有一系列的數據點 {xi,yi}{xi,yi},我們知道這些數據點近似的落在一個圓上,根據這些數據估計這個圓的參數就是一個很有意義的問題。今天就來講講如何來做圓的擬合。圓擬合的方法有很多種,最小二乘法屬於比較簡單的一種。今天就先將這種。
我們知道圓方程可以寫為:
通常的最小二乘擬合要求距離的平方和最小。也就是
最小。這個算起來會很麻煩。也得不到解析解。所以我們退而求其次。
這個式子要簡單的多。我們定義一個輔助函數:
那么上面的式子可以表示為:
按照最小二乘法的通常的步驟,可知ff 取極值時對應下面的條件。
先來化簡 ∂f∂R=0∂f∂R=0
我們知道半徑 RR 是不能為 0 的。所以必然有:
這是個非常有用的結論。剩下的兩個式子:
也就是下面兩個等式:
這里設:
其中:
那么簡單的推導一下,就會發現:
其實都不需要推導,這個變量替換只不過是修改了坐標原點的位置。而我們的等式根本就與坐標原點的具體位置無關。所以必然成立。
這兩個式子展開寫是這樣的:
進一步展開:
我們知道 ∑ui=0∑ui=0, ∑vi=0∑vi=0。所以上面兩個式子是可以化簡的。
為了簡化式子,我們定義幾個參數:
那么上面的式子可以寫為:
至此,就可以解出ucuc 和vcvc 了。
那么:
還剩下個 RR 沒求出來, 也很簡單。
所以:
好了。下面給出個代碼,這個代碼的具體公式和我這里給出的有一點小差異,但是原理是相同的。
/** * 最小二乘法擬合圓 * 擬合出的圓以圓心坐標和半徑的形式表示 * 此代碼改編自 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; }
- 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% 是干擾數據。擬合出的圓就明顯被拽偏了。
之所以出現這個問題就是因為最小二乘擬合的平方項對離群點非常敏感。解決這個問題就要用其他的擬合算法,比如用距離之和作為擬合判據。等我有空了再寫一篇博客介紹其他幾種方法。
