Harris角點檢測原理及C++實現__C++

來源:互聯網
上載者:User

1. 首先,我們不禁要問什麼是harris角點。

       對於角點,到目前為止還沒有明確的數學定義。但是你可以認為角點就是極值點,即在某方面屬性特別突出的點。一般的角點檢測都是對有具體定義的、或者是能夠具體檢測出來的興趣點的檢測。這意味著興趣點可以是角點,是在某些屬性上強度最大或者最小的孤立點、線段的終點,或者是曲線上局部曲率最大的點。

       通俗的來說,在一副映像中,我們可以認為角點是物體輪廓線的連接點(見圖1),當拍攝視角變化的時候,這些特徵點仍能很好地保持穩定的屬性。

                                                 

                                                                 圖1  corner

       角點在保留映像圖形重要特徵的同時,可以有效地減少資訊的資料量,使其資訊的含量很高,有效地提高了計算的速度,有利於映像的可靠匹配,使得即時處理成為可能。它的各種應用,這裡我就不再贅述了。

2. 如何檢測出harris角點。

                                 

                                                         圖2  角點檢測的基本思想

       角點檢測最原始的想法就是取某個像素的一個鄰域視窗,當這個視窗在各個方向上進行小範圍移動時,觀察視窗內平均的像素灰階值的變化(即,E(u,v),Window-averaged change of intensity)。從上圖可知,我們可以將一幅映像大致分為三個地區(‘flat’,‘edge’,‘corner’),這三個地區變化是不一樣的。

                       

      其中,u,v是視窗在水平,豎直方向的位移,

                     

      這裡可以先簡單複習一下泰勒級數展開的知識,因為馬上就用到啦,


            

這是一維的情況,對於多元函數,也有類似的泰勒公式。

       對I(x+u,y+v)進行二維泰勒級數展開,我們取一階近似,有

                                    

        圖中藍線圈出的部分我們稱之為結構張量(structure tensor),用M表示。

        講到這裡,先明確一點,我們的目的是什麼。我們的目的是尋找這樣的像素點,它使得我們的u,v無論怎樣取值,E(u,v)都是變化比較大的,這個像素點就是我們要找的角點。不難觀察,上式近似處理後的E(u,v)是一個二次型,而由下述定理可知,

                            

        令E(u,v)=常數,我們可用一個橢圓來描繪這一函數。

                                                

         橢圓的長短軸是與結構張量M的兩個特徵值相對應的量。通過判斷的情況我們就可以區分出‘flat’,‘edge’,‘corner’這三種地區,因為最直觀的印象:

corner:在水平、豎直兩個方向上變化均較大的點,即Ix、Iy都較大; 
 edge :僅在水平、或者僅在豎直方向有較大的點,即Ix和Iy只有其一較大 ;
  flat   : 在水平、豎直方向的變化量均較小的點,即Ix、Iy都較小;

       而結構張量M是由Ix,Iy構成,它的特徵值正好可以反映Ix,Iy的情況,下面我以一種更容易理解的方式來講述橢圓的物理意義。

                          

                         

                         

         這樣是不是更清楚了呢^_^......,因此我們可以得出結論:

                        

         當然,大牛們並沒有止步於此,這樣通過判斷兩個變數的值來判斷角點畢竟不是很方便。於是他們想出了一種更好的方法,對,就是定義角點響應函數R(corner response function),

                                            

      針對三種地區,R的取值如何呢。

                        

            至此,我們就可以通過判斷R的值來判斷某個點是不是角點了。

角點:R為大數值整數

邊緣:R為大數值負數

平坦區:絕對值R是小數值

3. harris角點檢測演算法步驟

  1.利用Soble計算出XY方向的梯度值

  2.計算出Ix^2,Iy^2,Ix*Iy

  3.利用高斯函數對Ix^2,Iy^2,Ix*Iy進行濾波

  4.計算局部特徵結果矩陣M的特徵值和響應函數C(i,j)=Det(M)-k(trace(M))^2   (0.04<=k<=0.06)

  5.將計算出響應函數的值C進行非極大值抑制,濾除一些不是角點的點,同時要滿足大於設定的閾值


下面放上原始碼:

