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)。针对平坦区域,边缘区域以及角点区域三种情形进行分析:
下图是对这三种情况窗口中的对应像素的梯度分布进行绘制:
如果使用椭圆进行数据集表示,则绘制图示如下:
不知道大家有没有注意到这三种区域的特点,平坦区域上的每个像素点所对应的(IX,IY)坐标分布在原点附近,其实也很好理解,针对平坦区域的像素点,他们的梯度方向虽然各异,但是其幅值都不是很大,所以均聚集在原点附近;边缘区域有一坐标轴分布较散,至于是哪一个坐标上的数据分布较散不能一概而论,这要视边缘在图像上的具体位置而定,如果边缘是水平或者垂直方向,那么Iy轴方向或者Ix方向上的数据分布就比较散;角点区域的x、y方向上的梯度分布都比较散。我们是不是可以根据这些特征来判断哪些区域存在角点呢?
虽然我们利用E(u,v)来描述角点的基本思想,然而最终我们仅仅使用的是矩阵M。让我们看看矩阵M形式,是不是跟协方差矩阵形式很像,像归像,但是还是有些不同,哪儿不同?一般协方差矩阵对应维的随机变量需要减去该维随机变量的均值,但矩阵M中并没有这样做,所以在矩阵M里,我们先进行各维的均值化处理,那么各维所对应的随机变量的均值为0,协方差矩阵就大大简化了,简化的最终结果就是矩阵M,是否明白了?我们的目的是分析数据的主要成分,相信了解PCA原理的,应该都了解均值化的作用。
如果我们对协方差矩阵M进行对角化,很明显,特征值就是主分量上的方差,这点大家应该明白吧?不明白的话可以复习下PCA原理。如果存在两个主分量所对应的特征值都比较大,说明什么? 像素点的梯度分布比较散,梯度变化程度比较大,符合角点在窗口区域的特点;如果是平坦区域,那么像素点的梯度所构成的点集比较集中在原点附近,因为窗口区域内的像素点的梯度幅值非常小,此时矩阵M的对角化的两个特征值比较小;如果是边缘区域,在计算像素点的x、y方向上的梯度时,边缘上的像素点的某个方向的梯度幅值变化比较明显,另一个方向上的梯度幅值变化较弱,其余部分的点都还是集中原点附近,这样M对角化后的两个特征值理论应该是一个比较大,一个比较小,当然对于边缘这种情况,可能是呈45°的边缘,致使计算出的特征值并不是都特别的大,总之跟含有角点的窗口的分布情况还是不同的。
注:M为协方差矩阵,需要大家自己去理解下,窗口中的像素集构成一个矩阵(2*n,假设这里有n个像素点),使用该矩阵乘以该矩阵的转置,即是协方差矩阵
因此可以得出下列结论:
-
特征值都比较大时,即窗口中含有角点
-
特征值一个较大,一个较小,窗口中含有边缘
-
特征值都比较小,窗口处在平坦区域
6. 如何度量角点响应?
通常用下面表达式进行度量:
其中k是常量,一般取值为0.04~0.06,这个参数仅仅是这个函数的一个系数,它的存在只是调节函数的形状而已。
但是为什么会使用这样的表达式呢?一下子是不是感觉很难理解?其实也不难理解,函数表达式一旦出来,我们就可以绘制它的图像,而这个函数图形正好满足上面几个区域的特征。 通过绘制函数图像,直观上更能理解。绘制的R函数图像如下:
那么如何通过矩阵判断角点的? 其实上面,我们已经推导出E(u,v)的表达式,大家看看这个表达式有什么特征,其中矩阵M是实对称矩阵,那么E表达式其实就是二次型,对于二次型想必大家会有印象,U,V代表窗口滑动方向以及滑动量,E代表灰度变化,通过矩阵M进行特征值求解,而特征值所对应的特征向量即为灰度变化方向。如果两个特征值较大,则表示有两个方向灰度变化较快。所以可以直接通过求解M的特征值进行角点判断。
下面是代码:
#include <opencv2/opencv.hpp>
#include "opencv2/highgui/highgui.hpp"
#include "opencv2/imgproc/imgproc.hpp"
using namespace cv;
using namespace std;
//-----------------------------------【宏定义部分】--------------------------------------------
// 描述:定义一些辅助宏
//------------------------------------------------------------------------------------------------
#define WINDOW_NAME1 "【程序窗口1】" //为窗口标题定义的宏
#define WINDOW_NAME2 "【程序窗口2】" //为窗口标题定义的宏
//-----------------------------------【全局变量声明部分】--------------------------------------
// 描述:全局变量声明
//-----------------------------------------------------------------------------------------------
Mat g_srcImage, g_srcImage1,g_grayImage;
int thresh = 30; //当前阈值
int max_thresh = 175; //最大阈值
//-----------------------------------【全局函数声明部分】--------------------------------------
// 描述:全局函数声明
//-----------------------------------------------------------------------------------------------
void on_CornerHarris( int, void* );//回调函数
static void ShowHelpText();
//-----------------------------------【main( )函数】--------------------------------------------
// 描述:控制台应用程序的入口函数,我们的程序从这里开始执行
//-----------------------------------------------------------------------------------------------
int main( int argc, char** argv )
{
//【0】改变console字体颜色
system("color 3F");
//【0】显示帮助文字
ShowHelpText();
//【1】载入原始图并进行克隆保存
g_srcImage = imread( "D:\\python_code\\goodimg.jpg", 1 );
if(!g_srcImage.data ) { printf("读取图片错误,请确定目录下是否有imread函数指定的图片存在~! \n"); return false; }
imshow("原始图",g_srcImage);
g_srcImage1=g_srcImage.clone( );
//【2】存留一张灰度图
cvtColor( g_srcImage1, g_grayImage, COLOR_BGR2GRAY );
//【3】创建窗口和滚动条
namedWindow( WINDOW_NAME1, WINDOW_AUTOSIZE );
createTrackbar( "阈值: ", WINDOW_NAME1, &thresh, max_thresh, on_CornerHarris );
//【4】调用一次回调函数,进行初始化
on_CornerHarris( 0, 0 );
waitKey(0);
return(0);
}
//-----------------------------------【on_HoughLines( )函数】--------------------------------
// 描述:回调函数
//----------------------------------------------------------------------------------------------
void on_CornerHarris( int, void* )
{
//---------------------------【1】定义一些局部变量-----------------------------
Mat dstImage;//目标图
Mat normImage;//归一化后的图
Mat scaledImage;//线性变换后的八位无符号整型的图
//---------------------------【2】初始化---------------------------------------
//置零当前需要显示的两幅图,即清除上一次调用此函数时他们的值
dstImage = Mat::zeros( g_srcImage.size(), CV_32FC1 );
g_srcImage1=g_srcImage.clone( );
//---------------------------【3】正式检测-------------------------------------
//进行角点检测
cornerHarris( g_grayImage, dstImage, 2, 3, 0.04, BORDER_DEFAULT );
// 归一化与转换
normalize( dstImage, normImage, 0, 255, NORM_MINMAX, CV_32FC1, Mat() );
convertScaleAbs( normImage, scaledImage );//将归一化后的图线性变换成8位无符号整型
//---------------------------【4】进行绘制-------------------------------------
// 将检测到的,且符合阈值条件的角点绘制出来
for( int j = 0; j < normImage.rows ; j++ )
{ for( int i = 0; i < normImage.cols; i++ )
{
if( (int) normImage.at<float>(j,i) > thresh+80 )
{
circle( g_srcImage1, Point( i, j ), 5, Scalar(10,10,255), 2, 8, 0 );
circle( scaledImage, Point( i, j ), 5, Scalar(0,10,255), 2, 8, 0 );
}
}
}
//---------------------------【4】显示最终效果---------------------------------
imshow( WINDOW_NAME1, g_srcImage1 );
imshow( WINDOW_NAME2, scaledImage );
Shi-Tomasi 算法
Shi-Tomasi 算法是Harris 算法的改进。Harris 算法最原始的定义是将矩阵 M 的行列式值与 M 的迹相减,再将差值同预先给定的阈值进行比较。后来Shi 和Tomasi 提出改进的方法,若两个特征值中较小的一个大于最小阈值,则会得到强角点。
对自相关矩阵 M 进行特征值分析,产生两个特征值
(
λ
0
,
λ
1
)
(\lambda_{0},\lambda_{1})
(λ0,λ1)和两个特征方向向量。因为较大的不确定度取决于较小的特征值,也就是,所以通过寻找最小特征值的最大值来寻找好的特征点也就解释的通了。
Shi 和Tomasi 的方法比较充分,并且在很多情况下可以得到比使用Harris 算法更好的结果。
但是,如果在我这个课题上来说,背景一旦变化,特征点追踪的时候车辆角点需要配合光流一起。背景的角点也都存在,而且背景是变化的。所以角点匹配并不是很好的方案。
参考:https://blog.csdn.net/xiaowei_cqu/article/details/7805206