1. 程式人生 > 實用技巧 >模板匹配加速——opencv

模板匹配加速——opencv

介紹

模板匹配是一個影象處理問題,當物件的姿勢(X、Y、+)未知時,它使用模板影象在另一個搜尋影象中查詢其位置。在這篇文章中,我們實現一個演算法,該演算法使用物件的邊緣資訊來識別搜尋影象中的物件。

背景

由於模板匹配的速度和可靠性問題,模板匹配本質上是一個棘手的問題。當物件部分可見或與其他物件混合時,該解決方案應針對亮度變化保持穩健,最重要的是,該演算法的計算效率應高。解決這個問題主要有兩種方法,基於灰值的匹配(或基於區域的匹配)和基於特徵的匹配(非基於區域的匹配)。

基於灰值的方法:在基於灰值的匹配中,規範化交叉關聯(NCC)演算法是從過去開始認識的。這通常通過減去均值和除以標準差來在每個步驟中完成。模板 t(x, y) 與子影象 f(x, y) 的交叉相關性是:

其中 n 是 t(x、y) 和 f(x, y) 中的畫素數。•維基]

儘管此方法針對線性照明變化是健壯的,但當物件部分可見或物件與其他物件混合時,演算法將失敗。此外,該演算法的計算成本很高,因為它需要計算模板影象中所有畫素與搜尋影象之間的相關性。

基於特徵的方法:在影象處理領域採用幾種基於特徵的模板匹配方法。與基於邊的物件識別一樣,物件邊緣是用於匹配的要素,在通用霍夫變換中,物件幾何特徵將用於匹配。

在這篇文章中,我們實現一個演算法,該演算法使用物件的邊緣資訊來識別搜尋影象中的物件。此實現使用開源計算機視覺庫作為平臺。

編譯示例程式碼

我們使用 OpenCV 2.0 和 Visual studio 2008 來開發此程式碼。要編譯示例程式碼,我們需要安裝 OpenCV。

OpenCV可以從這裡免費下載OpenCV(開源ComputerVision)是一個用於實時計算機視覺的程式設計函式庫。下載 OpenCV 並將其安裝到您的系統中。安裝資訊可從這裡讀取

我們需要配置我們的視覺化工作室環境。此資訊可在此處閱讀

演算法

在這裡,我們解釋一種基於邊緣的模板匹配技術。邊緣可以定義為數字影象中的點,其中影象亮度會急劇變化或具有不連續性。從技術上講,它是一種離散的分化操作,計算影象強度函式梯度的近似值。

邊緣檢測的方法有很多,但大多數方法可以分為兩類:基於搜尋和基於零交叉。基於搜尋的方法首先計算邊強度的度量,通常是一階導數表示式(如梯度幅度)來檢測邊緣,然後使用對邊緣的區域性方向(通常是梯度方向)的計算估計來搜尋梯度幅度的區域性方向最大值。在這裡,我們使用這樣的方法實現由索貝爾稱為索貝爾運算子。操作員計算每個點的影象

強度的漸變,給出從淺到暗的最大可能增加方向以及該方向的變化速率。

我們使用這些梯度或導數在X方向和Y方向進行匹配。

此演算法涉及兩個步驟。首先,我們需要建立模板影象的基於邊緣的模型,然後使用此模型在搜尋影象中搜索。

建立基於邊的模板模型

我們首先從模板影象的邊緣建立一個數據集或模板模型,用於在搜尋影象中查詢該物件的姿勢。

在這裡,我們使用 Canny 邊緣檢測方法的變體來查詢邊緣。你可以在這裡閱讀更多關於坎尼的邊緣檢測。對於邊緣提取,Canny 使用以下步驟:

第 1 步:查詢影象的強度漸變

在模板影象上使用 Sobel 篩選器,該篩選器返回 X (Gx) 和 Y (Gy) 方向的漸變。在此梯度中,我們將使用以下公式計算邊緣幅度和方向:

