概述
除了之前文章所说的利用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增大时,满足条件被保留的角点数目越来越少。