图像局部描述符
文章目录
- SIFT特征匹配简介及原理
- SIFT特征匹配处理及Harris特征提取
- 可视化接连的图片
- 环境配置问题及解决办法
1.SIFT特征匹配简介及原理
SIFT的全称是Scale Invariant Feature Transform,尺度不变特征变换,由加拿大教授David G.Lowe提出的。SIFT特征对旋转、尺度缩放、亮度变化等保持不变性,是一种非常稳定的局部特征。
sift特征检测的四个主要步骤:
- 尺度空间的极值检测
- 特征点定位
- 特征方向赋值
- 特种点描述
接下来的将从这四个方面逐一介绍sift特征检测的原理。
1.1 尺度空间和极值检测
- 高斯尺度空间
通过高斯函数进行卷积运算可以对图像进行模糊处理,用模糊程度来模拟物体由远到近的过程。
通过不同的高斯核可以得到不同程度的模糊图像,一副图像的高斯尺寸空间可以由不同的高斯卷积得到:
L ( x , y , σ ) = G ( x , y , σ ) ∗ I ( x , y ) L(x,y,σ)=G(x,y,σ)∗I(x,y)L(x,y,σ)=G(x,y,σ)∗I(x,y)
其中,G ( x , y , σ ) G(x,y,σ)G(x,y,σ)是高斯核函数,其中σ代表尺度空间因子,是高斯正态分布的标准差,反应了被模糊的程度,其值越大,模糊程度越高,尺度也就越大。L ( x , y , σ ) L(x,y,σ)L(x,y,σ)即代表高斯尺度空间,构建好后就可以检测出在不同尺度下的特征点。特征点的检测可以使用LoG算子,但是LoG的运算量过于大,通常使用的DoG,即差分高斯。
D ( x , y , σ ) = [ G ( x , y , k σ ) − G ( x , y , σ ) ] ∗ I ( x , y ) = L ( x , y , k σ ) − L ( x , y , σ ) D(x,y,σ)=[G(x,y,kσ)−G(x,y,σ)]∗I(x,y)=L(x,y,kσ)−L(x,y,σ)D(x,y,σ)=[G(x,y,kσ)−G(x,y,σ)]∗I(x,y)=L(x,y,kσ)−L(x,y,σ)
从式子中可以看出来,DoG是由两个相邻的高斯空间图像相减得到的。所以需要得到一系列的高斯空间尺度,可以在对图像平滑和向下取样得到结果的基础上加上高斯滤波,即对向下取样的每层图像使用不同的尺度空间因子σ进行模糊。通过这样的方式,通过相邻两层相减就可以得到DoG的集合。 - DoG空间的极值检测
为了寻找尺度空间的极值点,每个像素点要和其图像域(同一尺度空间)和尺度域(相邻的尺度空间)的所有相邻点进行比较,当其大于(或者小于)所有相邻点时,改点就是极值点。
其中,值得注意的是,一般在计算某个像素的特征点时,不仅要和其所在平面的响铃像素点进行比较,还要和上下两层的像素点进行比较(如图中绿色的像素点)。这就会导致第一层和最后一层没办法进行比较,为了满足要求,在每组图像的顶层会再用高斯模糊生成三幅图像。
1.2 特征点的选择
需要删除掉:低对比度的特征点和不稳定的边缘相应点。
主要是通过尺度空间DoG函数进行曲线拟合寻找极值点。可以去掉DoG局部曲率极其不对称的点。
1.3 确定特征值的主方向
利用特征点邻域像素的梯度分布特性来确定其方向参数,再利用图像的梯度直方图求取关键点局部结构的稳定方向。
在上述过程中我们已经找到了特征点,就可以得到特征点的尺度图像:
L ( x , y ) = G ( x , y , σ ) ∗ I ( x , y ) L(x,y)=G(x,y,σ)∗I(x,y)L(x,y)=G(x,y,σ)∗I(x,y)
以特征点为中心,以3 × 1.5 σ 3×1.5σ3×1.5σ为半径的区域图像的幅角和幅值,每个点L(x,y)的梯度的模m ( x , y ) m(x,y)m(x,y)以及方向θ ( x , y ) θ(x,y)θ(x,y)可通过下面公式求得:
m ( x , y ) = [ L ( x + 1 , y ) − L ( x − 1 , y ) ] 2 + [ L ( x , y + 1 ) − L ( x , y − 1 ) ] 2 m(x,y)=\sqrt[]{[L(x+1,y)−L(x−1,y)]^{2}+[L(x,y+1)−L(x,y−1)]^{2}}m(x,y)=[L(x+1,y)−L(x−1,y)]2+[L(x,y+1)−L(x,y−1)]2
θ ( x , y ) = a r c t a n L ( x , y + 1 ) − L ( x , y − 1 ) L ( x + 1 , y ) − L ( x − 1 , y ) θ(x,y)=arctan\frac{L(x,y+1)−L(x,y−1)}{L(x+1,y)−L(x−1,y)}θ(x,y)=arctanL(x+1,y)−L(x−1,y)L(x,y+1)−L(x,y−1)
计算得到梯度方向后,就要使用直方图统计特征点邻域内像素对应的梯度方向和幅值。梯度方向的直方图的横轴是梯度方向的角度(梯度方向的范围是0到360度,直方图每36度一个柱共10个柱,或者没45度一个柱共8个柱),纵轴是梯度方向对应梯度幅值的累加,在直方图的峰值就是特征点的主方向。
得到特征点的主方向后,对于每个特征点可以得到三个信息(x,y,σ,θ),即位置、尺度和方向。由此可以确定一个SIFT特征区域,一个SIFT特征区域由三个值表示,中心表示特征点位置,半径表示关键点的尺度,箭头表示主方向。具有多个方向的关键点可以被复制成多份,然后将方向值分别赋给复制后的特征点,一个特征点就产生了多个坐标、尺度相等,但是方向不同的特征点。
1.4 生成特征描述
在获得了SIFT特征点的信息之后,可以使用一组向量来描述关键点。而关键点的生成有一下三个过程:
- 通过得到的主方向对图像进行翻转,使其在方向上尽量保持一致
- 生成一个128维的特征向量,作为描述子
- 对特征向量长度进行归一化处理,去除光照的影响
以特征点为中心,在附近邻域内将坐标轴旋转θ(特征点的主方向)角度,即将坐标轴旋转为特征点的主方向。
种子点的获得:旋转后以主方向为中心取 8×8的窗口。下图所示,左图的中央为当前关键点的位置,每个小格代表为关键点邻域所在尺度空间的一个像素,求取每个像素的梯度幅值与梯度方向,箭头方向代表该像素的梯度方向,长度代表梯度幅值,然后利用高斯窗口对其进行加权运算。最后在每个4×4的小块上绘制8个方向的梯度直方图,计算每个梯度方向的累加值,即可形成一个种子点,如右图所示。每个特征点由4个种子点组成,每个种子点有8个方向的向量信息。
其中,对每个关键点使用4×4共16个种子点来描述,这样一个关键点就可以产生128维的SIFT特征向量。
2.SIFT特征匹配处理及Harris特征提取
2.1.1 resize
我选择了不同角度,大小不同的两张陈延奎图书馆的照片,为了加快运行速度,进行了25%的resize
img = cv2.imread('jm6.jpg')
img_test2 = cv2.resize(img, (0, 0), fx=0.25, fy=0.25, interpolation=cv2.INTER_NEAREST)
原图结果如下(原图):
2.1.2 使用SIFT对其进行特征匹配处理
结果如下:
可以看到即使在图片尺寸,拍摄角度不一致的情况下也能比较精准的进行特征值匹配。
但是sift算法对图片清晰度有一定的要求,当我把图片resize到原图的8%后,特征值的匹配数量出现了非常大的减少,如下图:
在某些角度差距特别大的同一事物的图片中,因为清晰度过低,甚至会出现没有一个特征可以匹配的情况。
2.1.3 SIFT代码
from imagedt.decorator import time_cost
import cv2
from pylab import *
import _pickle as cPickle
def bgr_rgb(img):
(r, g, b) = cv2.split(img)
return cv2.merge([b, g, r])
def orb_detect(image_a, image_b):
# feature match
orb = cv2.ORB_create()
kp1, des1 = orb.detectAndCompute(image_a, None)
kp2, des2 = orb.detectAndCompute(image_b, None)
# create BFMatcher object
bf = cv2.BFMatcher(cv2.NORM_HAMMING, crossCheck=True)
# Match descriptors.
matches = bf.match(des1, des2)
# Sort them in the order of their distance.
matches = sorted(matches, key=lambda x: x.distance)
# Draw first 10 matches.
img3 = cv2.drawMatches(image_a, kp1, image_b, kp2, matches[:100], None, flags=2)
return bgr_rgb(img3)
@time_cost
def sift_detect(img1, img2, detector='surf'):
if detector.startswith('si'):
sift = cv2.xfeatures2d.SURF_create()
else:
sift = cv2.xfeatures2d.SURF_create()
# find the keypoints and descriptors with SIFT
kp1, des1 = sift.detectAndCompute(img1, None)
kp2, des2 = sift.detectAndCompute(img2, None)
# BFMatcher with default params
bf = cv2.BFMatcher()
matches = bf.knnMatch(des1, des2, k=2)
# Apply ratio test
good = [[m] for m, n in matches if m.distance < 0.5 * n.distance]
# cv2.drawMatchesKnn expects list of lists as matches.
img3 = cv2.drawMatchesKnn(img1, kp1, img2, kp2, good, None, flags=2)
return bgr_rgb(img3)
if __name__ == "__main__":
img0 = cv2.imread('jm6.jpg')
img1 = cv2.imread('jm8.jpg')
# SIFT or SURF
img = sift_detect(img0, img1)
plt.imshow(img)
plt.show()
2.2 关于Harris特征匹配和sift的比较
Harris算法解决了图片的旋转不变性,通过根据所得的特征点局部图像结构求一个方向准基,使用图像梯度的方法求取该局部结构的稳定方法。
sift在此基础上解决了尺寸不变性的问题。
2.2.1 使用sift进行特征检测(左),使用Harris进行特征检测(右):
- 可以看出sift特征检测法检测到的特征点比Harris检测到的特征点要更多,更详细的一些。
2.2.2 当改变图片尺寸后(图片的长变为原来的40%,宽度不变)
使用sift进行特征检测(左),使用Harris进行特征检测(右):
- 可以发现改变尺寸对于Harris特征值检测的影响要大于对sift特征值检测的影响。
3.可视化接连的图片
要对上面匹配后的图像进行连接可视化,需要使用pydot工具包和GraphViz graphing库。具体的安装流程可以参见文章的最后,即第四部分(环境配置问题及解决办法)。
3.1 图片数据的介绍
我选择了共计18张图片,其中尚大楼近景图片的数量为11张,尚大楼远景及只有一小部分为尚大楼的图片数量为5张,和尚大楼一点关系没有的禹州楼照片为2张。
3.2 连线结果:
可以看到整个连线结果中只有15副图片,可以看出参与连接的尚大楼图片都是天气较为阴暗的情况下拍摄的;而没有参与连接的三张图片都是天气较为晴朗并且都是远景拍摄的情况。说明天气和目标物体的大小对匹配还是有所影响的,若图片的尺寸可以保持较为清晰的状态,匹配就会更加精确一些,但是匹配和连接的速度会变得非常缓慢。
除此之外,在尚大楼近景的连接,尚大楼和禹州的区分都能较好的完成。
3.3 代码实现
这里要注意,高版本和低版本的print用法不同(高版本的prin()需要括号,低版本的不需要),不但是代码中的print需要修改,imtool库中的有一句话也需要修改:
from pylab import *
from PIL import Image
from PCV.localdescriptors import sift
from PCV.tools import imtools
import pydot
""" This is the example graph illustration of matching images from Figure 2-10.
To download the images, see ch2_download_panoramio.py."""
#download_path = "panoimages" # set this to the path where you downloaded the panoramio images
#path = "/FULLPATH/panoimages/" # path to save thumbnails (pydot needs the full system path)
download_path = "D:/pycharmfile/test/buiding/" # set this to the path where you downloaded the panoramio images
path = "D:/pycharmfile/test/buiding/sift/" # path to save thumbnails (pydot needs the full system path)
# list of downloaded filenames
imlist = imtools.get_imlist(download_path)
nbr_images = len(imlist)
# extract features
featlist = [imname[:-3] + 'sift' for imname in imlist]
for i, imname in enumerate(imlist):
sift.process_image(imname, featlist[i])
matchscores = zeros((nbr_images, nbr_images))
for i in range(nbr_images):
for j in range(i, nbr_images): # only compute upper triangle
print('comparing ', imlist[i], imlist[j])
l1, d1 = sift.read_features_from_file(featlist[i])
l2, d2 = sift.read_features_from_file(featlist[j])
matches = sift.match_twosided(d1, d2)
nbr_matches = sum(matches > 0)
print('number of matches = ', nbr_matches)
matchscores[i, j] = nbr_matches
print("The match scores is: \n", matchscores)
# copy values
for i in range(nbr_images):
for j in range(i + 1, nbr_images): # no need to copy diagonal
matchscores[j, i] = matchscores[i, j]
#可视化
threshold = 2 # min number of matches needed to create link
g = pydot.Dot(graph_type='graph') # don't want the default directed graph
for i in range(nbr_images):
for j in range(i + 1, nbr_images):
if matchscores[i, j] > threshold:
# first image in pair
im = Image.open(imlist[i])
im.thumbnail((100, 100))
filename = path + str(i) + '.jpg'
im.save(filename) # need temporary files of the right size
g.add_node(pydot.Node(str(i), fontcolor='transparent', shape='rectangle', image=filename))
# second image in pair
im = Image.open(imlist[j])
im.thumbnail((100, 100))
filename = path + str(j) + '.jpg'
im.save(filename) # need temporary files of the right size
g.add_node(pydot.Node(str(j), fontcolor='transparent', shape='rectangle', image=filename))
g.add_edge(pydot.Edge(str(i), str(j)))
g.write_png('shangda1.png')
参考文章:http://yongyuan.name/pcvwithpython/chapter2.html?tdsourcetag=s_pcqq_aiomsg#sec-2-3-1
4.环境配置问题及解决办法
1.若在在使用sift的过程中出现了以下错误:
| sift = cv2.xfeatures2d.SURF_create() cv2.error: OpenCV(3.4.5) d:\build\opencv\opencv_contrib-3.4.5\modules\xfeatures2d\src\surf.cpp:1029: error: (-213:The function/feature is not implemented) This algorithm is patented and is excluded in this configuratiopip |
|---|
解决办法:
这里有可能是因为两种原因报错,其一:是由于opencv-python和opencv-contrib-python 版本不同造成的错误,并且OpenCV3.x以上版本opencv-python和opencv-contrib-python 是需要分开安装的。我尝试过安装一个和opencv-python一样版本的opencv-contrib-python,发现依旧报错,所以建议把原来的都卸载然后重新安装同样版本的opencv-python和opencv-contrib-python 。
A. 卸载之前的opencv-python和opencv-contrib-python 版本。
pip uninstall opencv-python
pip uninstall opencv-contrib-python
B. 安装同样版本的opencv-python和opencv-contrib-python
pip install opencv-python==3.4.2.16
pip install opencv-contrib-python==3.4.2.
其二:检查图片的地址是否正确,如果地址是 D:\pycharmfile\test 这样的形式,建议把地址中的 '\'改成 ‘/’。
2.pydot的安装和配置
2.1 安装pydot
可以直接使用pip安装
pip install pydot

安装完成后,运行代码。可能会出现以下问题:
| FileNotFoundError: [WinError 2] “neato” not found in path. |
|---|
这时候我们需要安装一个 Graphviz,下载安装完成后,找到Graphviz的bin文件夹地址,并把它加入到环境变量中。
之后再次运行代码,就没有问题了。
参考文章:https://blog.csdn.net/sinat_38653840/article/details/84776806