我們使用 OpenCV 函式來查詢這些值。

cvSobel( src, gx, 1,0, 3 ); //gradient in X direction
cvSobel( src, gy, 0, 1, 3 ); //gradient in Y direction

for( i = 1; i < Ssize.height-1; i++ )
{
    for( j = 1; j < Ssize.width-1; j++ )
    {          
        _sdx = (short*)(gx->data.ptr + gx->step*i);
        _sdy = (short*)(gy->data.ptr + gy->step*i);
        fdx = _sdx[j]; fdy = _sdy[j];
        // read x, y derivatives

        //Magnitude = Sqrt(gx^2 +gy^2)
        MagG = sqrt((float)(fdx*fdx) + (float)(fdy*fdy));
        //Direction = invtan (Gy / Gx)
        direction =cvFastArctan((float)fdy,(float)fdx);
        magMat[i][j] = MagG;
            
        if(MagG>MaxGradient)
            MaxGradient=MagG;
            // get maximum gradient value for normalizing.

            
        // get closest angle from 0, 45, 90, 135 set
        if ( (direction>0 && direction < 22.5) || 
              (direction >157.5 && direction < 202.5) || 
              (direction>337.5 && direction<360)  )
            direction = 0;
        else if ( (direction>22.5 && direction < 67.5) || 
                  (direction >202.5 && direction <247.5)  )
            direction = 45;
        else if ( (direction >67.5 && direction < 112.5)||
                  (direction>247.5 && direction<292.5) )
            direction = 90;
        else if ( (direction >112.5 && direction < 157.5)||
                  (direction>292.5 && direction<337.5) )
            direction = 135;
        else
            direction = 0;
            
        orients[count] = (int)direction;
        count++;
    }
}

找到邊緣方向後,下一步是關聯影象中可跟蹤的邊緣方向。描述周圍畫素有四種可能的方向:0 度、45 度、90 度和 135 度。我們指定所有方向到這些角度。

第 2 步:應用非最大抑制

找到邊緣方向後,我們將進行非最大抑制演算法。非最大抑制沿邊緣方向跟蹤左右畫素,如果當前畫素幅度小於左右畫素幅度,則禁止抑制。這將導致影象變薄。

for( i = 1; i < Ssize.height-1; i++ )
{
    for( j = 1; j < Ssize.width-1; j++ )
    {
        switch ( orients[count] )
        {
            case 0:
                leftPixel  = magMat[i][j-1];
                rightPixel = magMat[i][j+1];
                break;
            case 45:
                leftPixel  = magMat[i-1][j+1];
                rightPixel = magMat[i+1][j-1];
                break;
            case 90:
                leftPixel  = magMat[i-1][j];
                rightPixel = magMat[i+1][j];
                break;
            case 135:
                leftPixel  = magMat[i-1][j-1];
                rightPixel = magMat[i+1][j+1];
                break;
        }
        // compare current pixels value with adjacent pixels
        if (( magMat[i][j] < leftPixel ) || (magMat[i][j] < rightPixel ) )
            (nmsEdges->data.ptr + nmsEdges->step*i)[j]=0;
        Else
            (nmsEdges->data.ptr + nmsEdges->step*i)[j]=
                                 (uchar)(magMat[i][j]/MaxGradient*255);
        count++;
    }
}

第 3 步:執行滯後閾值

使用滯後設定閾值需要兩個閾值:高和低。我們應用一個高閾值來標出那些邊緣, 我們可以相當肯定是真實的。從這些開始,使用之前派生的方向資訊,其他邊緣可以通過影象進行跟蹤。在跟蹤邊時,我們應用較低的閾值,允許我們跟蹤邊緣的微弱部分,只要我們找到一個起點。

_sdx = (short*)(gx->data.ptr + gx->step*i);
_sdy = (short*)(gy->data.ptr + gy->step*i);
fdx = _sdx[j]; fdy = _sdy[j];
    
