CV笔记8:角点特征之Harris角点检测算法(基于python-opencv实现)


一、背景介绍

1.1 什么是特征

在数据挖掘、计算机视觉等领域经常会提起“特征”一词,那么究竟什么才是特征?数据挖掘中我们可以称被分析数据集的某一属性(一行为一个样本,一列为一个属性)为特征,因为它反映了该数据集的某一种特性,是该数据集的一种固有标识。在计算机视觉中,一张图像的特征其实也有类似的概念,这种特征也能够描述这张图像的全部(或局部)内容或含义。图像特征可以是①有意义的图像区域,该区域具有独特的特征和良好的识别性;也可以是②对图像信息的一种数据抽取和表示。
针对①可以是图像中的角点、边缘、斑点、直线、曲线及高密度区域等。大多数特征检测算法都会涉及图像的角点、边和斑点的识别,也有一些涉及脊向的概念,可以认为脊向是细长物体的对称轴,例如识别图像中的一条路。角点和边都好理解,那什么是斑点呢?斑点通常是指与周围有着颜色和灰度差别的区域。在实际地图中,往往存在着大量这样的斑点,如一颗树是一个斑点,一块草地是一个斑点,一栋房子也可以是一个斑点。由于斑点代表的是一个区域,相比单纯的角点,它的稳定性要好,抗噪声能力要强,所以它在图像配准上扮演了很重要的角色。
针对上述的②,在文章CV笔记7:计算机视觉的通俗理解 第二节中较通俗的解释了对图像特征的理解。

1.2 为什么做特征检测

图像的特征应用很广泛,我们通过图像特征能过够将实现图像的分类、目标检测、图像拼接等。而特征就需要特征检测来提出。我们直接使用下面的例子进行说明。
CV笔记8:角点特征之Harris角点检测算法(基于python-opencv实现)这里有两张同一山峰不同部位的图像,我们怎么能够将上面的两张图像拼接成一张图像呢?对于我们人来说,将两张图像相同的位置重合折叠在一起就可以了,就像下图,但是对于计算机要怎么实现?
CV笔记8:角点特征之Harris角点检测算法(基于python-opencv实现)
对于计算机来说,并不能像人一样直接对图像进行拼接,而是要根据一些关键点对图像进行处理。我们可以通过一些角点来让计算机认识并拼接图像,就像下面图片中画出的一样,我们找到图像中的一些关键点(角点),并让两张图像中相同的角点处进行拼接。这样计算机也能够像人一样实现图像的拼接了。
CV笔记8:角点特征之Harris角点检测算法(基于python-opencv实现)
上面的例子只是用到了角点特征,其实其他的类似特征,如边缘、线等都能用在不同的场景中。所以,最终达到的目标是:通过特征检测我们实现的就是能够让计算机“读懂”图像,将人类看到的视觉信息转化成计算机能够识别和处理的定量形式,这也就是一种图像特征提取方式。

1.3 角点特征

1.3.1 角点定义

我们在上面图像拼接的说明案例中提到了角点特征,本文也重点介绍角点特征的提取过程。角点一般多为图像轮廓之间的交点,对于同一场景,即使视角发生变化,也通常具备稳定性质的特征,在角点附近区域的像素点无论在梯度方向上还是其梯度幅值上有着较大变化。可以看出,在对图像求导后,极值点处往往就是角点的位置。
CV笔记8:角点特征之Harris角点检测算法(基于python-opencv实现)
再通过图像理解,就是在一幅图像中,如上图,我们考虑图像上的一个很小的窗口。在平坦的区域时,当小窗口进行任意方向的移动,窗口内的像素都没有太大灰度变化;在边缘区域时,当小窗口沿着边缘方向移动,窗口内的像素没有太大灰度变化,当沿着垂直边缘方向移动,窗口内的像素会发生跳变;在角点区域时,当小窗口沿任意方向移动,窗口内的像素都有明显的灰度变化。下图展示了不同类型的角点。
CV笔记8:角点特征之Harris角点检测算法(基于python-opencv实现)

