1.基础理论
在处理适用于多幅图像的特征描述子时,主要有两种特征检测方法,一种是基于角的检测,另一种是处理图像中的所有区域。这里主要讨论一下基于角的检测。
1988年,Harris and Stephens 提出了一种角检测算法, HS角检测器,见论文A Combined Corner and Edge Detector。
HS检测器原理如下,对于灰度变化有三种情况,1是各个方向上灰度均不变化, 2是在某个方向上变化, 但在另一个方向灰度不变,3是两个方向都发生变化。HS角检测器是使用数学公式来区分这三种情况。
f
表示图像,f(s, t)
表示由 (s, t)
的值定义的一小块图像 patch
,f(s+x, t+y)
是尺寸相同但移动了 (x,y)
的小块图像 patch
,两个图像块每个像素像素值间差的平方的加权和表示为:
C ( x , y ) = ∑ ∑ ω ( s , t ) [ f ( s + x , t + y ) − f ( s , t ) ] 2 C(x,y) = \sum\sum\omega(s,t)[f(s+x, t+y)-f(s,t)]^2 C(x,y)=∑∑ω(s,t)[f(s+x,t+y)−f(s,t)]2
ω
(
s
,
t
)
\omega(s,t)
ω(s,t)是一个加权函数。有角的地方,即C(x,y)
取最大值。取一个像素点,在其相邻像素计算
C
(
x
,
y
)
C(x,y)
C(x,y)。
将 f ( s + x , t + y ) f(s+x, t+y) f(s+x,t+y)表示泰勒展开的近似,
f ( s + x , t + y ) ≈ f ( s , t ) + x f x ( s , t ) + y f y ( s , t ) f(s+x, t+y)\approx f(s,t)+xf_x(s,t)+yf_y(s,t) f(s+x,t+y)≈f(s,t)+xfx(s,t)+yfy(s,t)
然后:
C ( x , y ) = ∑ ∑ ω ( s , t ) [ x f x ( s , t ) + y f y ( s , t ) ] 2 C(x,y) = \sum\sum\omega(s,t)[xf_x(s,t)+yf_y(s,t)]^2 C(x,y)=∑∑ω(s,t)[xfx(s,t)+yfy(s,t)]2
上述方程可以表示为矩阵形式:
C ( x , y ) = [ x , y ] M [ x y ] C(x,y) = [x,y]M\begin{bmatrix} x\\ y \end{bmatrix} C(x,y)=[x,y]M[xy]
其中,
M = ∑ ∑ ω ( s , t ) A M = \sum\sum\omega(s,t)A M=∑∑ω(s,t)A
A = [ f x 2 f x f y f x f y f y 2 ] A = \begin{bmatrix} f_x^2 & f_xf_y\\ f_xf_y & f_y^2 \end{bmatrix} A=[fx2fxfyfxfyfy2]
M
M
M有时被称为Harris
矩阵。
矩阵 M M M的特征向量指向最大的数据扩展方向,且对应的特征值与特征向量方向上的数据扩展量成正比。由此可以知道,两个小特征值表示几乎恒定的灰度,一个小特征值和一个大特征值表示存在垂直边界或水平边界,两个大特征值表示存在一个孤立的点或角。
然而,由于求解特征值计算开销较大,HS角检测器并未使用特征值,而是利用了方阵的性质,迹等于该矩阵的特征值之和,行列式等于特征值的积,
R = λ x λ y − k ( λ x + λ y ) 2 = d e t ( M ) − k t r a c e 2 ( M ) R=\lambda_x\lambda_y - k(\lambda_x + \lambda_y)^2=det(M)-ktrace^2(M) R=λxλy−k(λx+λy)2=det(M)−ktrace2(M)
k k k是一个常数,通常取 ( 0 , 0.25 ) (0, 0.25) (0,0.25)。
两个特征值都大时, R取较大的正值, 一个特征值较大一个特征值较小时,R取较大负值,两个特征值都较小时,R的绝对值较小
k可以看作一个敏感因子,k越小找到的角越多
Shi-Tomasi 角点检测器
1994年,Jianbo Shi and Carlo Tomasi提出Good features to track
直接使用
R
=
m
i
n
(
λ
x
,
λ
y
)
R=min(\lambda_x,\lambda_y)
R=min(λx,λy)作为度量,避免了超参数
k
k
k
2.OpenCV API
2.1cornerHarris
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
:边界像素的处理方式
2.2 goodFeaturesToTrack
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
矩阵的最小特征值小于
q
u
a
l
i
t
y
L
e
v
e
l
∗
m
a
x
(
q
u
a
l
i
t
y
M
e
a
s
u
r
e
M
a
p
(
x
,
y
)
)
qualityLevel*max(qualityMeasureMap(x,y))
qualityLevel∗max(qualityMeasureMap(x,y)),该角将被舍弃
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
角点检测的超参数
3.示例
#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 );
}