MagG = sqrt(fdx*fdx + fdy*fdy); //Magnitude = Sqrt(gx^2 +gy^2)
DirG =cvFastArctan((float)fdy,(float)fdx);     //Direction = tan(y/x)

////((uchar*)(imgGDir->imageData + imgGDir->widthStep*i))[j]= MagG;
flag=1;
if(((double)((nmsEdges->data.ptr + nmsEdges->step*i))[j]) < maxContrast)
{
    if(((double)((nmsEdges->data.ptr + nmsEdges->step*i))[j])< minContrast)
    {
        (nmsEdges->data.ptr + nmsEdges->step*i)[j]=0;
        flag=0; // remove from edge
        ////((uchar*)(imgGDir->imageData + imgGDir->widthStep*i))[j]=0;
    }
    else
    {   // if any of 8 neighboring pixel is not greater than max contraxt remove from edge
        if( (((double)((nmsEdges->data.ptr + nmsEdges->step*(i-1)))[j-1]) < maxContrast) &&
            (((double)((nmsEdges->data.ptr + nmsEdges->step*(i-1)))[j]) < maxContrast)   &&
            (((double)((nmsEdges->data.ptr + nmsEdges->step*(i-1)))[j+1]) < maxContrast) &&
            (((double)((nmsEdges->data.ptr + nmsEdges->step*i))[j-1]) < maxContrast)     &&
            (((double)((nmsEdges->data.ptr + nmsEdges->step*i))[j+1]) < maxContrast)     &&
            (((double)((nmsEdges->data.ptr + nmsEdges->step*(i+1)))[j-1]) < maxContrast) &&
            (((double)((nmsEdges->data.ptr + nmsEdges->step*(i+1)))[j]) < maxContrast)   &&
            (((double)((nmsEdges->data.ptr + nmsEdges->step*(i+1)))[j+1]) < maxContrast))
        {
            (nmsEdges->data.ptr + nmsEdges->step*i)[j]=0;
            flag=0;
            ////((uchar*)(imgGDir->imageData + imgGDir->widthStep*i))[j]=0;
        }
    }
}

第 4 步:儲存資料集

提取邊後,我們將所選邊的 X 和 Y 導數以及座標資訊作為模板模型。這些座標將被重新排列,以反映起點作為重心。

查詢基於邊的模板模型

演算法中的下一個任務是使用模板模型在搜尋影象中查詢物件。我們可以看到我們從包含一組點的模板影象建立的模型:,及其在 X 和 Y 方向的漸變,其中 i= 1 ...n,n是模板 (T) 資料集中的元素數。

我們還可以在搜尋影象 (S) 中找到漸變,其中 u = 1...搜尋影象中的列數。

在匹配過程中,應使用相似性度量度將模板模型與所有位置的搜尋影象進行比較。相似性度量背後的理念是採取模板影象的梯度向量的所有規範化點乘量的總和,並在模型資料集的所有點上搜索影象。這將導致搜尋影象中每個點的分數。這可表述如下:

如果模板模型和搜尋影象之間完全匹配,則此函式將返回分數 1。分數對應於搜尋影象中可見的物件部分。如果搜尋影象中不存在物件,則分數將為 0。