1.3.2 角点优势

角点相对于其他的图像特征具有以下的优势:

  • 点特征属于局部特征,对遮挡有一定的鲁棒性
  • 通常图像中可以检测到成百上千的点特征,以量取胜
  • 点特征辨识度好,不同物体上的点容易区分
  • 点特征提取速度很快

二、Harris角点检测原理

2.1 算法思想

我们已经对角点有了初步的认识,下一步开始进行更深的数理推导,也就是Harris角点检测原理。算法的核心是利用局部窗口在图像上进行移动,判断灰度是否发生较大的变化。如果窗口内的灰度值(在梯度图上)都有较大的变化,那么这个窗口所在区域就存在角点。
这样就可以将 Harris 角点检测算法分为以下三步:

  • 当窗口(局部区域)同时向 xx (水平)和 yy(垂直) 两个方向移动时,计算窗口内部的像素值变化量 E(x,y)E(x,y)
  • 对于每个窗口,都计算其对应的一个角点响应函数RR
  • 然后对该函数进行阈值处理,如果 R>thresholdR>threshold,表示该窗口对应一个角点特征。

2.2 算法推导

我们仍然考虑图像中的一个小窗口,如下图:
CV笔记8:角点特征之Harris角点检测算法(基于python-opencv实现)

2.2.1 灰度变化描述

当窗口发生[u,v][u,v]移动时,那么滑动前与滑动后对应的窗口中的像素点灰度变化描述如下:
E(u,v)=(x,y)Ww(x,y)[I(x+u,y+v)I(x,y)]2E(u,v)=∑_{(x,y) € W}w(x,y)[I(x+u,y+v)−I(x,y)]^2参数解释:

  • [u,v][u,v]是窗口WW的偏移量;
  • (x,y)(x,y)是窗口WW所对应的像素坐标位置,窗口有多大,就有多少个位置;
  • I(x,y)I(x,y)是像素坐标位置(x,y)(x,y)的图像灰度值;
  • I(x+u,y+v)I(x+u,y+v)是像素坐标位置(x+u,y+v)(x+u,y+v)的图像灰度值;
  • w(x,y)w(x,y)是窗口函数,最简单情形就是窗口WW内的所有像素所对应的ww权重系数均为1.但有时候,我们会将w(x,y)w(x,y)函数设置为以窗口WW中心为原点的二元正太分布。如果窗口W中心点是角点时,移动前与移动后,该点在灰度变化贡献最大;而离窗口WW中心(角点)较远的点,这些点的灰度变化几近平缓,这些点的权重系数,可以设定小值,以示该点对灰度变化贡献较小,那么我们自然而然想到使用二元高斯函数来表示窗口函数;
    CV笔记8:角点特征之Harris角点检测算法(基于python-opencv实现)
    根据上述表达式,当窗口在平坦区域上移动,可以想象得到,灰度不会发生太大变化。E(u,v)=0E(u,v)=0;如果窗口处在纹理比较丰富的区域上滑动,那么灰度变化会很大。算法最终思想就是计算灰度发生较大变化时所对应的位置,当然这个较大是指任意方向上的滑动,并非单指某个方向。
    同时,我们应该注意:E(u,v)E(u,v)计算的是窗口WW[u,v][u,v]这一个方向移动所产生的灰度变化

2.2.2 E(u,v)E(u,v)公式化简

为了提高计算效率,利用泰勒级数展开对上述公式进行简化。
对于二维的泰勒展开式公式为:
f(x+u,y+v)f(x,y)+ufx(x,y)+vfy(x,y)f(x+u,y+v)≈f(x,y)+uf_x(x,y)+vf_y(x,y)则对于I(x+u,y+v)I(x+u,y+v)有:
I(x+u,y+v)=I(x,y)+uIx+vIyI(x+u,y+v)=I(x,y)+uI_x+vI_y其中IxI_xIyI_yII的微分(偏导),在图像中就是求xxyy 方向的梯度:
Ix=I(x,y)xI_x=\frac{∂I(x,y)}{∂x} Iy=I(x,y)yI_y=\frac{∂I(x,y)}{∂y} 那么有:
E(u,v)=(x,y)Ww(x,y)[I(x+u,y+v)I(x,y)]2E(u,v)=∑_{(x,y) € W}w(x,y)[I(x+u,y+v)−I(x,y)]^2 (x,y)Ww(x,y)[I(x,y)+uIx+vIyI(x,y)]2≈∑_{(x,y)€W}w(x,y)[I(x,y)+uI_x+vI_y−I(x,y)]^2 =(x,y)Ww(x,y)[u2Ix2+2uvIxIy+v2Iy2]=∑_{(x,y)€W}w(x,y)[u^2I^2_x+2uvI_xI_y+v^2I^2_y] =(x,y)Ww(x,y)[u,v][Ix2IxIyIyIxIy2][uv]=\sum_{(x,y) €{W}} w(x,y)[u,v] \left[\begin{matrix} I_x^2&I_xIy \\I_yI_x&I_y^2 \end{matrix}\right] \left[\begin{matrix}u\\v \end{matrix} \right] =[u,v]((x,y)Ww(x,y)[Ix2IxIyIyIxIy2])[uv]=[u,v] (\sum_{(x,y) €{W}} w(x,y) \left[\begin{matrix} I_x^2&I_xIy \\I_yI_x&I_y^2 \end{matrix}\right] )\left[\begin{matrix}u\\v \end{matrix} \right]从而可以得到:
E(u,v)=[u,v]M[uv]E(u, v) = [u, v]M\left[\begin{matrix}u\\v \end{matrix} \right]其中:
M=(x,y)Ww(x,y)[Ix2IxIyIyIxIy2][ACCB]R1[λ100λ2]RM=\sum_{(x,y) €{W}} w(x,y) \left[\begin{matrix} I_x^2&I_xIy \\I_yI_x&I_y^2 \end{matrix}\right]\Longrightarrow \left[\begin{matrix}A&C \\ C&B \end{matrix} \right] \Longrightarrow R^{-1}\left[\begin{matrix}\lambda_1&0 \\ 0& \lambda_2 \end{matrix} \right]R最后矩阵形式表达是把实对称矩阵对角化处理后的结果,可以把RR看成旋转因子,其不影响两个正交方向的变化分量。经对角化处理后,将两个正交方向的变化分量提取出来,就是 λ1λ_1λ2λ_2(特征值)。

2.2.3 矩阵MM的理解

