概述
除了之前文章所說的利用Harris進行角點檢測和利用Shi-Tomasi方法進行角點檢測外,也可以自己製作角點檢測的函數:使用cornerEigenValsAndVecs()函數和minMaxLoc()函數結合來模擬Harris角點檢測,或者使用cornerMinEigenVal()函數和minMaxLoc()函數結合來模擬Shi-Tomasi角點檢測,最後特徵點選取的判斷條件要根據實際情況進行選擇。
cornerEigenValsAndVecs()函數
(1)函數原型
cornerEigenValsAndVecs()函數在角點檢測中計算掃描圖像塊的特徵向量與特徵值,其函數原型如下:
C++: void cornerEigenValsAndVecs(InputArray src, OutputArray dst, int blockSize, int ksize, int borderType=BORDER_DEFAULT );
C: void cvCornerEigenValsAndVecs(const CvArr* image, CvArr* eigenvv, int block_size, int aperture_size=3 );
(2)函數參數
函數參數說明如下:
src:輸入單通道8-bit或者浮點類型圖像。
dst:用來存儲結果的圖像,大小與輸入圖像一致並且爲CV_32FC(6)類型。
blockSize:鄰域大小。
ksize:Sobel算子當中的核大小,只能取1、3、5、7(具體可參考這裏:Sobel())。
borderType:像素擴展的方法,因爲在濾波處理的過程中會擴展圖像邊緣,每擴張一個邊界像素,都需要計算出該像素點在原圖中的位置,這個功能被提煉出來就變成了borderInterpolate()函數。該函數輸入一個點座標,返回他在原圖中的座標,關於這個函數的詳細解釋,參考:
1、在OpenCV中圖像邊界擴展 copyMakeBorder 的實現_人人IT網
2、borderInterpolate解釋_Halley_新浪博客
(3)函數功能
對於每個像素點p,該函數考慮一個blockSize*blockSize的鄰域S(p),它在鄰域上計算導數的協方差矩陣:
其中導數是使用Sobel()算子計算得到的。
隨後,函數計算矩陣M的特徵值和特徵向量,並將它們以(λ1, λ2, x1, y1, x2,y2)的形式存儲在目標圖像dst中。其中λ1, λ2是M未經過排序的特徵值;x1, y1是對應於λ1的特徵向量;x2, y2是對應於λ2的特徵向量。該函數的輸出能夠用於魯棒的邊緣或角點檢測。
cornerMinEigenVal()函數
(1)函數原型
cornerMinEigenVal()函數在角點檢測中計算梯度矩陣的最小特徵值,其函數原型如下:
C++: void cornerMinEigenVal(InputArray src, OutputArray dst, int blockSize, int ksize=3, int borderType=BORDER_DEFAULT );
C: void cvCornerMinEigenVal(const CvArr* image, CvArr* eigenval, int block_size, int aperture_size=3 );
(2)函數參數
函數參數說明中除了dst必須爲CV_32FC1類型以外,其它與cornerEigenValsAndVecs()函數的一致。
(3)函數功能
功能與cornerEigenValsAndVecs()函數相似,但是它只計算導數協方差矩陣的最小特徵值,按照cornerEigenValsAndVecs()函數給定的特徵值λ1, λ2來說就是min(λ1, λ2)。
代碼示例
(1)採用cornerEigenValsAndVecs()函數和minMaxLoc()函數結合來模擬Harris角點檢測的代碼示例如下:
/**
* @使用cornerEigenValsAndVecs()函數模擬Harris角點檢測
* @author holybin
*/
#include "opencv2/highgui/highgui.hpp"
#include "opencv2/imgproc/imgproc.hpp"
#include <iostream>
#include <stdio.h>
#include <stdlib.h>
using namespace cv;
using namespace std;
/// 全局變量
Mat src;
Mat srcGray, srcCopy; //srcCopy用於繪圖,srcGray用於檢測角點
Mat dstHarris; //dstHarris用於存儲角點檢測的結果
Mat resHarris; //resHarris用於存儲特徵點選擇後的結果
/// 各類閾值
int HarrisQualityLevel = 50;
int maxQualityLevel = 100;
double HarrisMinVal = 0.0;
double HarrisMaxVal = 0.0;
char* HarrisWindow = "My Harris corner detector";
/// 角點檢測函數聲明
void HarrisFunction( int, void* );
int main( int argc, char** argv )
{
/// 載入圖像並灰度化
src = imread("D:\\opencv_pic\\house_small.jpg", 1 );
cvtColor( src, srcGray, CV_BGR2GRAY );
/// 設置參數
int blockSize = 3;
int apertureSize = 3;
/// 使用cornerEigenValsAndVecs()函數檢測角點
dstHarris = Mat::zeros( srcGray.size(), CV_32FC(6) );
resHarris = Mat::zeros( srcGray.size(), CV_32FC1 );
cornerEigenValsAndVecs( srcGray, dstHarris, blockSize, apertureSize, BORDER_DEFAULT );
/// 特徵點選擇
for( int j = 0; j < srcGray.rows; j++ )
{
for( int i = 0; i < srcGray.cols; i++ )
{
// 兩個特徵值
float* lambda = dstHarris.ptr<float>( j, i);
float lambda1 = lambda[0];
float lambda2 = lambda[1];
// 會報錯!!!//
//float lambda1 = dstHarris.at<float>( j, i, 0);
//float lambda2 = dstHarris.at<float>( j, i, 1);
resHarris.at<float>(j,i) = lambda1*lambda2 - 0.04*pow( ( lambda1 + lambda2 ), 2 );
}
}
minMaxLoc( resHarris, &HarrisMinVal, &HarrisMaxVal, 0, 0, Mat() );
/// 創建窗口和滑動條
namedWindow( HarrisWindow, CV_WINDOW_AUTOSIZE );
createTrackbar( "Quality:", HarrisWindow, &HarrisQualityLevel, maxQualityLevel, HarrisFunction );
HarrisFunction( 0, 0 );
waitKey(0);
return(0);
}
/// 角點檢測函數實現
void HarrisFunction( int, void* )
{
/// 深度拷貝原圖像用於繪製角點
srcCopy = src.clone();
if( HarrisQualityLevel < 1 )
HarrisQualityLevel = 1;
/// 角點滿足條件則繪製
for( int j = 0; j < srcGray.rows; j++ )
{
for( int i = 0; i < srcGray.cols; i++ )
{
if( resHarris.at<float>(j,i) > HarrisMinVal + ( HarrisMaxVal - HarrisMinVal )*HarrisQualityLevel/maxQualityLevel )
circle( srcCopy, Point(i,j), 2, Scalar( 0,0,255 ), -1, 8, 0 );
}
}
imshow( HarrisWindow, srcCopy );
cout<<"Harris Quality Level: "<<HarrisQualityLevel<<endl;
}
實驗結果:
這裏需要注意這裏floatlambda1 = dstHarris.at<float>( j, i, 0);使用at方式訪問Mat的數據會報錯:
所以我改成了採用指針的形式float* lambda = dstHarris.ptr<float>( j, i);。結果顯示當Quality Level增大時,滿足條件被保留的角點數目越來越少。
(2)採用cornerMinEigenVal()函數和minMaxLoc()函數結合來模擬Shi-Tomasi角點檢測的代碼示例如下:
/**
* @使用cornerMinEigenVal()函數模擬Shi-Tomasi角點檢測
* @author holybin
*/
#include "opencv2/highgui/highgui.hpp"
#include "opencv2/imgproc/imgproc.hpp"
#include <iostream>
#include <stdio.h>
#include <stdlib.h>
using namespace cv;
using namespace std;
/// 全局變量
Mat src;
Mat srcGray, srcCopy; //srcCopy用於繪圖,srcGray用於檢測角點
Mat dstShiTomasi; //dstShiTomasi用於存儲角點檢測的結果
/// 各類閾值
int ShiTomasiQualityLevel = 50;
int maxQualityLevel = 100;
double ShiTomasiMinVal;
double ShiTomasiMaxVal;
char* ShiTomasiWindow = "My Shi-Tomasi corner detector";
/// 角點檢測函數聲明
void ShiTomasiFunction( int, void* );
int main( int argc, char** argv )
{
/// 載入圖像並灰度化
src = imread("D:\\opencv_pic\\house_small.jpg", 1 );
cvtColor( src, srcGray, CV_BGR2GRAY );
/// 設置參數
int blockSize = 3;
int apertureSize = 3;
/// 使用cornerMinEigenVal()函數檢測角點
dstShiTomasi = Mat::zeros( srcGray.size(), CV_32FC1 );
cornerMinEigenVal( srcGray, dstShiTomasi, blockSize, apertureSize, BORDER_DEFAULT );
minMaxLoc( dstShiTomasi, &ShiTomasiMinVal, &ShiTomasiMaxVal, 0, 0, Mat() );
/// 創建窗口和滑動條
namedWindow( ShiTomasiWindow, CV_WINDOW_AUTOSIZE );
createTrackbar( " Quality:", ShiTomasiWindow, &ShiTomasiQualityLevel, maxQualityLevel, ShiTomasiFunction );
ShiTomasiFunction( 0, 0 );
waitKey(0);
return(0);
}
/// 角點檢測函數實現
void ShiTomasiFunction( int, void* )
{
/// 深度拷貝原圖像用於繪製角點
srcCopy = src.clone();
if( ShiTomasiQualityLevel < 1 )
ShiTomasiQualityLevel = 1;
/// 角點滿足條件則繪製
for( int j = 0; j < srcGray.rows; j++ )
{
for( int i = 0; i < srcGray.cols; i++ )
{
if( dstShiTomasi.at<float>(j,i) > ShiTomasiMinVal + ( ShiTomasiMaxVal - ShiTomasiMinVal )*ShiTomasiQualityLevel/maxQualityLevel )
circle( srcCopy, Point(i,j), 2, Scalar( 255,0,0 ), -1, 8, 0 );
}
}
imshow( ShiTomasiWindow, srcCopy );
cout<<"Shi-Tomasi Quality Level: "<<ShiTomasiQualityLevel<<endl;
}
實驗結果:
結果顯示當QualityLevel增大時,滿足條件被保留的角點數目越來越少。