cvSobel( src, Sdx, 1, 0, 3 );  // find X derivatives
cvSobel( src, Sdy, 0, 1, 3 ); // find Y derivatives
for( i = 0; i < Ssize.height; i++ )
{
    for( j = 0; j < Ssize.width; j++ )
    { 
        partialSum = 0; // initilize partialSum measure
        for(m=0;m<noOfCordinates;m++)
        {
            curX    = i + cordinates[m].x ;    // template X coordinate
            curY    = j + cordinates[m].y ; // template Y coordinate
            iTx    = edgeDerivativeX[m];    // template X derivative
            iTy    = edgeDerivativeY[m];    // template Y derivative

            if(curX<0 ||curY<0||curX>Ssize.height-1 ||curY>Ssize.width-1)
                continue;
                     
            _Sdx = (short*)(Sdx->data.ptr + Sdx->step*(curX));
            _Sdy = (short*)(Sdy->data.ptr + Sdy->step*(curX));
                        
            iSx=_Sdx[curY]; // get curresponding  X derivative from source image
            iSy=_Sdy[curY];// get curresponding  Y derivative from source image
                        
            if((iSx!=0 || iSy!=0) && (iTx!=0 || iTy!=0))
            {
                //partial Sum  = Sum of(((Source X derivative* Template X drivative)
                //+ Source Y derivative * Template Y derivative)) / Edge
                //magnitude of(Template)* edge magnitude of(Source))
                partialSum = partialSum + ((iSx*iTx)+(iSy*iTy))*
                            (edgeMagnitude[m] * matGradMag[curX][curY]);
                                    
            }

在實際情況下,我們需要加快搜索過程。這可以通過各種方法實現。第一個方法是使用平均的屬性。找到相似性度量時,如果我們可以為相似性度量值設定最低分數 (Smin),則無需評估模板模型中的所有點。要檢查特定點 J 的部分分數 Su,v,我們必須找到點 m. Sm 的部分總和,可以定義如下:

顯然,該金額的剩餘條款較小或等於 1。因此,如果 ,我們可以停止評估。

另一個標準可能是,任何時間點的部分分數應大於最低分數。即.使用此條件時,匹配速度將非常快。但問題是,如果首先檢查物件的缺失部分,則部分總和將很低。在這種情況下,該物件的例項將不被視為匹配項。我們可以用另一個條件修改這一點,其中我們檢查模板模型的第一部分與安全停止標準,其餘與硬條件 ,。使用者可以指定貪婪引數 (g),其中使用硬條件檢查模板模型的分數。因此,如果 g=1,模板模型中的所有點都使用硬條件進行檢查,如果 g=0,則所有點將僅使用安全條件進行檢查。我們可以制定如下程式。

部分分數的評估可以停止在以下:

// stoping criterias to search for model
double normMinScore = minScore /noOfCordinates; // precompute minumum score 
double normGreediness = ((1- greediness * minScore)/(1-greediness)) /noOfCordinates;
// precompute greedniness

sumOfCoords = m + 1;
partialScore = partialSum /sumOfCoords ;
// check termination criteria
// if partial score score is less than the score than
// needed to make the required score at that position
// break serching at that coordinate.
if( partialScore < (MIN((minScore -1) + 
        normGreediness*sumOfCoords,normMinScore*  sumOfCoords)))
    break;

這種相似性度量具有以下幾個優點:相似性度量與非線性照明變化是不變的,因為所有梯度向量都已規範化。由於邊緣濾波上沒有分段,因此將顯示對照明任意變化的真不變性。更重要的是,當物件部分可見或與其他物件混合時,這種相似性度量是健壯的。

增強

此演算法可能提供各種增強功能。為了進一步加快搜索過程,可以使用金字塔式方法。在這種情況下,搜尋以小影象大小的低解析度開始。這對應於金字塔的頂部。如果搜尋在此階段成功,則搜尋將繼續在金字塔的下一個級別,該級別表示更高解析度的影象。以這種方式,搜尋繼續,因此,結果被優化,直到原始影象大小,即,到達金字塔的底部。

另一個增強是可能的,通過擴充套件旋轉和縮放演算法。這可以通過建立用於旋轉和縮放的模板模型以及使用所有這些模板模型執行搜尋來完成。

引用

  1. 機器視覺演算法和應用 [卡斯滕·斯特格、馬庫斯·烏爾裡希、克里斯蒂安·維德曼]
  2. 數字影象處理 [拉斐爾·岡薩雷斯,理查德·尤金·伍茲]
  3. http://dasl.mem.drexel.edu/alumni/bGreen/www.pages.drexel.edu/_weg22/can_tut.html
  4. https://www.codeproject.com/articles/99457/edge-based-template-matching