虽然我们已经得到了E(u,v)E(u,v),但我们并不直接使用它来进行判断当前窗口是否含有角点。Harris角点检测而是通过对窗口内的每个像素的xx方向上的梯度与yy方向上的梯度进行统计分析,并结合了矩阵MM的性质进行角点判定的。
我们以IxI_xIyI_y为坐标轴,每个像素的梯度坐标可以表示成(Ix,Iy)(I_x,I_y),在此基础上针对平坦区域,边缘区域以及角点区域和斜边缘区域四种情形进行分析:
CV笔记8:角点特征之Harris角点检测算法(基于python-opencv实现)
针对上图中四种窗口区域,统计对应像素的梯度分布情况,得到下图,其中IxI_x为横轴IyI_y为纵轴:
CV笔记8:角点特征之Harris角点检测算法(基于python-opencv实现)
我们从上面能够观察到这几种区域的特点:

  • 平坦区域上的每个像素点所对应的(Ix,Iy)(I_x,I_y)坐标分布在原点附近,这是因为平坦区域像素梯度方向虽然各异,但是其幅值都不是很大,所以均聚集在原点附近
  • 边缘区域沿一个方向分布较散,至于是哪一个方向不能一概而论,这要视边缘在图像上的具体位置而定,如果边缘是水平或者垂直方向,那么IyI_y轴方向或者IxI_x方向上的数据分布就比较散
  • 角点区域的在xxyy方向上的梯度分布都比较散
    我们再回头看E(u,v)E(u,v),根据上面的推导,MM可以表示为如下的形式:[ACCB]\left[\begin{matrix}A&C \\ C&B \end{matrix} \right] 所以,我们可以将E(u,v)E(u,v)近似为二项函数:
    E(u,v)=Au2+2Cuv+Bv2E(u,v)=Au^2+2Cuv+Bv^2 其中:
    A=(x,y)Ww(x,y)Ix2A=∑_{(x,y)€W}w(x,y)∗I^2_xB=(x,y)Ww(x,y)Iy2B=∑_{(x,y)€W}w(x,y)∗I^2_yC=(x,y)Ww(x,y)IxIyC=∑_{(x,y)€W}w(x,y)∗I_xI_y二次项函数本质上就是一个椭圆函数。椭圆的扁率和尺寸是由其特征值λ1、λ2决定的,方向是由其特征向量决定的。以下图为例,设椭圆方程为:
    [u,v]M[uv]=1[u, v]M\left[\begin{matrix}u\\v \end{matrix} \right]=1CV笔记8:角点特征之Harris角点检测算法(基于python-opencv实现)
    可以看出MM的特征根决定了椭圆的长短轴的长度,对应的特征向量决定了椭圆的方向(因为椭圆的两个轴指向特征向量的方向)。所以,我们知道了求得的E(u,v)E(u,v)是可以通过椭圆的形式来表达的,我们对之前的数据集用椭圆形式表示,绘制的图像如下图所示:
    CV笔记8:角点特征之Harris角点检测算法(基于python-opencv实现)
    之所以能够使用椭圆描述上面的数据集,是因为矩阵MM本身的形式和协方差矩阵就有着千丝万缕的联系。我们将Ix,IyI_x ,I_y看成两个字段,假设窗口内有mm个像素点,也就是等价于有mm个样本,我们先计算每个字段的均值:
    Ix=i=1mIxi\overline{I_x}=∑_{i=1}^mI_{xi} Iy=i=1mIyi\overline{I_y}=∑_{i=1}^mI_{yi} 我们仍然使用(Ixi,Iyi)(I_{xi},I_{yi})表示样本(Ixi,Iyi)(I_{xi},I_{yi})去均值后的值,则由这mm个样本组成的矩阵为:
    X=[Ix1Ix2...IxmIy1Iy2...Iym]X=\left[\begin{matrix}I_{x1}&I_{x2}&...&I_{xm}\\I_{y1}&I_{y2}&...&I_{ym}\end{matrix}\right]则对应的协方差矩阵为:
    C=1mXXT=1mi=1m[Ix2IxIyIyIxIy2]C=\frac{1}{m}XX^T=\frac{1}{m}\sum_{i=1}^m\left[\begin{matrix} I_x^2&I_xIy \\I_yI_x&I_y^2 \end{matrix}\right]我们可以看到,上式中的CC中,我们先进行了各维的零均值化,这样各维所对应的随机变量的均值为0,协方差矩阵就大大简化,简化的最终结果就是矩阵MM(注意:在这里为了简化运算,我们先假设了MM矩阵中的权重系数w(x,y)=1w(x,y)=1,并且忽略了最后除mm样本数的操作)。到这里是否已经明白了我们的目的:我们是来分析图像导数数据的主要成分。
    先前我们已经对MM进行了对角化操作:
    M=(x,y)Ww(x,y)[Ix2IxIyIyIxIy2]R1[λ100λ2]RM=\sum_{(x,y) €{W}} w(x,y) \left[\begin{matrix} I_x^2&I_xIy \\I_yI_x&I_y^2 \end{matrix}\right]\Longrightarrow R^{-1}\left[\begin{matrix}\lambda_1&0 \\ 0& \lambda_2 \end{matrix} \right]R是否也让你想起了PCA中的操作?不明白可以复习一下PCA(注:协方差矩阵需要大家深刻理解一下)。所以再结合上面数据集分布图像,可以知道:
  • 如果两个字段(Ix,Iy)(I_x,I_y)所对应的特征值都比较大,说明像素点的梯度分布比较散,梯度变化程度比较大,符合角点在窗口区域的特点;
  • 如果是平坦区域,那么像素点的梯度所构成的点集比较集中在原点附近,因为窗口区域内的像素点的梯度幅值非常小,此时矩阵M的对角化的两个特征值比较小;
  • 如果是边缘区域,在计算像素点的xyx、y方向上的梯度时,边缘上的像素点的某个方向的梯度幅值变化比较明显,另一个方向上的梯度幅值变化较弱,其余部分的点都还是集中原点附近,这样MM对角化后的两个特征值理论应该是一个比较大,一个比较小,当然对于边缘,某些情况下致使计算出的特征值并不是都特别的大,但仍跟含有角点的窗口的分布情况具有不同。
    CV笔记8:角点特征之Harris角点检测算法(基于python-opencv实现)
    所以我们可以直接根据MM两个特征值的大小对图像点进行分类,如上图所示:
  • 特征值都比较大时,即窗口中含有角点;
  • 特征值一个较大,一个较小,窗口中含有边缘;
  • 特征值都比较小,窗口处在平坦区域;

