xfeature2d模块是contrib中对于feature2d模块的扩展,该模块用于2D特征检测,描述与匹配。
研究如何使用OpenCV中的特征点检测子,特征点描述子以及匹配框架。
- 在计算机视觉中,我们通常需要寻找两张图上的匹配关键点。为什么?因为一旦我们知道了两张图是相关联的,我们就可以使用 *both 图像来提取它们中的信息。
- 匹配关键点 是指在场景中可以很容易识别出来的 特性 . 这些特性就是这里所说的 特征 。
- 因此,特征应该有什么样的特性呢? 应该具有 可识别的独一无二性。
- 图像特征类型:
- 边缘,(已经在前面见过了边缘检测和图像轮廓)
- 角点 (感兴趣关键点)
- 斑点(Blobs) (感兴趣区域)
角点通常被定义为两条边的交点,或者说,角点的局部邻域应该具有两个不同区域的不同方向的边界。角点检测(Corner Detection)是系统中获取图像特征的一种方法,广泛应用于运动检测、图像匹配、视频跟踪、三维重建和目标识别等,也可称为特征点检测。
1 角点的类型
1.1 角点检测算法的基本思想
使用一个固定窗口在图像上进行任意方向上的滑动,比较滑动前与滑动后两种情况,窗口中的像素灰度变化程度,如果存在任意方向上的滑动,都有着较大灰度变化,那么我们可以认为该窗口中存在角点。
目前,角点检测算法还不是十分完善,许多算法需要依赖大量的训练集和冗余数据来防止和减少错误的特征的出现。对于角点检测算法的重要评价标准是:其对多幅图像中相同或者相似特征的检测能力,并且能够应对光照变化、或者图像旋转等影响。
关于角点的具体描述可以有几种:
- 一阶导数(即灰度的梯度)的局部最大所对应的像素点;
- 两条及两条以上边缘的交点;
- 图像中梯度值和梯度方向的变化速率都很高的点;
- 角点处的一阶导数最大,二阶导数为零,指示物体边缘变化不连续的方向。
1.2 三类角点检测法
- 基于二值图像的角点检测;
- 基于轮廓曲线的角点检测;
- 基于灰度图像的角点检测:基于梯度、基于模板和基于模板和梯度组合三类方法;常见的基于模板的角点检测算法有:Kitchen-Rosenfeld角点检测算法,Harris角点检测算法,KLT角点检测算法及SUSAN角点检测算法。基于模板的方法主要是考虑像素领域点灰度的变化,即亮度的变化。
2 Harris角点检测
2.1 原理
角点原理来源于人对角点的感性判断,即图像在各个方向灰度有明显变化。算法的核心是利用局部窗口在图像上进行移动判断灰度发生较大的变化,所以此窗口用于计算图像的灰度变化为(3*3的窗口,这和计算梯度变化算子一样): ,
。根据下面三幅图可以清晰理解角点检测的过程:
当一个窗口在图像上移动时:
- 如图(a),窗口在各个方向上都没有变化,则认为窗口区域为平滑区域。
- 如图(b),窗口在某个方向上没有变化,另一个方向上有明显变化,那么,这块区域可能存在边缘。
- 如图(c),窗口在各个方向上灰度发生了较大的变化,那么,这块区域可能存在角点。Harris角点检测正是利用了这个直观的物理现象,通过窗口在各个方向上的变化程度,决定是否为角点。
根据算法思想,构建数学模型,计算移动窗口的的灰度差值。将图像窗口平移[u,v] 产生的灰度变化E(u,v) 为:
其中 为窗口函数,
为平移后的图像灰度,
为图像灰度
为了减小计算量,利用泰勒级数进行简化公式:
由: ,得到:
其中 ,
是图像在x,y方向上的导数(在opencv中,可以用cv2.Sobel()计算得到)。
于是对于局部微小的移动量[u,v] ,可以得到下面的近似表达:
其中M 是2×2 矩阵,可由图像的导数求得:
,
上式中w 函数表示窗口函数,M 矩阵为偏导数矩阵。对于矩阵可以进行对称矩阵的变化,假设利用矩阵M 两个特征值λmax,λmin 进行替代,其几何含义类似下图中的表达。在几何模型中通过判断两个特征值的大小,来判定像素的属性。
通过矩阵M 两个特征值λmax,λmin 的大小,对图像点进行分类,如下图
M 为梯度的协方差矩阵,在实际应用中为了能够应用更好的编程,定义了角点响应函数R :
通过判定R 大小来判断像素是否为角点。R 取决于M 的特征值,对于角点|R| 很大,平坦的区域|R| 很小,边缘的R 为负值。Harris角点检测算法就是对角点响应函数R进行阈值处理:R > threshold,即提取R的局部极大值。
2.2 cv2. cornerHarris() Harris角点检测函数
函数原型:
dist=cv2.cornerHarris(src[,dst],blocksize,ksize,k,borderType)
参数:
- src:输入的单通道8位图像或者数据类型为float32图像。
- dst:用于存放Harris角点检测的输出结果,和源图像有一样的尺寸和类型。
- blocksize:滑块窗口(领域)的尺寸(角点检测中要考虑的邻域大小)
- ksize: Sobel求导时使用的核大小。默认值为3.
- k:Harris中间参数,经验值式0.04~0.06
- borderType:边界类型。默认值BORDER_DEFAULT。
返回值:
dist:数组,用于保存Harris角点检测结果,32位单通道
3 Shi-Tomasi角点检测
3.1 原理
Shi-Tomasi 算法是Harris 算法的改进。Harris 算法最原始的定义是将矩阵 M 的行列式值与 M 的迹相减,再将差值同预先给定的阈值进行比较。后来Shi 和Tomasi 提出改进的方法,若两个特征值中较小的一个R=min(λ1,λ2) 大于最小阈值,则会得到强角点。
Shi-Tomasi 的方法比较充分,并且在很多情况下可以得到比使用Harris 算法更好的结果。
从上图中可以看出只有当 都大于最小值时才被认为是角点。
3.2 cv2. goodFeatureToTrack() Shi-Tomasi角点检测函数
函数原型:
corners =cv2.goodFeatureToTrack(image,maxCorners,qualityLevel,minDistance)
参数:
- image:输入的图像,8bit或32位单通道灰度图像的Mat矩阵。
- maxCorners:返回最大的角点数,是最有可能的角点数。如果这个参数不大于0,那么表示没有角点数的限制。按照角点强度降序排序,超过maxCorners的角点将被舍弃。
- qualityLevel:品质因子。筛选角点,品质因子越大,特征值越大的越好,得到的角点越少。检测出的角点强度值小于品质因子的会被抛弃(角点特征值小于qualityLevel*最大特征值的点将被舍弃;)。
- minDistance:角点之间最小的欧式距离。在这个距离范围内判断哪个品质因子最好,只要这一个角点。避免得到相邻点,以像素为单位;
- mask:检测区域。如果图像不是空的(它需要具有CV_8UC1类型和与图像相同的大小),它指定检测角的区域。
- blockSize:是协方差矩阵滤波的窗口大小。
- gradientSize:为sobel算子求微分的窗口的大小
- useHarrisDetector:选择是否采用Harris角点检测,默认是false.
- k:Harris检测的自由参数。
返回值:
- corners:搜索到的角点,在这里所有低于质量等级的角点被排除掉,然后把合格的角点按质量等级qualityLevel排序后,将较好的角点附近(小于最小欧式距离minDistance)的角点删掉,最后找到maxCorners个角点返回。位置点向量,用于保存检测到的角点坐标,32位单通道
4 示例
import numpy as np
import cv2
import matplotlib.pyplot as plt# 1 读图
img1 = cv2.imread('C:/Users/xxx/Downloads/arrow.png')
image1 =img1.copy()
image11 =img1.copy()
img2 = cv2.imread('C:/Users/xxx/Downloads/lena.jpg')
image2 =img2.copy()
image22 =img2.copy()
# 2 转化为灰度图
gray1 = cv2.cvtColor(img1,cv2.COLOR_BGR2GRAY)
gray2 = cv2.cvtColor(img2,cv2.COLOR_BGR2GRAY)# 3 数据类型转化为float32
gray1 = np.float32(gray1)
gray2 = np.float32(gray2)# 4 Harris角点检测
res1 = cv2.cornerHarris(gray1,blockSize=2,ksize=3,k=0.04)
res2 = cv2.cornerHarris(gray2,blockSize=2,ksize=3,k=0.04)# 5 Shi-Tomasi角点检测
res11 = cv2.goodFeaturesToTrack(gray1,1000,0.01,10)
res22 = cv2.goodFeaturesToTrack(gray2,1000,0.01,10)# 6 处理Harris角点检测结果,将检测大于0.001的认为是角点,并分别设置其颜色为黄色
image1[res1 > 0.001*res1.max()] = [0,255,255]
image2[res2 > 0.001*res2.max()] = [0,255,255]# 7 处理Shi-Tomasi角点检测结果
for i in res11:
x,y = i.ravel()
cv2.circle(image11,(int(x),int(y)),2,(0,255,255),-1)
for i in res22:
x,y = i.ravel()
cv2.circle(image22,(int(x),int(y)),2,(0,255,255),-1)# 8 可视化
names = ['original','cornerHarris','Shi-Tomasi','original','cornerHarris','Shi-Tomasi']
images = [img1,image1,image11,img2,image2,image22]plt.figure(figsize=(19.2,9.6),dpi=500)
for i in range(2):
for j in range(3):
# plt.imshow(img[:,:,::-1]),将cv2的bgr格式转换为rgb格式显示
plt.subplot(2,3,i*3+j+1),plt.imshow(images[i*3+j][:,:,::-1])
plt.title(names[i*3+j],fontsize=30), plt.xticks([]), plt.yticks([])
num=i*3+j
if num >= len(names)-1:
breakplt.show()
结果如下: