一、Harris角点检测原理
1. 何为角点?
下面有两幅不同视角的图像,通过找出对应的角点进行匹配。
我们可以直观的概括下角点所具有的特征:
>轮廓之间的交点;
>对于同一场景,即使视角发生变化,通常具备稳定性质的特征;
>该点附近区域的像素点无论在梯度方向上还是其梯度幅值上有着较大变化;
2. 角点检测算法基本思想是什么?
算法基本思想是使用一个固定窗口在图像上进行任意方向上的滑动,比较滑动前与滑动后两种情况,窗口中的像素灰度变化程度,如果存在任意方向上的滑动,都有着较大灰度变化,那么我们可以认为该窗口中存在角点。
3.如何用数学方法去刻画角点特征?
当窗口发生[u,v]移动时,那么滑动前与滑动后对应的窗口中的像素点灰度变化描述如下:
公式解释:
>[u,v]是窗口的偏移量
>(x,y)是窗口内所对应的像素坐标位置,窗口有多大,就有多少个位置
>w(x,y)是窗口函数,最简单情形就是窗口内的所有像素所对应的w权重系数均为1。但有时候,我们会将w(x,y)函数设定为以窗口中心为原点的二元正态分布。如果窗口中心点是角点时,移动前与移动后,该点的灰度变化应该最为剧烈,所以该点权重系数可以设定大些,表示窗口移动时,该点在灰度变化贡献较大;而离窗口中心(角点)较远的点,这些点的灰度变化几近平缓,这些点的权重系数,可以设定小点,以示该点对灰度变化贡献较小,那么我们自然想到使用二元高斯函数来表示窗口函数,这里仅是个人理解,大家可以参考下。
所以通常窗口函数有如下两种形式:
根据上述表达式,当窗口处在平坦区域上滑动,可以想象的到,灰度不会发生变化,那么E(u,v) = 0;如果窗口处在比纹理比较丰富的区域上滑动,那么灰度变化会很大。算法最终思想就是计算灰度发生较大变化时所对应的位置,当然这个较大是指针任意方向上的滑动,并非单指某个方向。
4.E(u,v)表达式进一步演化
首先需要了解泰勒公式,任何一个函数表达式,均可有泰勒公式进行展开,以逼近原函数,我们可以对下面函数进行一阶展开
这是一维的情况,对于多元函数,也有类似的泰勒公式。
那么,
所以E(u,v)表达式可以更新为:
这里矩阵M为,
5.矩阵M的关键性
难道我们是直接求上述的E(u,v)值来判断角点吗?Harris角点检测并没有这样做,而是通过对窗口内的每个像素的x方向上的梯度与y方向上的梯度进行统计分析。这里以Ix和Iy为坐标轴,因此每个像素的梯度坐标可以表示成(Ix,Iy)。针对平坦区域,边缘区域以及角点区域三种情形进行分析:
下图是对这三种情况窗口中的对应像素的梯度分布进行绘制:
如果使用椭圆进行数据集表示,则绘制图示如下:
当然 ,大牛们并没有止步于此,这样通过判断两个变量的值来判断角点毕竟不是很方便。于是他们想出了一种更好的方法,对,就是定义角点响应函数R(corner response function),
6. harris角点检测算法步骤
1.利用Soble计算出XY方向的梯度值
2.计算出Ix^2,Iy^2,Ix*Iy
3.利用高斯函数对Ix^2,Iy^2,Ix*Iy进行滤波
4.计算局部特征结果矩阵M的特征值和响应函数C(i,j)=Det(M)-k(trace(M))^2 (0.04<=k<=0.06)
5.将计算出响应函数的值C进行非极大值抑制,滤除一些不是角点的点,同时要满足大于设定的阈值
二、Harris与Shi-Tomasi角点检测原理
Shi-Tomasi 算法是Harris 算法的改进。Harris 算法最原始的定义是将矩阵 M 的行列式值与 M 的迹相减,再将差值同预先给定的阈值进行比较。后来Shi 和Tomasi 提出改进的方法,若两个特征值中较小的一个大于最小阈值,则会得到强角点。
即Shi-Tomasi使用的角点响应函数为:
R = min(λ1, λ2)
代码如下:
#include <opencv2/opencv.hpp>
#include <opencv2/core.hpp>
#include <opencv2/imgproc.hpp>
#include <opencv2/videoio.hpp>
#include <opencv2/highgui.hpp>
#include <iostream>
#include <math.h>
using namespace cv;
using namespace std;
Mat src, gray_src;
Mat harris_det,shi_tomasi_det;
Mat harri_rsp,shi_tomasi_rsp;
int harri_threshold = 60;
int shi_tomasi_threshold = 60;
int max_count = 100;
double k = 0.04;
double harri_min, harri_max;
double shi_tomasi_min, shi_tomasi_max;
void Harris_Demo( int, void* );
void shi_tomasi_Demo( int, void* );
int main( int argc, char** argv ) {
src = imread( "11.jpg" );
if(src.empty( )) {
printf( "could not load image...\n" );
return -1;
}
namedWindow( "input image", CV_WINDOW_AUTOSIZE );
imshow( "input image", src ); //显示原图
/********************** Harri角点检测 ******************************/
harris_det = Mat::zeros( src.size( ),CV_32FC(6));
harri_rsp = Mat::zeros( src.size( ), CV_32FC1 );
/* 1. 原图转换成灰度图 */
cvtColor( src, gray_src, CV_BGR2GRAY );
/* 2. 计算图像的特征值与特征向量 */
cornerEigenValsAndVecs( gray_src, harris_det, 3, 3, 4 ); //注意:领域与核的大小
/* 3. 计算图像的响应值 R=r1*r2 - k*(r1+r2)^2 */
for(int row = 0; row < harris_det.rows; row++)
{
for(int col = 0; col < harris_det.cols; col++)
{
double r1 = harris_det.at<Vec6f>( row, col )[0];
double r2 = harris_det.at<Vec6f>( row, col )[1];
harri_rsp.at<float>( row, col ) = r1* r2 - k*pow( (r1 + r2), 2 );
}
}
namedWindow( "harri_demo", CV_WINDOW_AUTOSIZE );
minMaxLoc( harri_rsp, &harri_min, &harri_max,NULL,NULL,Mat()); //找到响应图像的极值
createTrackbar( "harri_Threshold:", "harri_demo", &harri_threshold, max_count, Harris_Demo );
Harris_Demo( 0, 0 );
/********************** Shi_tomasi角点检测 ******************************/
shi_tomasi_rsp = Mat::zeros( src.size( ),CV_32FC1);
cornerMinEigenVal( gray_src, shi_tomasi_rsp, 3, 3 ); //获取Min(r1 , r2 )
minMaxLoc( shi_tomasi_rsp, &shi_tomasi_min, &shi_tomasi_max, NULL, NULL, Mat( ) );
namedWindow( "shi_tomasi_demo", CV_WINDOW_AUTOSIZE );
createTrackbar( "shitomasi_Threshold:", "shi_tomasi_demo", &shi_tomasi_threshold, max_count, shi_tomasi_Demo );
shi_tomasi_Demo( 0, 0 );
waitKey( 0 );
return 0;
}
void Harris_Demo( int, void* ) {
Mat dst, norm_dst, normScaleDst;
if(harri_threshold < 10)
harri_threshold = 10;
Mat harri_result = src.clone( );
float value = harri_min + (float)harri_threshold / max_count*(harri_max - harri_min); //计算图像角点的阈值
for(int row = 0; row < harri_result.rows; row++)
{
for(int col = 0; col < harri_result.cols; col++)
{
float v = harri_rsp.at<float>( row, col );
if( v > value )
{
/* 4. 超过阈值,则在此点加个圆圈 */
circle( harri_result, Point( col, row ), 2, Scalar( 0, 0, 255 ), 2, 8, 0 );
}
}
}
imshow( "harri_show", harri_result );
}
void shi_tomasi_Demo( int, void* ) {
Mat dst, norm_dst, normScaleDst;
if(shi_tomasi_threshold < 10)
shi_tomasi_threshold = 10;
Mat shi_tomasi_result = src.clone( );
float value = harri_min + (float)shi_tomasi_threshold / max_count*(shi_tomasi_max - shi_tomasi_min); //计算图像角点的阈值
for(int row = 0; row < shi_tomasi_result.rows; row++)
{
for(int col = 0; col < shi_tomasi_result.cols; col++)
{
float v = shi_tomasi_rsp.at<float>( row, col );
if(v > value)
{
/* 4. 超过阈值,则在此点加个圆圈 */
circle( shi_tomasi_result, Point( col, row ), 2, Scalar( 0, 0, 255 ), 2, 8, 0 );
}
}
}
imshow( "shi_tomasi_show", shi_tomasi_result );
}