距離絕對(duì)值的和最小擬合圓

上一篇博客介紹了最小二乘法擬合圓的方法咕缎。這種方法對(duì)誤差符合正態(tài)分布的數(shù)據(jù)點(diǎn)很有效滞欠。但是在機(jī)器視覺(jué)應(yīng)用中經(jīng)常會(huì)碰到一些干擾點(diǎn)残家。這些干擾點(diǎn)多數(shù)時(shí)候是偏向某一個(gè)方向的。這時(shí)要是用最小二乘法擬合莉撇,擬合出的圓會(huì)偏很多觉义。因此测摔,有必要研究更有效的擬合算法蛮瞄。

這里介紹一個(gè)我常用的擬合算法,根據(jù)數(shù)據(jù)點(diǎn)到圓的距離絕對(duì)值的和來(lái)確定圓的參數(shù)谆扎,也就是下面這個(gè)式子:

f=∑∣∣(xi?xc)2+(yi?yc)2??????????????????√?R∣∣f=∑|(xi?xc)2+(yi?yc)2?R|

使得ff取得最小值的xcxc挂捅、ycyc和RR就是最佳擬合參數(shù)。

用這個(gè)式子沒(méi)有解析解堂湖,只能靠數(shù)值算法闲先。這里我采用了 GSL (GNU Scientific Library )里的多維函數(shù)求極值的功能。關(guān)于如何安裝无蜂、使用 GSL 等問(wèn)題我有其他的博客介紹了伺糠,這里就不多說(shuō)了。下面給代碼斥季。

建立了一個(gè)類训桶,叫做 “CircleFitSolver”累驮。把所有的計(jì)算都封裝到這個(gè)類中。

#ifndef CIRCLEFITSOLVER_H#define CIRCLEFITSOLVER_H#include #include #include #include using namespace std;typedef complex POINT;bool circleLeastFit(const std::vector &points, double ¢er_x, double ¢er_y, double &radius);class CircleFitSolver

{public:

? ? CircleFitSolver();

? ? ~CircleFitSolver();

? ? void setMaxIter(int iter) {m_max_iter = iter;}

? ? /**

? ? * @brief circleFitL1? 擬合圓舵揭,擬合判據(jù)為數(shù)據(jù)點(diǎn)到擬合圓的距離絕對(duì)值之和最小谤专。

? ? * @param points 輸入?yún)?shù),存儲(chǔ)各個(gè)數(shù)據(jù)點(diǎn)午绳。

? ? * @param center_x radius > 0 時(shí)作為迭代算法的初始值置侍。計(jì)算完成后返回?cái)M合圓的圓心 X 坐標(biāo)

? ? * @param center_y radius > 0 時(shí)作為迭代算法的初始值。計(jì)算完成后返回?cái)M合圓的圓心 Y 坐標(biāo)

? ? * @param radius? radius < 0 時(shí)拦焚,用最小二乘擬合的結(jié)果作為迭代算法的初始值蜡坊。計(jì)算完成后返回?cái)M合圓的半徑。

? ? * @return true 表示擬合成功赎败,否則擬合失敗秕衙。

? ? */? ? bool circleFitL1(const vector &points, double ¢er_x, double ¢er_y, double &radius);private:

? ? gsl_multimin_function m_function;

? ? gsl_multimin_fminimizer * m_fminimizer;

? ? int m_max_iter; // 迭代算法的最大迭代次數(shù)? ? gsl_vector *m_start_point; // 迭代算法的初始值? ? gsl_vector *m_step_size; // 迭代算法的初始步長(zhǎng)? ? void setStartPoint(double center_x, double center_y, double radius);

? ? static double L1_distance(const gsl_vector * v, void * params);

};#endif// CIRCLEFITSOLVER_H

具體的實(shí)現(xiàn)代碼如下:

#include "circlefitsolver.h"#include using namespace std;/**

* 最小二乘法擬合圓

* 擬合出的圓以圓心坐標(biāo)和半徑的形式表示

* 此代碼改編自 newsmth.net 的 jingxing 在 Graphics 版貼出的代碼。

* 版權(quán)歸 jingxing螟够, 我只是搬運(yùn)工外加一些簡(jiǎn)單的修改工作灾梦。

*/bool circleLeastFit(const std::vector &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;

}double CircleFitSolver::L1_distance(const gsl_vector * v, void * params)

{

? ? vector *vect = (vector *)params;

? ? int N? = vect->size();

? ? double a, b, r;

? ? a = gsl_vector_get(v, 0);

? ? b = gsl_vector_get(v, 1);

? ? r = gsl_vector_get(v, 2);

? ? double sum = 0;

? ? for(int i = 0; i < N; i++)

? ? {

? ? ? ? const POINT p = vect->at(i);

? ? ? ? double xi = p.real() - a;

? ? ? ? double yi = p.imag() - b;

? ? ? ? double dist = sqrt(xi * xi + yi * yi) - r;

? ? ? ? sum += fabs(dist);

? ? }

? ? return sum;

}inline void CircleFitSolver::setStartPoint(double center_x, double center_y, double radius)

{

? ? gsl_vector_set (m_start_point, 0, center_x);

? ? gsl_vector_set (m_start_point, 1, center_y);

? ? gsl_vector_set (m_start_point, 2, radius);

}bool CircleFitSolver::circleFitL1(const vector &points, double ¢er_x, double ¢er_y, double &radius)

