ransca算法詳細介紹

1、算法概述:

RANSAC算法的基本假設是樣本中包含正確數據(inliers,可以被模型描述的數據),也包含異常數據(outliers,偏離正常範圍很遠、無法適應數學模型的數據),即數據集中含有噪聲。這些異常數據可能是由於錯誤的測量、錯誤的假設、錯誤的計算等產生的。同時RANSAC也假設,給定一組正確的數據,存在可以計算出符合這些數據的模型參數的方法。

2、算法思想描述:

RANSAC基本思想描述如下:
①考慮一個最小抽樣集的勢爲n的模型(n爲初始化模型參數所需的最小樣本數)和一個樣本集P,集合P的樣本數#(P)>n,從P中隨機抽取包含n個樣本的P的子集S初始化模型M;
②餘集SC=P\S中與模型M的誤差小於某一設定閾值t的樣本集以及S構成S*。S*認爲是內點集,它們構成S的一致集(Consensus Set);
③若#(S*)≥N,認爲得到正確的模型參數,並利用集S*(內點inliers)採用最小二乘等方法重新計算新的模型M*;重新隨機抽取新的S,重複以上過程。
④在完成一定的抽樣次數後,若未找到一致集則算法失敗,否則選取抽樣後得到的最大一致集判斷內外點,算法結束。
3、算法的基本假設:
RANSAC的基本假設是:
(1)數據由“局內點”組成,例如:數據的分佈可以用一些模型參數來解釋;
(2)“局外點”是不能適應該模型的數據;
(3)除此之外的數據屬於噪聲。
    局外點產生的原因有:噪聲的極值;錯誤的測量方法;對數據的錯誤假設。
    RANSAC也做了以下假設:給定一組(通常很小的)局內點,存在一個可以估計模型參數的過程;而該模型能夠解釋或者適用於局內點。


4、參考http://grunt1223.iteye.com/blog/961063

參考http://www.cnblogs.com/xrwang/archive/2011/03/09/ransac-1.html

給定兩個點p1與p2的座標,確定這兩點所構成的直線,要求對於輸入的任意點p3,都可以判斷它是否在該直線上。初中解析幾何知識告訴我們,判斷一個點在直線上,只需其與直線上任意兩點點斜率都相同即可。實際操作當中,往往會先根據已知的兩點算出直線的表達式(點斜式、截距式等等),然後通過向量計算即可方便地判斷p3是否在該直線上。 

生產實踐中的數據往往會有一定的偏差。例如我們知道兩個變量X與Y之間呈線性關係,Y=aX+b,我們想確定參數a與b的具體值。通過實驗,可以得到一組X與Y的測試值。雖然理論上兩個未知數的方程只需要兩組值即可確認,但由於系統誤差的原因,任意取兩點算出的a與b的值都不盡相同。我們希望的是,最後計算得出的理論模型與測試值的誤差最小。大學的高等數學課程中,詳細闡述了最小二乘法的思想。通過計算最小均方差關於參數a、b的偏導數爲零時的值。事實上,在很多情況下,最小二乘法都是線性迴歸的代名詞。 

遺憾的是,最小二乘法只適合與誤差較小的情況。試想一下這種情況,假使需要從一個噪音較大的數據集中提取模型(比方說只有20%的數據時符合模型的)時,最小二乘法就顯得力不從心了。例如下圖,肉眼可以很輕易地看出一條直線(模式),但算法卻找錯了。 

RANSAC算法詳解

RANSAC算法的輸入是一組觀測數據(往往含有較大的噪聲或無效點),一個用於解釋觀測數據的參數化模型以及一些可信的參數。RANSAC通過反覆選擇數據中的一組隨機子集來達成目標。被選取的子集被假設爲局內點,並用下述方法進行驗證: 

  • 有一個模型適應於假設的局內點,即所有的未知參數都能從假設的局內點計算得出。
  • 用1中得到的模型去測試所有的其它數據,如果某個點適用於估計的模型,認爲它也是局內點。
  • 如果有足夠多的點被歸類爲假設的局內點,那麼估計的模型就足夠合理。
  • 然後,用所有假設的局內點去重新估計模型(譬如使用最小二乘法),因爲它僅僅被初始的假設局內點估計過。
  • 最後,通過估計局內點與模型的錯誤率來評估模型。
  • 上述過程被重複執行固定的次數,每次產生的模型要麼因爲局內點太少而被捨棄,要麼因爲比現有的模型更好而被選用。


整個過程可參考下圖: 

RANSAC算法詳解
5、算法
5.1、僞代碼的表示形式
僞碼形式的算法如下所示:
輸入:
data —— 一組觀測數據
model —— 適應於數據的模型
n —— 適用於模型的最少數據個數
k —— 算法的迭代次數
t —— 用於決定數據是否適應於模型的閥值
d —— 判定模型是否適用於數據集的數據數目
輸出:
best_model —— 跟數據最匹配的模型參數(如果沒有找到好的模型,返回null)
best_consensus_set —— 估計出模型的數據點
best_error —— 跟數據相關的估計出的模型錯誤

iterations = 0
best_model = null
best_consensus_set = null
best_error = 無窮大
while ( iterations < k )
    maybe_inliers = 從數據集中隨機選擇n個點
    maybe_model = 適合於maybe_inliers的模型參數
    consensus_set = maybe_inliers

    for ( 每個數據集中不屬於maybe_inliers的點 )
        if ( 如果點適合於maybe_model,且錯誤小於t )
            將點添加到consensus_set
    if ( consensus_set中的元素數目大於d )
        已經找到了好的模型,現在測試該模型到底有多好
        better_model = 適合於consensus_set中所有點的模型參數
        this_error = better_model究竟如何適合這些點的度量
        if ( this_error < best_error )
            我們發現了比以前好的模型,保存該模型直到更好的模型出現
            best_model =  better_model
            best_consensus_set = consensus_set
            best_error =  this_error
    增加迭代次數
返回 best_model, best_consensus_set, best_error

    RANSAC算法的可能變化包括以下幾種:
    (1)如果發現了一種足夠好的模型(該模型有足夠小的錯誤率),則跳出主循環。這樣可能會節約計算額外參數的時間。
    (2)直接從maybe_model計算this_error,而不從consensus_set重新估計模型。這樣可能會節約比較兩種模型錯誤的時間,但可能會對噪聲更敏感。

5.2、關於算法的源代碼,Ziv Yaniv曾經寫一個不錯的C++版本,我在關鍵處增補了註釋: 
C代碼  收藏代碼
  1. #include <math.h>  
  2. #include "LineParamEstimator.h"  
  3.   
  4. LineParamEstimator::LineParamEstimator(double delta) : m_deltaSquared(delta*delta) {}  
  5.   
  6.   
  7. void LineParamEstimator::estimate(std::vector<Point2D *> &data,   
  8.                                                                     std::vector<double> &parameters)  
  9. {  
  10.     parameters.clear();  
  11.     if(data.size()<2)  
  12.         return;  
  13.     double nx = data[1]->y - data[0]->y;  
  14.     double ny = data[0]->x - data[1]->x;// 原始直線的斜率爲K,則法線的斜率爲-1/k  
  15.     double norm = sqrt(nx*nx + ny*ny);  
  16.       
  17.     parameters.push_back(nx/norm);  
  18.     parameters.push_back(ny/norm);  
  19.     parameters.push_back(data[0]->x);  
  20.     parameters.push_back(data[0]->y);          
  21. }  
  22.   
  23.   
  24. void LineParamEstimator::leastSquaresEstimate(std::vector<Point2D *> &data,   
  25.                                                                                             std::vector<double> &parameters)  
  26. {  
  27.     double meanX, meanY, nx, ny, norm;  
  28.     double covMat11, covMat12, covMat21, covMat22; // The entries of the symmetric covarinace matrix  
  29.     int i, dataSize = data.size();  
  30.   
  31.     parameters.clear();  
  32.     if(data.size()<2)  
  33.         return;  
  34.   
  35.     meanX = meanY = 0.0;  
  36.     covMat11 = covMat12 = covMat21 = covMat22 = 0;  
  37.     for(i=0; i<dataSize; i++) {  
  38.         meanX +=data[i]->x;  
  39.         meanY +=data[i]->y;  
  40.   
  41.         covMat11    +=data[i]->x * data[i]->x;  
  42.         covMat12    +=data[i]->x * data[i]->y;  
  43.         covMat22    +=data[i]->y * data[i]->y;  
  44.     }  
  45.   
  46.     meanX/=dataSize;  
  47.     meanY/=dataSize;  
  48.   
  49.     covMat11 -= dataSize*meanX*meanX;  
  50.         covMat12 -= dataSize*meanX*meanY;  
  51.     covMat22 -= dataSize*meanY*meanY;  
  52.     covMat21 = covMat12;  
  53.   
  54.     if(covMat11<1e-12) {  
  55.         nx = 1.0;  
  56.             ny = 0.0;  
  57.     }  
  58.     else {      //lamda1 is the largest eigen-value of the covariance matrix   
  59.                //and is used to compute the eigne-vector corresponding to the smallest  
  60.                //eigenvalue, which isn't computed explicitly.  
  61.         double lamda1 = (covMat11 + covMat22 + sqrt((covMat11-covMat22)*(covMat11-covMat22) + 4*covMat12*covMat12)) / 2.0;  
  62.         nx = -covMat12;  
  63.         ny = lamda1 - covMat22;  
  64.         norm = sqrt(nx*nx + ny*ny);  
  65.         nx/=norm;  
  66.         ny/=norm;  
  67.     }  
  68.     parameters.push_back(nx);  
  69.     parameters.push_back(ny);  
  70.     parameters.push_back(meanX);  
  71.     parameters.push_back(meanY);  
  72. }  
  73.   
  74.   
  75. bool LineParamEstimator::agree(std::vector<double> &parameters, Point2D &data)  
  76. {  
  77.     double signedDistance = parameters[0]*(data.x-parameters[2]) + parameters[1]*(data.y-parameters[3]);   
  78.     return ((signedDistance*signedDistance) < m_deltaSquared);  
  79. }  


RANSAC尋找匹配的代碼如下: 
C代碼  收藏代碼
  1.   
  2. template<class T, class S>  
  3. double Ransac<T,S>::compute(std::vector<S> &parameters,   
  4.                                                       ParameterEsitmator<T,S> *paramEstimator ,   
  5.                                                     std::vector<T> &data,   
  6.                                                     int numForEstimate)  
  7. {  
  8.     std::vector<T *> leastSquaresEstimateData;  
  9.     int numDataObjects = data.size();  
  10.     int numVotesForBest = -1;  
  11.     int *arr = new int[numForEstimate];// numForEstimate表示擬合模型所需要的最少點數,對本例的直線來說,該值爲2  
  12.     short *curVotes = new short[numDataObjects];  //one if data[i] agrees with the current model, otherwise zero  
  13.     short *bestVotes = new short[numDataObjects];  //one if data[i] agrees with the best model, otherwise zero  
  14.       
  15.   
  16.               //there are less data objects than the minimum required for an exact fit  
  17.     if(numDataObjects < numForEstimate)   
  18.         return 0;  
  19.         // 計算所有可能的直線,尋找其中誤差最小的解。對於100點的直線擬合來說,大約需要100*99*0.5=4950次運算,複雜度無疑是龐大的。一般採用隨機選取子集的方式。  
  20.     computeAllChoices(paramEstimator,data,numForEstimate,  
  21.                                         bestVotes, curVotes, numVotesForBest, 0, data.size(), numForEstimate, 0, arr);  
  22.   
  23.        //compute the least squares estimate using the largest sub set  
  24.     for(int j=0; j<numDataObjects; j++) {  
  25.         if(bestVotes[j])  
  26.             leastSquaresEstimateData.push_back(&(data[j]));  
  27.     }  
  28.         // 對局內點再次用最小二乘法擬合出模型  
  29.     paramEstimator->leastSquaresEstimate(leastSquaresEstimateData,parameters);  
  30.   
  31.     delete [] arr;  
  32.     delete [] bestVotes;  
  33.     delete [] curVotes;   
  34.   
  35.     return (double)leastSquaresEstimateData.size()/(double)numDataObjects;  
  36. }  


在模型確定以及最大迭代次數允許的情況下,RANSAC總是能找到最優解。經過我的實驗,對於包含80%誤差的數據集,RANSAC的效果遠優於直接的最小二乘法。 

RANSAC可以用於哪些場景呢?最著名的莫過於圖片的拼接技術。優於鏡頭的限制,往往需要多張照片才能拍下那種巨幅的風景。在多幅圖像合成時,事先會在待合成的圖片中提取一些關鍵的特徵點。計算機視覺的研究表明,不同視角下物體往往可以通過一個透視矩(3X3或2X2)陣的變換而得到。RANSAC被用於擬合這個模型的參數(矩陣各行列的值),由此便可識別出不同照片中的同一物體。可參考下圖: 

RANSAC算法詳解

RANSAC算法詳解

RANSAC算法詳解

另外,RANSAC還可以用於圖像搜索時的糾錯與物體識別定位。下圖中,有幾條直線是SIFT匹配算法的誤判,RANSAC有效地將其識別,並將正確的模型(書本)用線框標註出來: 

RANSAC算法詳解  
6、參數
我們不得不根據特定的問題和數據集通過實驗來確定參數t和d。然而參數k(迭代次數)可以從理論結果推斷。當我們從估計模型參數時,用p表示一些迭代過程中從數據集內隨機選取出的點均爲局內點的概率;此時,結果模型很可能有用,因此p也表徵了算法產生有用結果的概率。用w表示每次從數據集中選取一個局內點的概率,如下式所示:
    w = 局內點的數目 / 數據集的數目
    通常情況下,我們事先並不知道w的值,但是可以給出一些魯棒的值。假設估計模型需要選定n個點,wn是所有n個點均爲局內點的概率;1 − wn是n個點中至少有一個點爲局外點的概率,此時表明我們從數據集中估計出了一個不好的模型。 (1 − wn)k表示算法永遠都不會選擇到n個點均爲局內點的概率,它和1-p相同。因此,
    1 − p = (1 − wn)k
    我們對上式的兩邊取對數,得出
    
    值得注意的是,這個結果假設n個點都是獨立選擇的;也就是說,某個點被選定之後,它可能會被後續的迭代過程重複選定到。這種方法通常都不合理,由此推導出的k值被看作是選取不重複點的上限。例如,要從上圖中的數據集尋找適合的直線,RANSAC算法通常在每次迭代時選取2個點,計算通過這兩點的直線maybe_model,要求這兩點必須唯一。
    爲了得到更可信的參數,標準偏差或它的乘積可以被加到k上。k的標準偏差定義爲:
    
7、優點與缺點
RANSAC的優點是它能魯棒的估計模型參數。例如,它能從包含大量局外點的數據集中估計出高精度的參數。RANSAC的缺點是它計算參數的迭代次數沒有上限;如果設置迭代次數的上限,得到的結果可能不是最優的結果,甚至可能得到錯誤的結果。RANSAC只有一定的概率得到可信的模型,概率與迭代次數成正比。RANSAC的另一個缺點是它要求設置跟問題相關的閥值。
    RANSAC只能從特定的數據集中估計出一個模型,如果存在兩個(或多個)模型,RANSAC不能找到別的模型。


發佈了37 篇原創文章 · 獲贊 20 · 訪問量 13萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章