第十七节、基于ORB的特征检测和特征匹配
之前我们已经介绍了SIFT算法,以及SURF算法,但是由于计算速度较慢的原因。人们提出了使用ORB来替代SIFT和SURF。与前两者相比,ORB有更快的速度。ORB在2011年才首次发布。在前面小节中,我们已经提到了ORB算法。ORB算法将基于FAST关键点的技术和基于BRIEF描述符的技术相结合,关于FAST和BRIEF相关内容可以参考博客第十四节、FAST角点检测(附源码)和第十六节、特征描述符BRIEF(附源码)。
一 ORB算法原理
ORB算法将FAST特征点的检测方法和BRIEF特征描述子结合起来,并在它们的基础上做了改进与优化。
首先,它利用FAST特征点检测的方法来检测特征点,然后利用Harris角点的度量方法,从FAST特征点中挑选出Harris角点响应值最大的N个特征点。其中Harris角点的响应函数定义为:
$$R=detM-k(trace(M))^2$$
关于$M$的含义和响应函数可以参考第十一节、Harris角点检测原理(附源码)这篇博客。
1.1 旋转不变性
在现在生活中,我们从不同的距离,不同的方向、角度、不同的光照条件下观察一个物体时,物体的大小、形状,明暗都会有所不同。但是我们仍然可以判断它是一个物体。理想的特征描述子应该具备这些性质,即在大小、方向、明暗不同的图像中,同一特征点应具有足够相似的描述子,称之为描述子的可复现性。
但是ORB并没有解决尺度不一致的问题,在OpenCV的ORB实现中采用了图像金字塔来改善这方面的性能,我们通过构建高斯金字塔,然后在每一层金字塔图像上检测角点,来实现尺度不变性。ORB主要解决了BRIEF描述子不具备旋转不变性的问题,ORB论文种提出了一种利用灰度质心法来解决这个问题,灰度质心法假设角点的灰度与质心之间存在一个偏移,这个向量可以用于表示一个方向。对于任意一个特征点$p$来说,我们定义$p$的邻域像素的矩为:
$$m_{ij}=\sum\limits_{x=-r}^{r}\sum\limits_{y=-r}^{r}x^iy^jI(x,y)$$
其中$I(x,y)$为点$(x,y)$处的灰度值,$q$为质心,$i,j=0,1$。那么我们可以得到图像的质心为:
$$C=(\frac{m_{10}}{m_{00}},\frac{m_{01}}{m_{00}})$$
那么特征点与质心的夹角定义为FAST特征点的方向:
$$\theta=arctan(m_{01},m_{10})$$
为了提高算法的旋转不变性,需要确保$x$和$y$在半径为$r$的圆形区域内,即$x,y\in[-r,r]$,$r$等于邻域半径。
1.2 特征点的描述
ORB选择了BRIEF作为特征描述方法,但是我们知道BRIEF不具备旋转不变性,所以我们要给BRIEF加上旋转不变性,把这种方法称为"Steer BRIEF"。对于任何一个特征点来说,它的BRIEF描述子是一个长度为$n$的二值码串,这个二值码串是由特征点邻域$n$个点对生成的,我们现在讲这$2n$个点$(x_i,y_i),i=1,2,.....,2n$组成一个矩阵$S$:
$$S=\begin{bmatrix} x_1 & x_2 & ... &x_{2n} \\ y_1 & y_2 & ... & y_{2n} \end{bmatrix}$$
Calonder建议为每个块的旋转和投影集合分别计算BRIEF描述子,但代价昂贵。ORB中采用了一个更有效的方法:使用邻域方向$\theta$和对应的旋转矩阵$R_{\theta}$,构建$S$的一个校正版本$S_{\theta}$:
$$S_{\theta}=R_{\theta}S$$
其中:
$$R_{\theta}=\begin{bmatrix} cos\theta & sin\theta \\ -sin\theta & cos\theta \end{bmatrix}$$
而$\theta$即我们为特征点求得的主方向。
即我们把坐标轴旋转$]theta$,计算以主方向为坐标系的匹配点对,如下图:
实际上,我们可以把角度离散化,即把360度分为12份,每一份是30度,然后我们对这个12个角度分别求得一个$S_{\theta}$,这样我们就创建了一个查找表,对于每一个$\theta$,我们只需要查表即可快速得到它的点的集合$S_{\theta}$。
1.3 解决描述子的区分性
BRIEF令人惊喜的特性之一是:对于$n$维的二值串的每个特征位,所有特征点在该位上的值都满足一个均值接近于0.5,而方差很大的高斯分布。方差越大,说明区分性越强,那么不同特征点的描述子就表现出来越大差异性,对匹配来说不容易误配。但是当我们把BRIEF沿着特征点的方向调整为Steered BRIEF时,均值就漂移到一个更加分散式的模式。可以理解为有方向性的角点关键点对二值串则展现了一个更加均衡的表现。而且论文中提到经过PCA对各个特征向量进行分析,得知Steered BRIEF的方差很小,判别性小,各个成分之间相关性较大。
为了减少Steered BRIEF方差的亏损,并减少二进制码串之间的相关性,ORB使用了一种学习的方法来选择一个较小的点对集合。方法如下:
首先建立一个大约300k关键点的测试集,这些关键点来自于PASCAL2006集中的图像。
对于这300k个关键点中的每一个特征点,考虑它的$31\times{31}$的邻域,我们将在这个邻域内找一些点对,不同于BRIEF中要先对这个Patch内的点做平滑,再用以Patch中心为原点的高斯分布选择点对的方法。ORB为了去除某些噪声点的干扰,选择了一个$5\times{5}$大小的区域的平均灰度来代替原来一个单点的灰度,这里$5\times{5}$区域内图像平均灰度的计算可以用积分图的方法。我们知道$31\times{31}$的Patch里共有$N=(31-5+1)\times{(31-5+1)}$个这种窗口,那么我们要$N$个子窗口中选择2个子窗口的话,共有$C_N^2$种方法。所以对于300k中每一个特征点,我们都可以从它的$31\times{31}$大小的邻域中提取一个很长的二进制串,长度为$M=C_N^2$,表示为:
$$binArray = \begin{bmatrix} p_1 & p_2 & ... &p_M \end{bmatrix},p_i\in\{0,1\} $$
那么当300k个关键点全部进行上面的特征提取之后,我们就得到了一个$300k\times{M}$的矩阵,矩阵中的每个元素值为0或者1.
对该矩阵的每个列向量,也就是每个点对在300k个特征点上的测试结果,计算其均值。把所有的列向量按均值进行重新排序。排好后,组成了一个向量$T$,$T$的每一个元素都是一个列向量。
进行贪婪搜索,从$T$中把排在第一的那个列放到$R$中,$T$中就没有这个点对的测试结果了,然后把$T$中的排在下一个的列与$R$中的所有元素比较,计算它们的相关性,如果相关超过了某一事先设定好的阈值,就扔了它,否则就把它方到$R$里面。重复上面的步骤,直到$R$中有256个列向量位置。如果把$T$全部找完也没有找到256个,那么我们可以把相关的阈值调高一些,再尝试一遍。
这样,我们就得到了256个点对。上面这个过程我们称它为rBRIEF。
二 OpenCV实现
2.1 cv2.ORB_create
ORB中有很多参数可以设置,在OpenCV中它可以通过ORB来创建一个ORB检测器。
cv2.ORB_create([,nfeatues[,scaleFactor[,nlevels[,edgeThreshold[,firstLevel[,WTA_K[,[scoreType,[patchSize,fastThreshold]]]]]]]]])
参数说明:
- nfeatures :最多提取的特征点的数量;
- scaleFactor : 金字塔图像之间的尺度参数,类似于SIFT中的$k$;
- nlevels: 高斯金字塔的层数;
- edgeThreshold :边缘阈值,这个值主要是根据后面的patchSize来定的,靠近边缘edgeThreshold以内的像素是不检测特征点的。
- firstLevel-:看过SIFT都知道,我们可以指定第一层的索引值,这里默认为0。
- WET_K : 用于产生BIREF描述子的点对的个数,一般为2个,也可以设置为3个或4个,那么这时候描述子之间的距离计算就不能用汉明距离了,而是应该用一个变种。OpenCV中,如果设置WET_K = 2,则选用点对就只有2个点,匹配的时候距离参数选择NORM_HAMMING,如果WET_K设置为3或4,则BIREF描述子会选择3个或4个点,那么后面匹配的时候应该选择的距离参数为NORM_HAMMING2。
- scoreType :用于对特征点进行排序的算法,你可以选择HARRIS_SCORE,也可以选择FAST_SCORE,但是它也只是比前者快一点点而已。
- patchSize :用于计算BIREF描述子的特征点邻域大小。
2.2 示例
# -*- coding: utf-8 -*- """ Created on Mon Sep 10 22:37:36 2018 @author: zy """ ''' ORB特征匹配 ''' import numpy as np import cv2 def orb_test(): #加载图片 灰色 img1 = cv2.imread('./image/orb1.jpg') gray1 = cv2.cvtColor(img1,cv2.COLOR_BGR2GRAY) img2 = cv2.imread('./image/orb2.jpg') img2 = cv2.resize(img2,dsize=(450,300)) gray2 = cv2.cvtColor(img2,cv2.COLOR_BGR2GRAY) image1 = gray1.copy() image2 = gray2.copy() ''' 1.使用ORB算法检测特征点、描述符 ''' orb = cv2.ORB_create(128) keypoints1,descriptors1 = orb.detectAndCompute(image1,None) keypoints2,descriptors2 = orb.detectAndCompute(image2,None) #在图像上绘制关键点 image1 = cv2.drawKeypoints(image=image1,keypoints = keypoints1,outImage=image1,color=(255,0,255),flags=cv2.DRAW_MATCHES_FLAGS_DRAW_RICH_KEYPOINTS) image2 = cv2.drawKeypoints(image=image2,keypoints = keypoints2,outImage=image2,color=(255,0,255),flags=cv2.DRAW_MATCHES_FLAGS_DRAW_RICH_KEYPOINTS) #显示图像 cv2.imshow('orb_keypoints1',image1) cv2.imshow('orb_keypoints2',image2) cv2.waitKey(20) ''' 2、匹配 ''' matcher = cv2.BFMatcher_create(cv2.HAMMING_NORM_TYPE,crossCheck=True) matchePoints = matcher.match(descriptors1,descriptors2) print(type(matchePoints),len(matchePoints),matchePoints[0]) #按照距离从小到大排序,选取最优匹配的 sorted(matchePoints,key=lambda x:x.distance) #绘制最优匹配点 outImg = None outImg = cv2.drawMatches(img1,keypoints1,img2,keypoints2,matchePoints[:10],outImg,matchColor=(0,255,0),flags=cv2.DRAW_MATCHES_FLAGS_DEFAULT) cv2.imshow('matche',outImg) cv2.waitKey(0) cv2.destroyAllWindows() cv2.waitKey(0) cv2.destroyAllWindows() if __name__ == '__main__': orb_test()
三 代码下载
参考文章: