OpenCV角點檢測goodFeaturesToTrack()源代碼分析


  上面一篇博客分析了HARRIS和ShiTomasi角點檢測的源代碼。而為了提取更准確的角點,OpenCV中提供了goodFeaturesToTrack()這個API函數,來獲取更加准確的角點位置。這篇博客主要分析goodFeaturesToTrack()的源代碼。

  函數原型如下:

void cv::goodFeaturesToTrack( InputArray _image, OutputArray _corners,
                              int maxCorners, double qualityLevel, double minDistance,
                              InputArray _mask, int blockSize, int gradientSize,
                              bool useHarrisDetector, double harrisK )

  _image為輸入的單通道圖像;_corners為輸出提取的角點坐標;maxCorners為設置的最大角點個數,程序中會按照角點強度降序排序,超過maxCorners的角點將被舍棄;qualityLevel為角點強度的閾值系數,如果檢測出所有角點中的最大強度為max,則強度值<max*qualityLevel得角點會被舍棄;minDistance為角點與其鄰域強角點之間的歐式距離,與鄰域強角點距離小於minDistance的角點將被舍棄;_mask為設定的感興趣區域,通常可不設置;blockSize是協方差矩陣濾波的窗口大小;gradientSize為sobel算子求微分的窗口的大小;useHarrisDetector為是否使用Harris角點檢測;harrisK為Harris角點檢測特征表達式中的常數k值。

  接下來看源代碼,首先根據useHarrisDetector這個flg值的設定選取不同的方法提取初始角點:

   //提取初始角點
  if
( useHarrisDetector ) cornerHarris( image, eig, blockSize, gradientSize, harrisK ); else cornerMinEigenVal( image, eig, blockSize, gradientSize );

  然后選取出所有角點特征值的最大值maxVal,並進行閾值化,將小於maxVal*qulityLevel的特征值舍棄掉(置為0),其余保持不變。並用3x3的方形膨脹核對特征值矩陣進行膨脹操作,膨脹的目的是使孤立的局部最大值擴大化,膨脹過后3x3鄰域的非局部最大值點被局部最大值取代。膨脹過后的特征值圖像為tmp,后面會用到。

    //獲取最大角點特征值
    double maxVal = 0;
    minMaxLoc( eig, 0, &maxVal, 0, 0, _mask );
    threshold( eig, eig, maxVal*qualityLevel, 0, THRESH_TOZERO );
    dilate( eig, tmp, Mat());

  取出局部最大值點,並將其地址保存到tmpCorners這個容器中。(val==tem_data[x]表示該點未被膨脹操作影響,就是局部極大值點)

// collect list of pointers to features - put them into temporary image
    Mat mask = _mask.getMat();
    for( int y = 1; y < imgsize.height - 1; y++ )
    {
        const float* eig_data = (const float*)eig.ptr(y);
        const float* tmp_data = (const float*)tmp.ptr(y);
        const uchar* mask_data = mask.data ? mask.ptr(y) : 0;

        for( int x = 1; x < imgsize.width - 1; x++ )
        {
            float val = eig_data[x];
            if( val != 0 && val == tmp_data[x] && (!mask_data || mask_data[x]) )
                tmpCorners.push_back(eig_data + x);
        }
    }

  然后將tmpCorners中指針指向的值進行降序排列,(其實是只改變指針指向的位置):

 std::sort( tmpCorners.begin(), tmpCorners.end(), greaterThanPtr() ); 

  接下來是不太好理解的地方,首先進行minDistance的判斷。如果minDistance<1,也就是不進行minDistance的條件限制,直接保存前maxCorners個角點並返回;如果minDistance>=1,則執行if{}中的代碼段。

  首先弄清楚grid這個是什么東西,grid容器中元素個數是圖像像素點個數的1/(cell_size*cell_size)倍,但是它是一個vector<vector<Point2f>>,也就是指grid中的每一個元素是一個vetcor<Point2f>(意思就是一個元素對應n個2維坐標點)。這個理解起來就是grid中相差一個像素,相當於原圖像中相差minDistance個像素。原圖像中以當前像素為中心的minDistance范圍內的點在grid中其實視為一個像素的3x3鄰域(一對多的關系)。

  接下來的分析見注釋:

if (minDistance >= 1)
    {
         // Partition the image into larger grids
        int w = image.cols;
        int h = image.rows;

        const int cell_size = cvRound(minDistance);
    
     //grid中元素(vector)個數為grid_width*grid_height個,+cell_size-1的目的是保證能夠覆蓋所有的像素點
const int grid_width = (w + cell_size - 1) / cell_size; const int grid_height = (h + cell_size - 1) / cell_size; std::vector<std::vector<Point2f> > grid(grid_width*grid_height); minDistance *= minDistance; for( i = 0; i < total; i++ ) { int ofs = (int)((const uchar*)tmpCorners[i] - eig.ptr()); int y = (int)(ofs / eig.step); int x = (int)((ofs - y*eig.step)/sizeof(float)); //假設開始所有的角點是good bool good = true; //將原圖中角點坐標歸一化到mindistance鄰域 int x_cell = x / cell_size; int y_cell = y / cell_size; //取歸一化后點的鄰域4個點坐標(不是4鄰域,而是4個角),在grid中的坐標 int x1 = x_cell - 1; int y1 = y_cell - 1; int x2 = x_cell + 1; int y2 = y_cell + 1; // boundary check 邊界判斷 x1 = std::max(0, x1); y1 = std::max(0, y1); x2 = std::min(grid_width-1, x2); y2 = std::min(grid_height-1, y2); //遍歷鄰域4個點, grid中的鄰域坐標 for( int yy = y1; yy <= y2; yy++ ) { for( int xx = x1; xx <= x2; xx++ ) {
//取出grid中一個元素對應的所有強角點坐標位置 std::vector
<Point2f> &m = grid[yy*grid_width + xx]; //如果某元素對應的原圖像角點容器中有已經保存的強角點,則需要進行距離判斷。否則指定原圖像中該角點就是強角點 if( m.size() ) {
//遍歷其對應容器內的其他強角點,並依次判斷原圖像中當前角點與其鄰域內其他強角點之間的歐式距離,如果歐式距離小於minDistance,則將當前角點標志置為good=false(拋棄),並跳出
for(j = 0; j < m.size(); j++) { float dx = x - m[j].x; float dy = y - m[j].y; if( dx*dx + dy*dy < minDistance ) { good = false; goto break_out; } } } } } break_out:        //如果角點為good,則將該角點保存在當前grid中一個元素對應的容器中,同時保存在輸出容器corners中,並累加計數器ncorners。由於已經進行過降序排序,前面保存的都是強角點。 if (good) { grid[y_cell*grid_width + x_cell].push_back(Point2f((float)x, (float)y)); corners.push_back(Point2f((float)x, (float)y)); ++ncorners; if( maxCorners > 0 && (int)ncorners == maxCorners ) break; } } } else { for( i = 0; i < total; i++ ) { int ofs = (int)((const uchar*)tmpCorners[i] - eig.ptr()); int y = (int)(ofs / eig.step); int x = (int)((ofs - y*eig.step)/sizeof(float)); corners.push_back(Point2f((float)x, (float)y)); ++ncorners; if( maxCorners > 0 && (int)ncorners == maxCorners ) break; } } Mat(corners).convertTo(_corners, _corners.fixedType() ? _corners.type() : CV_32F);

  結束!

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM