# Harris角点检测
# 基础理论
在处理适用于多幅图像的特征描述子时,主要有两种特征检测方法,一种是基于角的检测,另一种是处理图像中的所有区域。这里主要讨论一下基于角的检测。
1988年,Harris and Stephens 提出了一种角检测算法, HS角检测器,见论文A Combined Corner and Edge Detector (opens new window)。
HS检测器原理如下,对于灰度变化有三种情况,1是各个方向上灰度均不变化, 2是在某个方向上变化, 但在另一个方向灰度不变,3是两个方向都发生变化。HS角检测器是使用数学公式来区分这三种情况。
f
表示图像,f(s, t)
表示由 (s, t)
的值定义的一小块图像 patch
,f(s+x, t+y)
是尺寸相同但移动了 (x,y)
的小块图像 patch
,两个图像块每个像素像素值间差的平方的加权和表示为:
C(x,y)
取最大值。取一个像素点,在其相邻像素计算
将
然后:
上述方程可以表示为矩阵形式:
C(x,y) = [x,y]M\begin{bmatrix} x\\ y \end{bmatrix}
其中,
A = \begin{bmatrix} f_x^2 & f_xf_y\\ f_xf_y & f_y^2 \end{bmatrix}
Harris
矩阵。
矩阵
然而,由于求解特征值计算开销较大,HS角检测器并未使用特征值,而是利用了方阵的性质,迹等于该矩阵的特征值之和,行列式等于特征值的积,
两个特征值都大时, R取较大的正值, 一个特征值较大一个特征值较小时,R取较大负值,两个特征值都较小时,R的绝对值较小
k可以看作一个敏感因子,k越小找到的角越多
Shi-Tomasi 角点检测器
1994年,Jianbo Shi and Carlo Tomasi提出Good features to track (opens new window)
直接使用
# OpenCV API
# cornerHarris (opens new window)
void cv::cornerHarris(
InputArray src,
OutputArray dst,
int blockSize,
int ksize,
double k,
int borderType = BORDER_DEFAULT
)
src
:输入,8位单通道图像,灰度图dst
:存储Harris
检测结果的数据,数据类型CV_32FC1
blockSize
:计算C(x,y)
时邻域的大小ksize
:使用Sobel
算子计算梯度时,卷积核的大小k
:计算R
时的超参数,即检测角的敏感度的调解因子,小则角多,大则角少borderType
:边界像素的处理方式
# goodFeaturesToTrack (opens new window)
void cv::goodFeaturesToTrack (
InputArray image,
OutputArray corners,
int maxCorners,
double qualityLevel,
double minDistance,
InputArray mask = noArray(),
int blockSize = 3,
bool useHarrisDetector = false,
double k = 0.04
)
该函数的作用,
1.先使用cornerHarris
计算每个角的度量
2.在每个像素3x3
的邻域范围执行极大值抑制
3.每个角对应的Harris
矩阵的最小特征值小于
4.将余下的角根据quality measure
降序排列
5.移除那些距离小于maxDistance
的角点
image
:单通道图像corners
:角的输出向量maxCorners
: 最多支持检测到的角的个数qualityLevel
: 控制角的质量水平,例如最好的是1500
,qualityLevel
为0.01
,则quality measure
小于1500*0.01
的将被舍弃minDistance
: 角与角之间的最小距离mask
:掩码,控制对图像哪一部分进行角点检测blockSize
: 计算梯度相关矩阵时使用的邻域大小,参考cornerEigenValsAndVecsuseHarrisDetector
:是否使用Harris
角点检测k
:Harris
角点检测的超参数
# 示例
#include <opencv2/imgproc.hpp>
#include <opencv2/highgui.hpp>
#include <opencv2/imgcodecs.hpp>
#include <iostream>
using namespace cv;
using namespace std;
Mat src, src_gray;
int maxCorners = 23;
int maxTrackbar = 100;
RNG rng(12345);
int thresh = 200;
int max_thresh = 255;
const char* source_window = "Source image";
const char* corners_window = "Corners detected";
void cornerHarris_demo( int, void* );
void goodFeaturesToTrack_Demo( int, void* );
int main( int argc, char** argv )
{
CommandLineParser parser( argc, argv, "{@input | building.jpg | input image}" );
src = imread( samples::findFile( parser.get<String>( "@input" ) ) );
if ( src.empty() )
{
cout << "Could not open or find the image!\n" << endl;
cout << "Usage: " << argv[0] << " <Input image>" << endl;
return -1;
}
cvtColor( src, src_gray, COLOR_BGR2GRAY );
namedWindow( source_window );
createTrackbar( "Threshold: ", source_window, &thresh, max_thresh, cornerHarris_demo );
imshow( source_window, src );
// cornerHarris_demo( 0, 0 );
goodFeaturesToTrack_Demo( 0, 0 );
waitKey();
return 0;
}
void cornerHarris_demo( int, void* )
{
int blockSize = 2;
int apertureSize = 3;
double k = 0.04;
Mat dst = Mat::zeros( src.size(), CV_32FC1 );
cornerHarris( src_gray, dst, blockSize, apertureSize, k );
Mat dst_norm, dst_norm_scaled;
normalize( dst, dst_norm, 0, 255, NORM_MINMAX, CV_32FC1, Mat() );
convertScaleAbs( dst_norm, dst_norm_scaled );
for( int i = 0; i < dst_norm.rows ; i++ )
{
for( int j = 0; j < dst_norm.cols; j++ )
{
if( (int) dst_norm.at<float>(i,j) > thresh )
{
circle( dst_norm_scaled, Point(j,i), 5, Scalar(0), 2, 8, 0 );
}
}
}
namedWindow( corners_window );
imshow( corners_window, dst_norm_scaled );
imwrite("corner_grid.png", dst_norm_scaled);
}
void goodFeaturesToTrack_Demo( int, void* )
{
maxCorners = MAX(maxCorners, 1);
vector<Point2f> corners;
double qualityLevel = 0.01;
double minDistance = 10;
int blockSize = 3, gradientSize = 3;
bool useHarrisDetector = false;
double k = 0.04;
Mat copy = src.clone();
goodFeaturesToTrack( src_gray,
corners,
maxCorners,
qualityLevel,
minDistance,
Mat(),
blockSize,
gradientSize,
useHarrisDetector,
k );
cout << "** Number of corners detected: " << corners.size() << endl;
int radius = 8;
for( size_t i = 0; i < corners.size(); i++ )
{
circle( copy, corners[i], radius, Scalar(0, 0, rng.uniform(0, 256)), FILLED );
}
namedWindow( source_window );
imwrite("corner_grid_st.png", copy);
imshow( source_window, copy );
}