#include "opencv2/imgproc/imgproc.hpp"  #include "opencv2/highgui/highgui.hpp"  #include <iostream>  #include <cmath>using namespace cv;using namespace std;/*RGB轉換成灰階映像的一個常用公式是:Gray = R*0.299 + G*0.587 + B*0.114*///******************灰階轉換函式*************************  //第一個參數image輸入的彩色RGB映像的引用;  //第二個參數imageGray是轉換後輸出的灰階映像的引用;  //*******************************************************void ConvertRGB2GRAY(const Mat &image, Mat &imageGray);//******************Sobel卷積因子計算X、Y方向梯度和梯度方向角********************  //第一個參數imageSourc原始灰階映像;  //第二個參數imageSobelX是X方向梯度映像;  //第三個參數imageSobelY是Y方向梯度映像;  //第四個參數pointDrection是梯度方向角數組指標  //*************************************************************  void SobelGradDirction(Mat &imageSource, Mat &imageSobelX, Mat &imageSobelY);//******************計算Sobel的X方向梯度幅值的平方*************************  //第一個參數imageGradX是X方向梯度映像;    //第二個參數SobelAmpXX是輸出的X方向梯度映像的平方  //*************************************************************  void SobelXX(const Mat imageGradX, Mat_<float> &SobelAmpXX);//******************計算Sobel的Y方向梯度幅值的平方*************************    //第一個參數imageGradY是Y方向梯度映像;  //第二個參數SobelAmpXX是輸出的Y方向梯度映像的平方  //*************************************************************  void SobelYY(const Mat imageGradY, Mat_<float> &SobelAmpYY);//******************計算Sobel的XY方向梯度幅值的乘積*************************    //第一個參數imageGradX是X方向梯度映像;//第二個參數imageGradY是Y方向梯度映像;//第二個參數SobelAmpXY是輸出的XY方向梯度映像 //*************************************************************  void SobelXY(const Mat imageGradX, const Mat imageGradY, Mat_<float> &SobelAmpXY);//****************計算一維高斯的權值數組*****************//第一個參數size是代表的卷積核的邊長的大小//第二個參數sigma表示的是sigma的大小//*******************************************************double *getOneGuassionArray(int size, double sigma);//****************高斯濾波函數的實現*****************//第一個參數srcImage是代表的輸入的原圖//第二個參數dst表示的是輸出的圖//第三個參數size表示的是卷積核的邊長的大小//*******************************************************void MyGaussianBlur(Mat_<float> &srcImage, Mat_<float> &dst, int size);//****計算局部特漲結果矩陣M的特徵值和響應函數H = (A*B - C) - k*(A+B)^2******//M//A  C//C  B//Tr(M)=a+b=A+B//Det(M)=a*b=A*B-C^2//計算輸出響應函數的值得矩陣//****************************************************************************void harrisResponse(Mat_<float> &GaussXX, Mat_<float> &GaussYY, Mat_<float> &GaussXY, Mat_<float> &resultData,float k);//***********非極大值抑制和滿足閾值及某鄰域內的局部極大值為角點**************//第一個參數是響應函數的矩陣//第二個參數是輸入的灰階映像//第三個參數表示的是輸出的角點檢測到的結果圖void LocalMaxValue(Mat_<float> &resultData, Mat &srcGray, Mat &ResultImage,int kSize);int main(){const Mat srcImage = imread("3.jpg");if (!srcImage.data){printf("could not load image...\n");return -1;}imshow("srcImage", srcImage);Mat srcGray;ConvertRGB2GRAY(srcImage, srcGray);Mat imageSobelX;Mat imageSobelY;Mat resultImage;Mat_<float> imageSobelXX;Mat_<float> imageSobelYY;Mat_<float> imageSobelXY;Mat_<float> GaussianXX;Mat_<float> GaussianYY;Mat_<float> GaussianXY;Mat_<float> HarrisRespond;//計算Soble的XY梯度SobelGradDirction(srcGray, imageSobelX, imageSobelY);//計算X方向的梯度的平方SobelXX(imageSobelX, imageSobelXX);SobelYY(imageSobelY, imageSobelYY);SobelXY(imageSobelX, imageSobelY, imageSobelXY);//計算高斯模糊XX YY XYMyGaussianBlur(imageSobelXX, GaussianXX,3);MyGaussianBlur(imageSobelYY, GaussianYY, 3);MyGaussianBlur(imageSobelXY, GaussianXY, 3);harrisResponse(GaussianXX, GaussianYY, GaussianXY, HarrisRespond, 0.05);LocalMaxValue(HarrisRespond, srcGray, resultImage, 3);imshow("imageSobelX", imageSobelX);imshow("imageSobelY", imageSobelY);imshow("resultImage", resultImage);waitKey(0);return 0;}void ConvertRGB2GRAY(const Mat &image, Mat &imageGray){if (!image.data || image.channels() != 3){return;}//建立一張單通道的灰階映像imageGray = Mat::zeros(image.size(), CV_8UC1);//取出儲存映像像素的數組的指標uchar *pointImage = image.data;uchar *pointImageGray = imageGray.data;//取出映像每行所佔的位元組數size_t stepImage = image.step;size_t stepImageGray = imageGray.step;for (int i = 0; i < imageGray.rows; i++){for (int j = 0; j < imageGray.cols; j++){pointImageGray[i*stepImageGray + j] = (uchar)(0.114*pointImage[i*stepImage + 3 * j] + 0.587*pointImage[i*stepImage + 3 * j + 1] + 0.299*pointImage[i*stepImage + 3 * j + 2]);}}}//儲存梯度膜長void SobelGradDirction(Mat &imageSource, Mat &imageSobelX, Mat &imageSobelY){imageSobelX = Mat::zeros(imageSource.size(), CV_32SC1);imageSobelY = Mat::zeros(imageSource.size(), CV_32SC1);//取出原圖和X和Y梯度圖的數組的首地址uchar *P = imageSource.data;uchar *PX = imageSobelX.data;uchar *PY = imageSobelY.data;//取出每行所佔據的位元組數int step = imageSource.step;int stepXY = imageSobelX.step;int index = 0;//梯度方向角的索引for (int i = 1; i < imageSource.rows - 1; ++i){for (int j = 1; j < imageSource.cols - 1; ++j){//通過指標遍曆映像上每一個像素   double gradY = P[(i + 1)*step + j - 1] + P[(i + 1)*step + j] * 2 + P[(i + 1)*step + j + 1] - P[(i - 1)*step + j - 1] - P[(i - 1)*step + j] * 2 - P[(i - 1)*step + j + 1];PY[i*stepXY + j*(stepXY / step)] = abs(gradY);double gradX = P[(i - 1)*step + j + 1] + P[i*step + j + 1] * 2 + P[(i + 1)*step + j + 1] - P[(i - 1)*step + j - 1] - P[i*step + j - 1] * 2 - P[(i + 1)*step + j - 1];PX[i*stepXY + j*(stepXY / step)] = abs(gradX);}}//將梯度數群組轉換成8位無符號整型convertScaleAbs(imageSobelX, imageSobelX);convertScaleAbs(imageSobelY, imageSobelY);}void SobelXX(const Mat imageGradX, Mat_<float> &SobelAmpXX){SobelAmpXX = Mat_<float>(imageGradX.size(), CV_32FC1);for (int i = 0; i < SobelAmpXX.rows; i++){for (int j = 0; j < SobelAmpXX.cols; j++){SobelAmpXX.at<float>(i, j) = imageGradX.at<uchar>(i, j)*imageGradX.at<uchar>(i, j);}}//convertScaleAbs(SobelAmpXX, SobelAmpXX);}void SobelYY(const Mat imageGradY, Mat_<float> &SobelAmpYY){SobelAmpYY = Mat_<float>(imageGradY.size(), CV_32FC1);for (int i = 0; i < SobelAmpYY.rows; i++){for (int j = 0; j < SobelAmpYY.cols; j++){SobelAmpYY.at<float>(i, j) = imageGradY.at<uchar>(i, j)*imageGradY.at<uchar>(i, j);}}//convertScaleAbs(SobelAmpYY, SobelAmpYY);}void SobelXY(const Mat imageGradX, const Mat imageGradY, Mat_<float> &SobelAmpXY){SobelAmpXY = Mat_<float>(imageGradX.size(), CV_32FC1);for (int i = 0; i < SobelAmpXY.rows; i++){for (int j = 0; j < SobelAmpXY.cols; j++){SobelAmpXY.at<float>(i, j) = imageGradX.at<uchar>(i, j)*imageGradY.at<uchar>(i, j);}}//convertScaleAbs(SobelAmpXY, SobelAmpXY);}//計算一維高斯的權值數組double *getOneGuassionArray(int size, double sigma){double sum = 0.0;//定義高斯核半徑int kerR = size / 2;//建立一個size大小的動態一維數組double *arr = new double[size];for (int i = 0; i < size; i++){// 高斯函數前的常數可以不用計算,會在歸一化的過程中給消去arr[i] = exp(-((i - kerR)*(i - kerR)) / (2 * sigma*sigma));sum += arr[i];//將所有的值進行相加}//進行歸一化for (int i = 0; i < size; i++){arr[i] /= sum;cout << arr[i] << endl;}return arr;}void MyGaussianBlur(Mat_<float> &srcImage, Mat_<float> &dst, int size){CV_Assert(srcImage.channels() == 1 || srcImage.channels() == 3); // 只處理單通道或者三通道映像int kerR = size / 2;dst = srcImage.clone();int channels = dst.channels();double* arr;arr = getOneGuassionArray(size, 1);//先求出高斯數組   //遍曆映像 水平方向的卷積for (int i = kerR; i < dst.rows - kerR; i++){for (int j = kerR; j < dst.cols - kerR; j++){float GuassionSum[3] = { 0 };//滑窗搜尋完成高斯核平滑for (int k = -kerR; k <= kerR; k++){if (channels == 1)//如果只是單通道{GuassionSum[0] += arr[kerR + k] * dst.at<float>(i, j + k);//行不變,列變換,先做水平方向的卷積}else if (channels == 3)//如果是三通道的情況{Vec3f bgr = dst.at<Vec3f>(i, j + k);auto a = arr[kerR + k];GuassionSum[0] += a*bgr[0];GuassionSum[1] += a*bgr[1];GuassionSum[2] += a*bgr[2];}}for (int k = 0; k < channels; k++){if (GuassionSum[k] < 0)GuassionSum[k] = 0;else if (GuassionSum[k] > 255)GuassionSum[k] = 255;}if (channels == 1)dst.at<float>(i, j) = static_cast<float>(GuassionSum[0]);else if (channels == 3){Vec3f bgr = { static_cast<float>(GuassionSum[0]), static_cast<float>(GuassionSum[1]), static_cast<float>(GuassionSum[2]) };dst.at<Vec3f>(i, j) = bgr;}}}//豎直方向for (int i = kerR; i < dst.rows - kerR; i++){for (int j = kerR; j < dst.cols - kerR; j++){float GuassionSum[3] = { 0 };//滑窗搜尋完成高斯核平滑for (int k = -kerR; k <= kerR; k++){if (channels == 1)//如果只是單通道{GuassionSum[0] += arr[kerR + k] * dst.at<float>(i + k, j);//行變,列不換,再做豎直方向的卷積}else if (channels == 3)//如果是三通道的情況{Vec3f bgr = dst.at<Vec3f>(i + k, j);auto a = arr[kerR + k];GuassionSum[0] += a*bgr[0];GuassionSum[1] += a*bgr[1];GuassionSum[2] += a*bgr[2];}}for (int k = 0; k < channels; k++){if (GuassionSum[k] < 0)GuassionSum[k] = 0;else if (GuassionSum[k] > 255)GuassionSum[k] = 255;}if (channels == 1)dst.at<float>(i, j) = static_cast<float>(GuassionSum[0]);else if (channels == 3){Vec3f bgr = { static_cast<float>(GuassionSum[0]), static_cast<float>(GuassionSum[1]), static_cast<float>(GuassionSum[2]) };dst.at<Vec3f>(i, j) = bgr;}}}delete[] arr;}void harrisResponse(Mat_<float> &GaussXX, Mat_<float> &GaussYY, Mat_<float> &GaussXY, Mat_<float> &resultData,float k){//建立一張響應函數輸出的矩陣resultD

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.