2.2.4 角点响应函数RR

我们已经知道了什么样的窗口含有角点又或者是边缘等。在实际应用中,为了更好的应用到编程中,我们有定义了角点响应函数RR,通过判定RR大小来判断窗口是否有角点,即R>thresholdR>threshold时则有角点,thresholdthreshold是我们自定义的一个阈值。
最简单的一种是,角点应该满足基本性质:窗口最小的特征值尽量的大。此时定义R=λminR=\lambda_{min}
还有比上式更有效的角点相应函数:
R=det(M)α(trace(M))2k[0.04,0.06](1)R = det(M) - \alpha (trace(M))^2 \qquad{k \in [0.04,0.06]} \qquad{(1)} R=λ1kλ2k=0.05(2)R = \lambda_1 - k \lambda_2 \qquad{k = 0.05} \qquad{(2)} R=det(M)trace(M)=λ1λ2λ1+λ2(3)R = \frac{det(M)}{trace(M)}=\frac{\lambda_1\lambda_2}{\lambda_1+\lambda_2} \qquad{(3)}上面新给出了三种角点响应函数,我们以(1)进行讲解:
R=det(M)α(trace(M))2k[0.04,0.06]R = det(M) - \alpha (trace(M))^2 \qquad{k \in [0.04,0.06]} det(M)=λ1λ2det(M)=\lambda_1\lambda_2 trace(M)=λ1+λ2trace(M)=\lambda_1+\lambda_2 这里λ1,λ2λ_1,λ_2是矩阵MM的2个特征值,kk是一个指定值,这是一个经验参数,需要实验确定它的合适大小,通常它的值在0.04和0.06之间,它的存在只是调节函数的形状而已。
为什么可以使用这个函数进行判定呢?我们将其图像画出进行理解,图像如下,可以看出这个函数图形正好满足角点、边缘和平坦区域的特征。
CV笔记8:角点特征之Harris角点检测算法(基于python-opencv实现)
之后,我们根据需求设定RR的阈值,就可以进行角点的判断了。后续还可以增加很多处理,比如如果需要可以在3×3或者5×5的邻域进行非最大值抑制,则局部最大值点即为图像中的角点。

三、基于python-opencv实现Harris角点检测

参考

第十一节、Harris角点检测原理(附源码)