{

? ? m_function.params = (void *)&points;

? ? if( radius < 0 )

? ? {

? ? ? ? // 用最小二乘擬合的結(jié)果作為初始值? ? ? ? if( !circleLeastFit(points, center_x, center_y, radius) )

? ? ? ? {

? ? ? ? ? ? return false;

? ? ? ? }

? ? }

? ? setStartPoint(center_x, center_y, radius);

? ? /* 經(jīng)驗(yàn)值,初始步長(zhǎng)設(shè)置為半徑的十分之一 */? ? gsl_vector_set (m_step_size, 0, radius / 10.0);

? ? gsl_vector_set (m_step_size, 1, radius / 10.0);

? ? gsl_vector_set (m_step_size, 2, radius / 10.0);

? ? gsl_multimin_fminimizer_set(m_fminimizer, &m_function, m_start_point, m_step_size);

? ? int iter = 0;

? ? int status;

? ? do? ? {

? ? ? ? iter++;

? ? ? ? status = gsl_multimin_fminimizer_iterate(m_fminimizer);

? ? ? ? if (status == GSL_ENOPROG ) // 表示無(wú)法找到更好的解了? ? ? ? {

? ? ? ? ? ? break;

? ? ? ? }

? ? ? ? double size = gsl_multimin_fminimizer_size (m_fminimizer);

? ? ? ? status = gsl_multimin_test_size (size, 1e-2);

? ? }

? ? while (status == GSL_CONTINUE && iter < m_max_iter);

? ? gsl_vector * out = gsl_multimin_fminimizer_x(m_fminimizer);

? ? center_x = gsl_vector_get(out, 0);

? ? center_y = gsl_vector_get(out, 1);

? ? radius = gsl_vector_get(out, 2);

? ? return true;

}

CircleFitSolver::CircleFitSolver()

{

? ? m_max_iter = 100; // 默認(rèn)最大迭代 100 步? ? m_function.n = 3;

? ? m_function.f = L1_distance;

? ? m_start_point = gsl_vector_alloc (m_function.n);

? ? m_step_size = gsl_vector_alloc (m_function.n);

? ? m_fminimizer = gsl_multimin_fminimizer_alloc(gsl_multimin_fminimizer_nmsimplex, 3);

}

CircleFitSolver::~CircleFitSolver()

{

? ? gsl_vector_free(m_start_point);

? ? gsl_vector_free(m_step_size);

? ? gsl_multimin_fminimizer_free(m_fminimizer);

}

這個(gè)代碼基本上就是中規(guī)中矩的計(jì)算妓笙,沒(méi)太多可說(shuō)的若河。唯一一個(gè)小技巧就是用最小二乘法的結(jié)果作為迭代的初始值。這樣很快就能收斂寞宫。

下面給個(gè)圖萧福,說(shuō)說(shuō)它的效果吧。


?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末辈赋,一起剝皮案震驚了整個(gè)濱河市鲫忍,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌钥屈,老刑警劉巖悟民,帶你破解...
    沈念sama閱讀 218,204評(píng)論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異篷就,居然都是意外死亡射亏,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,091評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門竭业,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)智润,“玉大人,你說(shuō)我怎么就攤上這事未辆】弑粒” “怎么了?”我有些...
    開(kāi)封第一講書人閱讀 164,548評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵咐柜,是天一觀的道長(zhǎng)兼蜈。 經(jīng)常有香客問(wèn)我攘残,道長(zhǎng),這世上最難降的妖魔是什么饭尝? 我笑而不...
    開(kāi)封第一講書人閱讀 58,657評(píng)論 1 293
  • 正文 為了忘掉前任肯腕,我火速辦了婚禮,結(jié)果婚禮上钥平,老公的妹妹穿的比我還像新娘实撒。我一直安慰自己,他們只是感情好涉瘾,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,689評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布知态。 她就那樣靜靜地躺著,像睡著了一般立叛。 火紅的嫁衣襯著肌膚如雪负敏。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書人閱讀 51,554評(píng)論 1 305
  • 那天秘蛇,我揣著相機(jī)與錄音其做,去河邊找鬼。 笑死赁还,一個(gè)胖子當(dāng)著我的面吹牛妖泄,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播艘策,決...
    沈念sama閱讀 40,302評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼蹈胡,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了朋蔫?” 一聲冷哼從身側(cè)響起罚渐,我...
    開(kāi)封第一講書人閱讀 39,216評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎驯妄,沒(méi)想到半個(gè)月后荷并,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,661評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡青扔,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,851評(píng)論 3 336
  • 正文 我和宋清朗相戀三年璧坟,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片赎懦。...
    茶點(diǎn)故事閱讀 39,977評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖幻工,靈堂內(nèi)的尸體忽然破棺而出励两,到底是詐尸還是另有隱情,我是刑警寧澤囊颅,帶...
    沈念sama閱讀 35,697評(píng)論 5 347
  • 正文 年R本政府宣布当悔,位于F島的核電站傅瞻,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏盲憎。R本人自食惡果不足惜嗅骄,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,306評(píng)論 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望饼疙。 院中可真熱鬧溺森,春花似錦、人聲如沸窑眯。這莊子的主人今日做“春日...
    開(kāi)封第一講書人閱讀 31,898評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)磅甩。三九已至炊林,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間卷要,已是汗流浹背渣聚。 一陣腳步聲響...
    開(kāi)封第一講書人閱讀 33,019評(píng)論 1 270
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留僧叉,地道東北人奕枝。 一個(gè)月前我還...
    沈念sama閱讀 48,138評(píng)論 3 370
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像彪标,于是被迫代替她去往敵國(guó)和親倍权。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,927評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容