精华内容
下载资源
问答
  • 角点检测

    2020-09-23 19:02:14
    角点检测应用于运动检测,图像匹配,视频跟踪,三维建模和目标识别。 图像的特征类型: 边缘 感兴趣关键点(角点) 感兴趣区域(斑点) 阻挠:光照变换和图像旋转的变化会使已有角点的特征变化。 角点检测算法 ...

    角点/兴趣点/特征点:特征明显的点。

    具体描述:

    1. 灰度的梯度的局部最大所对应的像素点
    2. 两条及两条以上边缘的交点
    3. 图像中梯度值和梯度方向的变换速率都很高的点

    角点检测应用于运动检测,图像匹配,视频跟踪,三维建模和目标识别。

    图像的特征类型:

    1. 边缘
    2. 感兴趣关键点(角点)
    3. 感兴趣区域(斑点)

    阻挠:光照变换和图像旋转的变化会使已有角点的特征变化。

    角点检测算法

    (1)基于灰度图像的角点检测

    ① 基于梯度

    ② 基于模板

    主要考虑像素邻域点的灰度变化,将与邻域内亮度对比足够大的点定义未角点。
    Kitchen-Rosenfeld算法

    1. Harris算法
    void cornerHarris(InputArray src, 
    				  OutputArray dst, 
    				  int blockSize, 
    				  int ksize,
    				  double k, 
    				  int borderType=BORDER_DEFAULT);
    

    参数详解:

    • 第一个参数:输入图像。须为单通道8位或者浮点型图像。
    • 第二个参数:存放Harris角点检测的输出结果,和输入图像有一样的尺寸和类型。
    • 第三个参数:邻域的大小
    • 第四个参数:Sobel算子的孔径大小。
    • 第五个参数:Harris参数。
    • 第六个参数:图像像素的边界模式。默认值BORDER_DEFAULT。

    程序实例

    #include<opencv2/opencv.hpp>
    #include<iostream>
    
    using namespace std;
    using namespace cv;
    
    #define WINDOW_NAME1 "【程序窗口1】"
    #define WINDOW_NAME2 "【程序窗口2】"
    
    Mat g_srcImage, g_srcImage1, g_grayImage;
    int thresh = 40;
    int max_thresh = 175;
    
    void on_CornerHarris(int, void*);
    
    int main()
    {
    	g_srcImage = imread("E://Study//RoboMaster//resource//3.jpg");
    
    	if (!g_srcImage.data)
    	{
    		printf("error!\n");
    		return false;
    	}
    
    	imshow("原始图", g_srcImage);
    	g_srcImage1 = g_srcImage.clone();
    
    	cvtColor(g_srcImage, g_grayImage, COLOR_BGR2GRAY);
    
    	namedWindow(WINDOW_NAME1, WINDOW_AUTOSIZE);
    	createTrackbar("阈值:", WINDOW_NAME1, &thresh, max_thresh, on_CornerHarris);
    
    	on_CornerHarris(0, 0);
    
    	waitKey(0);
    	return(0);
    }
    
    void on_CornerHarris(int, void*)
    {
    	Mat dstImage;
    	Mat normImage;
    	Mat scaledImage;
    
    	dstImage = Mat::zeros(g_srcImage.size(), CV_32FC1);
    	g_srcImage1 = g_srcImage.clone();
    
    	cornerHarris(g_grayImage, dstImage, 2, 3, 0.04, BORDER_DEFAULT);
    
    	normalize(dstImage, normImage, 0, 255, NORM_MINMAX, CV_32FC1, Mat());
    	
    	convertScaleAbs(normImage, scaledImage);
    
    	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);
    			}
    		}
    	}
    	imshow(WINDOW_NAME1, g_srcImage1);
    	imshow(WINDOW_NAME2, scaledImage);
    }
    
    1. Shi-Tomasi
    void goodFeaturesToTrack(InputArray image,
    						 OutputArray corners,
    						 int maxCorners,
    						 double qualityLevel,
    						 double minDistance,
    						 InputArray mask = noArray(),
    						 int blockSize = 3,
    						 bool useHarrisDetector = false,
    						 double k = 0.04);
    

    参数详解:

    • 第一个参数:输入图像。须为8位或浮点型32位单通道图像。
    • 第二个参数:检测到的角点的输出向量。
    • 第三个参数:角点的最大数量。
    • 第四个参数:角点检测可接受的最小特征值。
    • 第五个参数:角点间的最小距离。
    • 第六个参数:感兴趣区域。默认值noArray()。
    • 第七个参数:计算导数自相关矩阵时指定的邻域范围。默认值3。
    • 第八个参数:指示是否使用Harris角点检测。默认false。
    • 第九个参数:设置Hessian自相关矩阵行列式的相对权重的权重系数。默认值0.04。
    1. KLT算法

    2. SUSAN算法

    ③ 基于模板梯度组合

    (2)基于二值图像的角点检测

    (3)基于轮廓曲线的角点检测

    亚像素级角点检测

    void cornerSubPix(InputArray image,
    				  InputOutputArray corners,
    				  Size winSize,
    				  Size zeroZone,
    				  TermCriteria criteria)
    

    参数详解:

    • 第一个参数:输入图像。
    • 第二个参数:提供输入角点的初始坐标和精确的输出坐标。
    • 第三个参数:搜索窗口的一半尺寸。
    • 第四个参数:死区的一般尺寸。
    • 第五个参数:求角点的迭代过程的终止条件。
    展开全文
  • Harris角点检测原理详解

    万次阅读 多人点赞 2017-01-20 23:34:03
    网上也有很多博客对Harris角点检测原理进行描述,但基本上只是描述了算法流程,而其中有关细节并未作出解释,这里我想对有些地方做出解释,如有不对,还请指正。 1. 首先,何为角点? 下面有两副不同视角的图像,...

    关于角点的应用在图像处理上比较广泛,如图像匹配(FPM特征点匹配)、相机标定等。网上也有很多博客对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)。针对平坦区域,边缘区域以及角点区域三种情形进行分析:


    下图是对这三种情况窗口中的对应像素的梯度分布进行绘制:


    如果使用椭圆进行数据集表示,则绘制图示如下:


    不知道大家有没有注意到这三种区域的特点,平坦区域上的每个像素点所对应的(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函数图像如下:



    所以说难点不在于理解这个函数表达式,而在于如何创造出这个函数表达式。Harris也许对很多函数模型非常了解,对于创造出这样的一个函数表达式,易如反掌,当然在我们看来感觉是很了不起的,那是因为我们见过的函数模型太少。如果你也能构造一个函数模型,更能精确满足上述的三个特征,那么你比Harris更牛偷笑,纯属玩笑。

    最后设定R的阈值,进行角点判断。当然其中还有些后处理步骤就不再说了,比如说角点的极大值抑制等,就到这里吧,欢迎讨论留言。


    NOTE:
    有些朋友在问是如何通过矩阵判断角点的? 其实上面,我们已经推导出E(u,v)的表达式,大家看看这个表达式有什么特征,其中矩阵M是实对称矩阵,那么E表达式其实就是二次型,对于二次型想必大家会有印象,U,V代表窗口滑动方向以及滑动量,E代表灰度变化,通过矩阵M进行特征值求解,而特征值所对应的特征向量即为灰度变化方向。如果两个特征值较大,则表示有两个方向灰度变化较快。所以可以直接通过求解M的特征值进行角点判断
    展开全文
  • 角点 检测

    2009-10-29 15:12:00
    点检测的一份非常详细的教程:http://www.cim.mcgill.ca/~...这里只是简单的整理一下,用白话说明一下Harris角点检测算法的步骤:1. 计算图像的方向导数,分别保存为两个数组Ix以及Iy,这里可以使用任何方法,比较正统

    点检测的一份非常详细的教程:

    http://www.cim.mcgill.ca/~dparks/CornerDetector/index.htm

    在上述教程中涉及到了Harris角点检测的推导以及各种角点检测方法的比较,非常值得一看。这里只是简单的整理一下,用白话说明一下Harris角点检测算法的步骤:

    1. 计算图像的方向导数,分别保存为两个数组Ix以及Iy,这里可以使用任何方法,比较正统的是使用Gaussian函数,因为在Harris角点检测的推导过程中默认是采用了Gaussian函数作为其计算图像偏导数的方法。当然使用简单的Prewitt或者Sobel算子也没有关系。

    2. 为每一个点计算局部自相关矩阵 u(x,y) = [Ix(x,y)^2*W Iy(x,y)Ix(x,y)*W;Ix(x,y)Iy(x,y)*W Iy(x,y)^2*W];这里*W代表以x,y为中心与高斯模板W做卷积,而这个模板的大小则需要你自己指定。

    3. 如果这个u的两个特征值都很小,则说明这个区域是个平坦区域。如果u的某个特征值一个大一个小,则是线,如果两个都很大,那么就说明这是个角点。Harris提供了另一个公式来获取这个点是否是角点的一个评价:corness = det(u) - k*trace(u)^2;

    这个corness就代表了角点值,其中k是你自己取的一个固定的变量,典型的为[0.04,0.06]之间。当然在求取了每个点的corness以后,最好再做一个极大值抑制,这样的效果比直接设置一个阀值要好。

     

    本文来自CSDN博客,转载请标明出处:http://blog.csdn.net/prleader/archive/2009/02/23/3930571.aspx

    展开全文
  • Harris 角点检测实现原理OpenCV 函数优化GFTT 在图像处理和计算机视觉领域,兴趣点(inter points),也被称作关键点(key points)、特征点(feature points)。它被大量用于解决物体识别、图像识别、图像匹配、...

    在图像处理和计算机视觉领域,兴趣点(inter points),也被称作关键点(key points)、特征点(feature points)。它被大量用于解决物体识别、图像识别、图像匹配、视觉跟踪、三维重建等一系列的问题。我们不再观察整幅图,而是选择某些特殊的点,然后对它们进行局部有的放矢地分析。如果能检测到足够多的这种点,同时它们的区分度很高,并且可以精确定位稳定的特征,那么这个方法就具有实用价值。

    图像特征类型可以被分为如下三种:

    • 边缘
    • 角点(感兴趣关键点)
    • 斑点(Blobs)(感兴趣区域)

    对于边缘检测之前已经讲过,见 边缘检测1 边缘检测2 。其中角点是个很特殊的存在。如果某一点在任意方向的一个微小变动都会引起灰度很大的变化,那么我们就把它称之为角点。角点作为图像上的特征点,包含有重要的信息,在图像融合和目标跟踪及三维重建中有重要的应用价值。他们在图像中可以轻易地定位,同时,在人造物体场景,比如门、窗、桌等处也随处可见。因为角点位于两条边缘的角点处,代表了两个边缘变化的方向上的点,所以它们是可以精确定位的二维特征,甚至可以达到亚像素的精度。又由于其图像梯度有很高的变化,这种变化是可以用来帮助检测角点的。需要注意的是,角点与位于相同强度区域上的点不同,与物体轮廓上的点也不同,因为轮廓点难以在相同的其他物体上精确定位。

    另外,关于角点的具体描述可以有如下几种:

    • 一阶导数(即灰度的梯度)的局部最大所对应的像素点;
    • 两条及以上边缘的交点;
    • 图像中梯度值和梯度方向的变化速率都很高的点;
    • 角点处的一阶导数最大,二阶导数为零,它指示了物体边缘变化不连续的方向

    现有的角点检测算法并不是都十分健壮。很多方法都要求有大量的训练集和冗余数据来防止减少错误特征的出现。另外,角点检测方法的一个很重要的评价标准是其对多幅图像种相同或相似的检测能力,并且能够应对光照变换、图像旋转等图像变化。

    在当前的图像处理领域,角点检测算法可归纳为以下三类:

    • 基于灰度图像的角点检测
    • 基于二值图像的角点检测
    • 基于轮廓曲线的角点检测

    而基于灰度图像的角点检测又可分为基于梯度、基于模板和基于模板梯度组合三类方法。其中基于模板的方法主要考虑像素领域点的灰度变化,即图像亮度的变化,将与邻点亮度对比足够大的点定义为角点。常见的基于模板的角点检测算法有 Kitchen-Rosenfeld 角点检测算法、Harris 角点检测算法、KLT角点检测算法及SUSAN角点检测算法。

    Harris 角点检测

    Harris 角点检测是一种直接基于灰度图像的角点提取算法,稳定性高,尤其对L型角点检测精度高。但由于采用了高斯滤波,运算速度相对较慢,角点信息有丢失和位置偏移的现象,而且角点提取有聚簇现象。

    实现原理

    为了定义图像中角点的概念,Harris 特征检测方法在假定的兴趣点周围放置了一个小窗口,并观察窗口内某个方向上强度值的平均变化。如果位移向量为 (u,v)(u, v) ,那么可以用均方差之和表示强度的变化:
    R=(I(x+u,y+v)I(x,y))2 R = \sum (I(x+u,y+v) - I(x, y))^2
    累加的范围是该像素周围一个预先定义的邻域(邻域的尺寸取决于 cv::cornerHarris函数的第三个参数)。在所有方向上计算平均强度变化值,如果不止一个方向的变化值很高,就认为这个点是角点。根据这个定义,Harris测试的步骤应为:首先获得平均强度值变化最大的方向,然后检查垂直方向上的平均强度变化值,看它是否也很大;如果是,就说明这是一个角点。

    从数学的角度看,可以用泰勒展开式近似地计算上述公式,验证这个判断:
    R((I(x,y)+Ixu+IyvI(x,y))2=((Ixu)2+(Iyv)2+2IxIyuv)) R \approx \sum \left((I(x,y) + \frac{\partial I}{\partial x}u + \frac{\partial I}{\partial y}v - I(x,y)\right)^2 = \sum \left( (\frac{\partial I}{\partial x}u)^2 + (\frac{\partial I}{\partial y}v)^2 + 2 \frac{\partial I}{\partial x} \frac{\partial I}{\partial y}uv) \right)
    写成矩阵形式就是:
    R[u,v][(δIδx)2(δIδxδIδy)(δIδxδIδy)(δIδy)2][uv] R \approx [u, v] \begin{bmatrix} \sum(\frac{\delta I}{\delta x})^2 & \sum(\frac{\delta I}{\delta x}\frac{\delta I}{\delta y}) \\ \sum(\frac{\delta I}{\delta x}-\frac{\delta I}{\delta y})&\sum(\frac{\delta I}{\delta y})^2 \end{bmatrix} \begin{bmatrix} u \\ v \end{bmatrix}
    这是一个协方差矩阵,表示在所有方向上强度值变化的速率。这个定义包括了图像的一阶导数,通常用 Sobel 计算。这个协方差矩阵的两个特征值分别表示最大平均强度值变化和垂直方向的平均强度值变化。如果这两个特征值都很小,就说明是在相对同质的区域;如果一个特征值很大,另一个很小,那肯定是在边缘上;如果两者都很大,那么就是在角点上。因此判断一个点为角点的条件是它的协方差矩阵的最小特征值要大于指定的阈值。

    Harris角点算法的原始定义用到了特征分解理论的一些属性,从而避免显式地计算特征值带来的开销。这些属性是:

    • 矩阵的特征值之积等于它的行列式值
    • 矩阵的特征值之和等于它的对角元素之和(也就是矩阵的迹)

    通过计算下面的评分,可以验证矩阵的特征值高不高:
    Dst(x,y)=Det(C(x,y))k(trC(x,y))2 Dst(x,y) = Det(C^{(x,y)}) - k \cdot (tr C^{(x,y)})^2
    只要两个特征值都高,就很容易证明这个评分肯定也高。这个评分在每个像素的位置计算得到。数值 kk 是参数,确定这个参数的最佳值是比较困难的。但是根据经验,0.05~0.5 通常是比较好的选择。

    OpenCV 函数

    cornerHarris

    void cv::cornerHarris(InputArray		src,
                          OutputArray		dst,
                          int				blockSize,
                          int				ksize,
                          double			k,
                          int				borderType = BORDER_DEFAULT 
                          )		
    //Python:
    dst	= cv.cornerHarris(src, blockSize, ksize, k[, dst[, borderType]])
    

    参数解释

    参数 解释
    src 输入图像,8 位或浮点型图像
    dst 输出图像,存储函数调用后的运算结果,即 harris 角点检测的输出结果。类型为CV_32FC1,大小与src相同
    blockSize 邻域的大小
    ksize Sobel 算子的孔径大小
    k Harris 参数
    borderType 图像的边界模式,默认 BORDER_DEFAULT

    C++ 示例

    void myHarris(Mat& image){
        Mat cornerStrenth, harrisTh;
      	// 检测 Harris 角点
        cornerHarris(image, cornerStrenth, 2, 3, 0.01);
      	// 对角点强度阈值化
        threshold(cornerStrenth, harrisTh, 0.0001, 255, CV_THRESH_BINARY);
        imwrite("harrisTh.jpg", harrisTh);
    }
    int main(){
        Mat image = imread("test.jpg");
        Mat imageGray;
        cvtColor(image, imageGray, COLOR_BGR2GRAY);
        myHarris(image);
        return 0;
    }
    

    效果如下:

    在这里插入图片描述

    由此得到的角点分布图中包含很多聚集的角点像素,而不是我们想要检测的具有明确定位的角点。接下来通过定义一个检测 Harris 角点的类,改进角点检测方法。

    优化

    为了提升检测效果,使用了一个额外的非最大值抑制步骤,作用是排除掉紧邻的 Harris 角点。因此,Harris 角点不仅要有高于指定阈值的评分,还必须是局部范围内的最大值。为了检查这个条件,对 Harris 使用了膨胀运算,膨胀运算会在邻域中把每个像素值替换成最大值,因此只有局部最大值的像素是不变的。

    文件 harrisDetector.h

    #include<iostream>
    using namespace std;
    #include<opencv2/opencv.hpp>
    #include<opencv2/highgui.hpp>
    using namespace cv;
    
    class HarrisDetector{
    
    private:
        // 32 位浮点数型的角点强度图像
        Mat cornerStrength;
        // 32 位浮点数型的阈值化角点图像
        Mat cornerTh;
        // 局部最大值
        Mat localMax;
        // 平滑导数的邻域尺寸
        int neighborhood;
        // 梯度计算的口径
        int aperture;
        // Harris 参数
        double k;
        // 阈值计算的最大强度
        double maxStrength;
        // 计算得到的阈值(内部)
        double threshold;
        // 非最大值抑制的邻域尺寸
        int nonMaxSize;
        // 非最大值抑制的内核
        Mat kernel;
    
    public:
        Mat getCornerTh(){return cornerTh;}
    
        HarrisDetector() : neighborhood(3), aperture(3), k(0.01), maxStrength(0.0), threshold(0.01), nonMaxSize(3){
            // 创建用于非最大值一直的内核
            setLocalMaxWindowSize(nonMaxSize);
        }
        // 计算 Harris 角点
        void detect(const Mat& image);
        // 用 Harris 值得到角点分布图
        Mat getCornerMap(double qualityLevel);
        // 用 Harris 得到特征点
        void getCorners(vector<Point>& points, double qualityLevel);
        // 用角点分布图得到特征点
        void getCorners(vector<Point>& points, const Mat& cornerMap);
        // 在特征点的位置画圆形
        void drawOnImage(Mat& image, const vector<Point>& points, Scalar color=Scalar(0, 0, 255), int radius=3, int thickness=1);
    
        void setLocalMaxWindowSize(int nonMaxSize);
    };
    

    文件 harrisDetector.cpp

    #include "harrisDetector.h"
    
    
    /**
     * 计算 Harris 角点
     * */
    void HarrisDetector::detect(const Mat& image){
        // 计算 Harris
        cornerHarris(image, cornerStrength, neighborhood, aperture, k);
        // 计算内部阈值
        minMaxLoc(cornerStrength, 0, &maxStrength);
        // 检测局部最大值
        Mat dilated; // 临时图像
        dilate(cornerStrength, dilated, Mat());
        compare(cornerStrength, dilated, localMax, CMP_EQ);
    }
    
    /**
     * 用指定的阈值获得特征点
     * */
    Mat HarrisDetector::getCornerMap(double qualityLevel){
        Mat cornerMap;
        // 对角点强度阈值化
        threshold = qualityLevel * maxStrength;
        cv::threshold(cornerStrength, cornerTh, threshold, 255, THRESH_BINARY);
        // 转换成 8 位图像
        cornerTh.convertTo(cornerMap, CV_8U);
        // 非最大值抑制
        bitwise_and(cornerMap, localMax, cornerMap);
        return cornerMap;
    }
    
    void HarrisDetector::getCorners(vector<Point>& points, double qualityLevel){
        // 获得角点分布
        Mat cornerMap = getCornerMap(qualityLevel);
        // 获得角点
        getCorners(points, cornerMap);
    }
    
    void HarrisDetector::getCorners(vector<Point>& points, const Mat& cornerMap){
        // 迭代遍历,得到所有特征
        for(int y = 0; y < cornerMap.rows; y++){
            const uchar* rowPtr = cornerMap.ptr<uchar>(y);
            for(int x = 0; x < cornerMap.cols; x++){
                // 如果它是一个特征点
                if (rowPtr[x]){
                    points.push_back(Point(x, y));
                }
            }
        }
    }
    
    void HarrisDetector::drawOnImage(Mat& image, const vector<Point>& points, Scalar color, int radius, int thickness){
        for(int i = 0; i < points.size(); i++){
            circle(image, points[i], 3, color, thickness);
        }
    }
    
    void HarrisDetector::setLocalMaxWindowSize(int nonMaxSize){
        kernel = getStructuringElement(MORPH_RECT, Size(nonMaxSize, nonMaxSize));
    }
    

    文件 main.cpp

    #include "harrisDetector.h"
    
    
    int main(){
        Mat image = imread("test.jpg");
        Mat imageGray;
        cvtColor(image, imageGray, COLOR_BGR2GRAY);
        // 创建 Harris 检测器实例
        HarrisDetector harris;
        // 计算 Harris 值
        harris.detect(imageGray);
        // 检测 Harris 角点
        vector<Point> pts;
        harris.getCorners(pts, 0.02);
        // 画出 Harris 角点
        harris.drawOnImage(image, pts);
        // 保存画出角点的图像
        imwrite("harris.jpg", image);
        return 0;
    

    效果如下:

    在这里插入图片描述

    尽管引入了局部最大值这个条件,兴趣点仍不会在图像中均匀分布,而是聚集在高度纹理化的位置。接下来介绍 OpenCV 的一个角点检测优化,可以让兴趣点均匀分布在图像中。

    Shi-Tomasi 角点检测

    实现原理

    通过限制两个兴趣点之间的最短距离解决特征点聚集的问题,使角点在图像中的分布更加均匀。从 Harris 值最强的点开始(即具有最大的最低特征值),只允许一定距离之外的点成为兴趣点。在 OpenCV 中用 good-features-to-track(GFTT)实现这个算法。这个算法得名于它检测的特征非常适合作为视觉跟踪程序的起始集合,可以使用类 从cv::GFTTDetector ,也可以使用方法 cv::goodFeaturesToTrack 做角点检测。

    在内部,cv::goofFeaturesToTrack()cv::GFTTDetector() 有一定特定的阶段:自相关矩阵 C 的计算、该矩阵的分析以及应用的某种阈值。关键步骤使用函数 cv::cornerharris()cv::cornerMinEigenVal() 完成,这两个函数的参数完全类似于函数 cv::goodFeatureTotrack() ,首先使用 Harris 使用的特征值填充 dst,第二次使用 Shi 和 Tomasi 使用的特征值填充 dst,即自相关矩阵 C 的最小特征值。

    GFTTDetector 使用方法如下:

    // 计算适合跟踪的特征
    vector<KeyPoint> keypoints;
    // GTFF 检测器
    Ptr<GFTTDetector> ptrGFTT = GFTTDetector::create(500, 0.01, 10);
    // 检测 GFTT
    ptrGFTT->detect(image, keypoints);
    

    首先使用特定的静态函数(cv::GFTTDetector::create)创建特征检测器,并初始化参数。除了质量等级阈值和兴趣点间的最小距离,该函数还需要提供允许返回的最大点数(这些点是按照强度排序的)。函数返回一个执行检测器实例的智能指针。构建完这个实例后,就可以调用检测方法了。其中 cv::KeyPoint 类,封装了每个检测到的特征点的属性,可以使用 drawKeypoints 画出关键点。对于 Harris 角点来说,只与关键点位置和它的反馈强度有关。

    方法goodFeaturesToTrack 见以下 OpenCV 函数:

    OpenCV 函数

    cv::GFTTDetector::create

    static Ptr<GFTTDetector> cv::GFTTDetector::create (
    		    int			maxCorners = 1000,
    			double		qualityLevel = 0.01,
    			double 		minDistance = 1,
    			int			blockSize = 3,
    			bool		useHarrisDetector = false,
    			double 		k = 0.04 
    	) 		
    static Ptr<GFTTDetector> cv::GFTTDetector::create (
    			int 		maxCorners,
    			double		qualityLevel,
    			double		minDistance,
    			int			blockSize,
    			int			gradiantSize,
    			bool		useHarrisDetector = false,
    			double		k = 0.04 
    	) 	
    //Python:
    retval = cv.GFTTDetector_create([,maxCorners[,qualityLevel[,minDistance[, blockSize[,useHarrisDetector[, k]]]]]])
    retval = cv.GFTTDetector_create(maxCorners, qualityLevel, minDistance, blockSize, gradiantSize[, useHarrisDetector[, k]])
    

    OpenCV 特征检测公共接口定义了一个虚拟类 cv::Feature2DGFTTDetector 继承于这个虚拟类,它可以确保其他类包含以下格式的 detect 方法:

    detect

    void detect(InputArray image,
                CV_OUT std::vector<KeyPoint>& keypoints,
                InputArray mask=noArray() );
    void detect(InputArrayOfArrays images,
                CV_OUT std::vector<std::vector<KeyPoint> >& keypoints,
                InputArrayOfArrays masks=noArray() );
    

    第二个方法,可以检测多个图像的关键点。

    参数 解释
    image 要检测的图像
    keypoints 检测到的关键点,在方法的第二个变体中,keypoints[i] 是在 images[i] 中检测到的一组关键点
    mask 指定在哪里寻找关键点的掩码(可选)。 它必须是一个8位整数矩阵,在感兴趣的区域中具有非零值。

    drawKeypoints

    void cv::drawKeypoints( InputArray			image,
    						const std::vector< KeyPoint > &	keypoints,
    						InputOutputArray	outImage,
    						const Scalar &		color = Scalar::all(-1),
    						int					flags = DrawMatchesFlags::DEFAULT 
    	) 		
    //Python:
    outImage = cv.drawKeypoints(image, keypoints, outImage[, color[, flags]])
    
    参数 解释
    image 源图像
    keypoints 源图像的关键点
    outImage 输出图像,它的内容取决于 flags,flags 定义了在输出图像中绘制的内容。
    color 关键点的颜色
    flags 设置绘制方式。

    使用负数作为关键点的颜色,画每个圆时会随机选取不同的颜色。

    flags:

    • DEFAULT = 0
      将创建输出图像矩阵,即可以重用输出图像的现有内存。将绘制两个源图像,匹配项和单个关键点。 对于每个关键点,只会绘制中心点(没有围绕关键点的圆以及关键点的大小和方向)。
    • DRAW_OVER_OUTIMG = 1
      将不创建输出图像矩阵。匹配项将在现有的输出图像上进行绘制。
    • NOT_DRAW_SINGLE_POINTS = 2
      不会绘制单个关键点。
    • DRAW_RICH_KEYPOINTS = 4
      对于每个关键点,将在关键点周围绘制带有关键点大小和方向的圆圈。

    Note : For Python API, flags are modified as cv2.DRAW_MATCHES_FLAGS_DEFAULT, cv2.DRAW_MATCHES_FLAGS_DRAW_RICH_KEYPOINTS, cv2.DRAW_MATCHES_FLAGS_DRAW_OVER_OUTIMG, cv2.DRAW_MATCHES_FLAGS_NOT_DRAW_SINGLE_POINTS

    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 
    )		
    // Python:
    corners	=cv.goodFeaturesToTrack(image, maxCorners, qualityLevel, minDistance[, corners[, mask[, blockSize[, useHarrisDetector[, k]]]]])
    corners	= cv.goodFeaturesToTrack(	image, maxCorners, qualityLevel, minDistance, mask, blockSize, gradientSize[, corners[, useHarrisDetector[, k]]])
    
    
    参数 解释
    image 输入图像,8位或32位单通道图像
    corners 检测到的角点的输出向量
    maxCorners 角点的最大数量
    qualityLevel 角点检测可接受的最小特征值,其实实际用于过滤角点的最小特征值是 qualityLevel 与图像中最大特征值的乘积。所以 qualityLevel 通常不会超过1(常用的值为0.10 或 0.01)。而检测完所有的角点后,还要进一步剔除掉一些距离较近的角点
    minDistance 角点之间的最小距离,此参数用于保证返回的角点之间的距离不小于 minDistance 个像素
    mask 可选参数,表示感兴趣区域,有默认值 noArray()。若此参数非空(需为 CV_8UC1 类型,且和第一个参数 image 有相同的尺寸),便用于指定角点检测区域。
    blockSize 默认值3,计算导数自相关矩阵时指定的邻域范围
    useHarrisDetector 默认值 false,指示是否使用 Harris 角点检测
    k 默认值 0.04,为用于设置 Hessian 自相关矩阵行列式的相对权重的权重系数

    C++示例

    
    #include<iostream>
    using namespace std;
    
    #include<opencv2/opencv.hpp>
    #include<opencv2/highgui.hpp>
    using namespace cv;
    
    
    void myHarris(Mat& image){
        // 计算适合跟踪的特征
        vector<KeyPoint> keypoints;
        // GTFF 检测器
        Ptr<GFTTDetector> ptrGFTT = GFTTDetector::create(500, 0.01, 10);
        // 检测 GFTT
        ptrGFTT->detect(image, keypoints);
    	// 画出关键点    
    	drawKeypoints(image, keypoints, image, Scalar::all(-1), DrawMatchesFlags::DRAW_OVER_OUTIMG);
    
        imwrite("harrisThGFTT.jpg", image);
    }
    
    void myHarris2(Mat& image){
        vector<Point> corner;
        goodFeaturesToTrack(image, corner, 500, 0.01, 10);
        for (int i = 0; i < corner.size(); i++){
            circle(image, corner[i], 3, Scalar::all(-1), 1);
        } 
    }
    
    int main(){
        Mat image = imread("test.jpeg");
        Mat imageGray;
        cvtColor(image, imageGray, COLOR_BGR2GRAY);
        myHarris(imageGray);
        return 0;
    }
    

    效果如下:

    在这里插入图片描述

    由于需要让兴趣点按照 Harris 评分排序,因此该检测方法的复杂度有所提高,但是它也明显改进了兴趣点在整幅图像中的分布情况。此函数还有一个可选的标志,该标志要求在检测 Harris 角点时,采用经典的角点评分定义(使用协方差矩阵的行列式值和迹)。

    FAST 角点检测

    Harris 算子对角点(或者更通用的兴趣点)做出了规范的数学定义,该定义基于强度值在两个互相垂直的方向上的变化率。但是它需要计算图像的导数,而计算导数是非常耗时的。尤其要注意的是,检测兴趣点通常只是更复杂的算法中的第一步。

    本篇文章将介绍另一种特征点算子,叫做 FAST(Features from Accelerated Segment Test,加速分割测试获得特征)。这种算子专门用来快速检测兴趣点——只需对比几个像素,就可以判断它是否为关键点。

    实现原理

    跟 Harris 检测器的情况一样,FAST 特征算法源于“什么构成了角点”的定义。FAST 对角点的定义基于候选特征点周围的图像强度值。以某个点为中心做一个圆,根据原上的像素值判断该点是否为关键点。如果存在这样一段圆弧,它的连续长度超过周长的 3/4,并且它上面所有像素的强度值都与圆心的强度值明显不同(全部更暗或更亮),那么就认定这是一个关键点。

    这种测试方法非常简单,计算速度也很快。而且在它的原始公式中,算法还用了一个技巧来进一步提高处理速度。如果我们测试圆周上相隔 90 度的四个点(例如取上、下、左、右四个位置),就很容易证明:为了满足前面的条件,其中必须有三个点都比圆心更亮或都比圆心更暗。

    如果不满足该条件,就可以立即排除这个点,不需要检查圆周上的其他点。这种方法非常高效,因为在实际应用中,图像中大部分像素都可以用这种“四点比较法”排除。

    从概念上讲,用于检查像素的圆的半径作为方法的一个参数。但是根据经验,半径为 3 时可以得到好的结果和较高的计算效率。因此需要在圆周上检查 16 个像素,如下图所示。

    在这里插入图片描述

    这里用来预测试的像素时1、5、9 和 13,至少需要 9 个比圆心更暗(或更亮)的连续像素。这种设置通常称为 FAST-9 角点检测器,也是 OpenCV 默认采用的方法。你可以在构建检测器实例时指定 FAST 检测器的类型。

    一个点与圆心强度值的差距必须达到一个指定的值,才能被认为是明显更暗或更亮;这个值就是创建检测器实例时指定的阈值参数。这个阈值越大,检测到的角点数量就越少。

    至于 Harris 特征,通常最好在发现的角点上执行非最大值抑制。因此,需要定义一个角点强度的衡量方法。有多种衡量方法可供选择,可以通过以下方法获取角点强度——计算中心点像素与认定的连续圆弧上的像素的差值,然后将这些差值的绝对值累加,就能得到角点强度。可以从 cv::KeyPoint 实例的 response 属性获取角点强度。

    用这个算法检测兴趣点的速度非常快,因此十分适合需要优先考虑速度的应用,包括实时视觉跟踪、目标识别等,它们需要在实时视频流中跟踪或匹配多个点。

    OpenCV 函数

    FastFeatureDetector

    static Ptr<FastFeatureDetector> cv::FastFeatureDetector::create(
      		int		threshold = 10,
          	bool	nonmaxSuppression = true,
          	int		type = FastFeatureDetector::TYPE_9_16 
    )		
    // Python:
    retval = cv.FastFeatureDetector_create([, threshold[, nonmaxSuppression[, type]]])
    

    参数解释

    参数 解释
    threshold 与圆心强度值的阈值
    nonmaxSuppression 非最大值抑制
    type 圆的大小

    每种类型都指定圆的周长和该圆的中心被认为是关键点所需的临近点的数量,type:

    • TYPE_5_8 = 0
      圆半径为1,表示表示 8 点中的 5 点应该比中心点都更亮或者都要暗
    • TYPE_7_12 = 1
      圆半径为2,表示表示 12 点中的 7 点应该比中心点都更亮或者都要暗
    • TYPE_9_16 = 2
      圆半径为3,表示 16 点中的 9 点应该比中心点都更亮或者都要暗

    C++示例

    void myFAST(Mat& image){
        // 关键点的向量
        vector<KeyPoint> keypoints;
        // FAST 特征检测器,阈值为 40
        Ptr<FastFeatureDetector> ptrFAST = FastFeatureDetector::create(40);
        // 检测关键点
        ptrFAST->detect(image, keypoints);
        // 画出关键点
        drawKeypoints(image, keypoints, image, Scalar::all(-1), DrawMatchesFlags::DRAW_OVER_OUTIMG);
    }
    
    int main()
    {
        string outDir = "./";
        Mat image = imread("img7.jpg");
        if (image.empty()) {
            cout << "could not load image..." << endl;
            return -1;
        }
        myFAST(image);
        imwrite(outDir + "FAST.jpg", image);
        return 0;
    }
    

    效果图

    在这里插入图片描述

    优化

    应用程序不同,检测特征点时采用的策略也不同。

    例如在事先明确兴趣点数量的情况下,可以对检测过程进行动态适配。简单的做法就是采用范围较大的阈值检测出很多兴趣点,然后从中提取出 n 个强度最大的。为此可以使用这个标准 C++ 函数 std::nth_element()

    if(numberOfPoint < keypoints.size())
    	std::nth_element(
      				keypoints.begin(),
                    keypoints.begin()+numberOfPoints,
                    keypoints.end(),
                    [](cv::KeyPoint& a, cv::KeyPoint& b){
                    return a.response > b.response;})
    

    函数解释

    函数中 keypoints 类型是 std::vector,表示检测到的兴趣点, numberOfPoints 是需要的兴趣点数量。最后一个参数 lambda 比较器,用于提取最佳的兴趣点。如果检测到的兴趣点太少(少于需要的数量),那就要采用更小的阈值,但是阈值太宽松又会加大计算量,所以需要权衡利弊,选取最佳的阈值。

    检测图像特征点时还会遇到一种情况,就是兴趣点分布很不均匀。keypoint 通常会聚集在纹理较多的区域。对此有一种常用的处理方法,就是把图像分割成网格状,对每个小图像进行单独检测。

    C++示例

    void myFAST2(Mat& image){
        int totalPoints = 100;  // 总共 100 个关键点
        int cols = image.cols;
        int rows = image.rows;
        int vstep = 10;					// 垂直分 10 份
        int hstep = 10;					// 水平分 10 份
        int hsize = cols / hstep;
        int vsize = rows / vstep;
        int subtotal = totalPoints / (vstep * hstep); // 每个子块中的关键点
        Mat imageROI;
        // 关键点
        vector<KeyPoint> keypoints;
        vector<KeyPoint> gridpoints;
        // FAST 特征检测器
        Ptr<FastFeatureDetector> ptrFAST = FastFeatureDetector::create(40);
        // 检测每个网格
        for(int i = 0; i < vstep; i++){
            for(int j = 0; j < hstep; j++){
                // 在当前网格创建ROI
                imageROI = image(Rect(j*hsize, i*vsize, hsize, vsize));
    //            imageROI = image(Range(), Range());
                // 在网格中检测关键点
                gridpoints.clear();
                ptrFAST->detect(imageROI, gridpoints);
                // 获取强度最大的 FAST 特征
                auto itEnd(gridpoints.end());
                if(gridpoints.size() > subtotal){
                    // 选取最强的特征
                    nth_element(gridpoints.begin(), gridpoints.begin()+subtotal, gridpoints.end(), [](cv::KeyPoint& a, cv::KeyPoint& b){return a.response > b.response;});
                    itEnd = gridpoints.begin() + subtotal;
                }
                for(auto it = gridpoints.begin(); it != itEnd; ++it){
                    // 转成图像上的坐标
                    it->pt += cv::Point2f(j*hsize, i*vsize);
                    keypoints.push_back(*it);
                }
            }
        }
        // 画出关键点
        drawKeypoints(image, keypoints, image, Scalar::all(-1), DrawMatchesFlags::DRAW_OVER_OUTIMG);
    }
    
    int main()
    {
        string outDir = "./";
        Mat image = imread("img7.jpg");
        if (image.empty()) {
            cout << "could not load image..." << endl;
            return -1;
        }
        Mat imageGray;
        cvtColor(image, imageGray, COLOR_BGR2GRAY);
        myFAST2(image);
        imwrite(outDir + "FAST2.jpg", image);
        return 0;
    
    }
    

    网格优化

    利用 ROI 对每个网格的小图像进行关键点检测,这样的到的关键点分布较为均匀。


    这一篇文章介绍的特征检测器可以比较好的解决方向不变性问题,即图像旋转后仍能检测到相同的特征点。下一篇文章介绍可以解决尺度不变性问题的方法,不仅在任何尺度下拍摄的物体都能检测到一致的关键点,而且每个被检测的特征点都对应一个尺度因子。

    展开全文
  • 基于光强比较的角点检测,直接比较光强(像素灰度值),而不计算梯度。所以实时性更好,所需的存储空间更小。 SUSAN 角点检测 SUSAN 全称Smallest univalue segment assimilating nucleus,最小核同值区。提出者...
  • Harris角点检测

    2020-12-21 16:23:47
    Harris角点检测

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 5,440
精华内容 2,176
关键字:

角点检测