除了之前文章所说的利用Harris进行角点检测和利用Shi-Tomasi方法进行角点检测外,也可以自己制作角点检测的函数:使用cornerEigenValsAndVecs()函数和minMaxLoc()函数结合来模拟Harris角点检测,或者使用cornerMinEigenVal()函数和minMaxLoc()函数结合来模拟Shi-Tomasi角点检测,最后特征点选取的判断条件要根据实际情况进行选择。
(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的特征向量。该函数的输出能够用于鲁棒的边缘或角点检测。
(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增大时,满足条件被保留的角点数目越来越少。