OpenCV-Python系列之SIFT尺度不变特征变换(二)
我们在上一个教程中谈到SIFT总共有四个步骤,之前已经讨论了一部分,今天我们将结束这一部分,我们现在接着上一个教程继续。
删除不好的极值点(特征点)
通过比较检测得到的DoG的局部极值点实在离散的空间搜索得到的,由于离散空间是对连续空间采样得到的结果,因此在离散空间找到的极值点不一定是真正意义上的极值点,因此要设法将不满足条件的点剔除掉。可以通过尺度空间DoG函数进行曲线拟合寻找极值点,这一步的本质是去掉DoG局部曲率非常不对称的点。
要剔除掉的不符合要求的点主要有两种:
1. 低对比度的特征点
2. 不稳定的边缘响应点
剔除低对比度的特征点
候选特征点x,其偏移量定义为Δx,其对比度为D(x)的绝对值∣D(x)∣,对D(x)应用泰勒展开式:
由于x是D(x)的极值点,所以对上式求导并令其为0,得到
然后再把求得的Δx代入到D(x)的泰勒展开式中
设对比度的阈值为T,若∣D(x^)∣≥T,则该特征点保留,否则剔除掉。
剔除不稳定的边缘响应点
在边缘梯度的方向上主曲率值比较大,而沿着边缘方向则主曲率值较小。候选特征点的DoG函数D(x)的主曲率与2×2Hessian矩阵H的特征值成正比。
为了避免求具体的值,可以使用H特征值得比例。设α=λmax为H的最大特征值,β=λmin为H的最小特征值,则
其中,Tr(H)为矩阵H的迹,Det(H)为矩阵H的行列式。
设γ=α/β 表示最大特征值和最小特征值的比值,则
上式的结果与两个特征值的比例有关,和具体的大小无关,当两个特征值想等时其值最小,并且随着γ的增大而增大。因此为了检测主曲率是否在某个阈值Tγ下,只需检测
如果上式成立,则剔除该特征点,否则保留。
求取特征点的主方向
经过上面的步骤已经找到了在不同尺度下都存在的特征点,为了实现图像旋转不变性,需要给特征点的方向进行赋值。利用特征点邻域像素的梯度分布特性来确定其方向参数,再利用图像的梯度直方图求取关键点局部结构的稳定方向。
找到了特征点,也就可以得到该特征点的尺度σσ,也就可以得到特征点所在的尺度图像
计算以特征点为中心、以3×1.5σ为半径的区域图像的幅角和幅值,每个点L(x,y)的梯度的模m(x,y)以及方向θ(x,y)可通过下面公司求得
计算得到梯度方向后,就要使用直方图统计特征点邻域内像素对应的梯度方向和幅值。梯度方向的直方图的横轴是梯度方向的角度(梯度方向的范围是0到360度,直方图每36度一个柱共10个柱,或者没45度一个柱共8个柱),纵轴是梯度方向对应梯度幅值的累加,在直方图的峰值就是特征点的主方向。在Lowe的论文还提到了使用高斯函数对直方图进行平滑以增强特征点近的邻域点对关键点方向的作用,并减少突变的影响。为了得到更精确的方向,通常还可以对离散的梯度直方图进行插值拟合。具体而言,关键点的方向可以由和主峰值最近的三个柱值通过抛物线插值得到。在梯度直方图中,当存在一个相当于主峰值80%能量的柱值时,则可以将这个方向认为是该特征点辅助方向。所以,一个特征点可能检测到多个方向(也可以理解为,一个特征点可能产生多个坐标、尺度相同,但是方向不同的特征点)。Lowe在论文中指出:
15%的关键点具有多方向,而且这些点对匹配的稳定性很关键。
得到特征点的主方向后,对于每个特征点可以得到三个信息(x,y,σ,θ),即位置、尺度和方向。由此可以确定一个SIFT特征区域,一个SIFT特征区域由三个值表示,中心表示特征点位置,半径表示关键点的尺度,箭头表示主方向。具有多个方向的关键点可以被复制成多份,然后将方向值分别赋给复制后的特征点,一个特征点就产生了多个坐标、尺度相等,但是方向不同的特征点。
生成特征描述
通过以上的步骤已经找到了SIFT特征点位置、尺度和方向信息,下面就需要使用一组向量来描述关键点也就是生成特征点描述子,这个描述符不只包含特征点,也含有特征点周围对其有贡献的像素点。描述子应具有较高的独立性,以保证匹配率。
特征描述符的生成大致有三个步骤:
1. 校正旋转主方向,确保旋转不变性。
2. 生成描述子,最终形成一个128维的特征向量
3. 归一化处理,将特征向量长度进行归一化处理,进一步去除光照的影响。
为了保证特征矢量的旋转不变性,要以特征点为中心,在附近邻域内将坐标轴旋转θ(特征点的主方向)角度,即将坐标轴旋转为特征点的主方向。旋转后邻域内像素的新坐标为:
旋转后以主方向为中心取 8×8的窗口。下图所示,左图的中央为当前关键点的位置,每个小格代表为关键点邻域所在尺度空间的一个像素,求取每个像素的梯度幅值与梯度方向,箭头方向代表该像素的梯度方向,长度代表梯度幅值,然后利用高斯窗口对其进行加权运算。最后在每个4×4的小块上绘制8个方向的梯度直方图,计算每个梯度方向的累加值,即可形成一个种子点,如图所示。每个特征点由4个种子点组成,每个种子点有8个方向的向量信息。这种邻域方向性信息联合增强了算法的抗噪声能力,同时对于含有定位误差的特征匹配也提供了比较理性的容错性。
与求主方向不同,此时每个种子区域的梯度直方图在0-360之间划分为8个方向区间,每个区间为45度,即每个种子点有8个方向的梯度强度信息。在实际的计算过程中,为了增强匹配的稳健性,Lowe建议:
对每个关键点使用4×4共16个种子点来描述,这样一个关键点就可以产生128维的SIFT特征向量。
通过对特征点周围的像素进行分块,计算块内梯度直方图,生成具有独特性的向量,这个向量是该区域图像信息的一种抽象,具有唯一性。
OpenCV中的SIFT算法
我们之前所讲述的都是理论,现在我们需要进行实践,首先我们需要注意:
SIFT已经被申请专利了,所以,在OpenCV3.4.3.16 版本后,这个功能就不能用了。
所以我们可以把OpenCV的版本降级:
pip install opencv-python==3.4.2.16 pip install opencv-contrib-python==3.4.2.16
我们来看代码:
import cv2 import numpy as np def sift_kp(image): gray_image = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY) sift = cv2.xfeatures2d.SIFT_create() kp, des = sift.detectAndCompute(image, None) kp_image = cv2.drawKeypoints(gray_image, kp, None) return kp_image, kp, des def get_good_match(des1, des2): bf = cv2.BFMatcher() matches = bf.knnMatch(des1, des2, k=2) # des1为模板图,des2为匹配图 matches = sorted(matches, key=lambda x: x[0].distance / x[1].distance) good = [] for m, n in matches: if m.distance < 0.75 * n.distance: good.append(m) return good img1 = cv2.imread(r'd:/t1.jpg') img2 = cv2.imread(r'd:/t2.jpg') kpimg1, kp1, des1 = sift_kp(img1) kpimg2, kp2, des2 = sift_kp(img2) cvshow('img1',np.hstack((img1,kpimg1))) cvshow('img2',np.hstack((img2,kpimg2))) goodMatch = get_good_match(des1, des2) all_goodmatch_img= cv2.drawMatches(img1, kp1, img2, kp2, goodMatch, None, flags=2) # goodmatch_img自己设置前多少个goodMatch[:10] goodmatch_img = cv2.drawMatches(img1, kp1, img2, kp2, goodMatch[:10], None, flags=2) cvshow('all_goodmatch_img', all_goodmatch_img) cvshow('goodmatch_img', goodmatch_img)
图1与其SIFT关键点检测:
图2与其SIFT关键点检测:
match(画出所有goodmatch):
match(只画出前N个goodmatch)效果较好:
SIFT的部分就到此结束了,我们在之后会讨论更加强大的算法。