计算机视觉——SIFT特征提取与检索

1. SIFT算法

1.1 基本概念

尺度不变特征转换(Scale-invariant feature transform或SIFT) 是一种电脑视觉的算法用来侦测与描述影像中的局部性特征,它在空间尺度中寻找极值点,并提取出其位置、尺度、旋转不变量。
1999年David G.Lowe教授总结了基于特征不变技术的检测方法,在图像尺度 空间基础上,提出了对图像缩放、旋转保持不变性的图像局部特征描述算子 -SIFT(尺度不变特征变换),该算法在2004年被加以完善。其应用范围包含物体辨识、机器人地图感知与导航、影像缝合、3D模型建立、手势辨识、影像追踪和动作比对。
局部影像特征的描述与侦测可以帮助辨识物体,SIFT 特征是基于物体上的一些局部外观的兴趣点而与影像的大小和旋转无关。对于光线、噪声、些微视角改变的容忍度也相当高。基于这些特性,它们是高度显著而且相对容易撷取,在母数庞大的特征数据库中,很容易辨识物体而且鲜有误认。使用 SIFT特征描述对于部分物体遮蔽的侦测率也相当高,甚至只需要3个以上的SIFT物体特征就足以计算出位置与方位。在现今的电脑硬件速度下和小型的特征数据库条件下,辨识速度可接近即时运算。SIFT特征的信息量大,适合在海量数据库中快速准确匹配。

SIFT算法可以解决的问题:

  1. 目标的旋转、缩放、平移(RST)
  2. 图像仿射/投影变换(视点viewpoint)
  3. 弱光照影响(illumination)
  4. 部分目标遮挡(occlusion)
  5. 杂物场景(clutter)
  6. 噪声

1.2 SIFT算法基本原理

1.2.1 特征点

SIFT中要查找的关键点:
一些十分突出的点不会因光照、尺度、旋转等因素的改变而消失,比如角点、边缘点、暗区域的亮点以及亮区域的暗点。既然两幅图像中有相同的景物,那么使用某种方法分别提取各自的稳定点,这些点之间会有相互对应的匹配点。

1.2.2 尺度空间

尺度空间理论最早于1962年提出,其主要思想是通过 对原始图像进行尺度变换,获得图像多尺度下的空间表示。从而实现边缘、角点检测和不同分辨率上的特征提取,以满足特征点的尺度不变性。
尺度空间中各尺度图像的模糊程度逐渐变大,能够模拟人在距离目标由近到远时目标在视网膜上的形成过程。尺度越大图像越模糊。

一个图像的尺度空间 L ( x , y , σ ) L(x,y,\sigma) ,定义为一个变化尺度的高斯函数 G ( x , y , σ ) G(x,y,\sigma) 与原图像 I ( x , y ) I(x,y) 的卷积:
L ( x , y , σ ) = G ( x , y , σ ) I ( x , y ) L(x,y,σ)=G(x,y,\sigma) * I(x,y) 其中 * 表示卷积运算。

1.2.3 高斯函数

G ( x , y , σ ) = 1 2 Π σ 2 e ( x m / 2 ) 2 + ( y n / 2 ) 2 2 σ 2 G(x,y,\sigma)=\frac{1}{2Π\sigma^2}e^{\frac{(x-m/2)^2+(y-n/2)^2}{2\sigma^2}}

其中, m , n m,n 表示高斯模板的维度(由确定)。 ( x , y ) (x, y) 代表图像的像素位置。 σ \sigma 是尺度空间因子,值越小表示图像被平滑的越少,相应的尺度也就越小。大尺度对应于图像的概貌特征,小尺度对应于图像的细节特征。

1.2.4 高斯模糊

高斯模糊是在Adobe Photoshop等图像处理软件中广泛使用的处理 效果,通常用它来减小图像噪声以及降低细节层次。这种模糊技术生成 的图像的视觉效果是好像经过一个半透明的屏幕观察图像。如下图效果:

在这里插入图片描述

1.2.5 高斯金字塔

图像的金字塔模型是指,将原始图像不断降阶采样,得到一系列大小不一的图像,由大到小,从下到上构成的塔状模型。原图像为金子塔的第一层,每次降采样所得到的新图像为金字塔的一层(每层一张图像),每个金字塔共n层。金字塔的层数根据图像的原始大小和塔顶图像的大小共同决定,其计算公式如下:
n = log 2 m i n ( M , N ) t , t [ 0 , log 2 m i n ( M , N ) ] n=\log_{2}{min(M,N)}-t,t\in[0,\log_{2}{min(M,N)}]
其中 M N M,N 为原图像的大小, t t 为塔顶图像的最小维数的对数值。
如,对于大小为512512的图像,金字塔上各层图像的大小如表3.1所示,当塔顶图像为44时,n=7,当塔顶图像为2*2时,n=8。

图像大小 512 216 128 64 16 8 4 2 1
金字塔层数 1 2 3 4 5 6 7 8 9

尺度空间在实现时使用高斯金字塔表示,高斯金子塔的构建过程可分为两步:
① 对图像做高斯平滑
② 对图像做降采样
为了让尺度体现其连续性,在简单下采样的基础上加上了高斯滤波。,将图像金字塔每层的一张图像使用不同参数做高斯模糊,使得金字塔的每层含有多张高斯模糊图像,将金字塔每层多张图像合称为一组(Octave),金字塔每层只有一组图像,组数和金字塔层数相等,每组含有多张(也叫层Interval)图像。另外,降采样时,高斯金字塔上一组图像的初始图像(底层图像)是由前一组图像的倒数第三张图像隔点采样得到的。
在这里插入图片描述
高斯图像金字塔共o组、s层, 则有:
σ ( s ) = σ 0 2 s / S \sigma(s)=\sigma_02^{s/S}
其中,σ为尺度空间坐标; s为sub-level层坐标; σ 0 σ_0 为初始尺度; S为每组层数(一般为3~5)。
最后可将组内和组间尺度 归为:
2 i 1 ( σ , k σ , k 2 σ , . . . , k n 1 σ ) 2^{i-1}(\sigma,k\sigma,k^2\sigma,...,k^{n-1}\sigma)
k = 2 1 / S k=2^{1/S}
其中,i为金字塔组数 n为每一组的层数。

1.2.6 DOG函数

用更高效的高斯差分算子代替拉普拉斯算子进行极值检测,公式如下:
L ( x , y , σ ) = G ( x , y , σ ) I ( x , y ) L(x,y,σ)=G(x,y,\sigma) * I(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,\sigma)=[G(x,y,k\sigma)-G(x,y,\sigma]*I(x,y)=L(x,y,k\sigma)-L(x,y,\sigma)
由上式可知:DoG在计算上只需相邻高斯平滑后图像相减,因此简化了计算。

1.2.7 高斯差分金字塔

对应DOG算子,需构建DOG金字塔。可以通过高斯差分图像看出图像上的像素值变化情况。(如果没有变化,也就没有特征。特征必须是变化尽可能多的点。) DOG图像描绘的是目标的轮廓。
在实际计算时,使用高斯金字塔每组中相邻上下两层图像相减,得到高斯差分图像,如下图所示,进行极值检测。
在这里插入图片描述

1.2.8 DOG局部极值检测

  • DoG的局部极值点
    特征点是由DOG空间的局部极值点组成的。为了寻找DoG函数的极值点, 每一个像素点要和它所有的相邻点比较,看其是否比它的图像域和尺度域的相邻点大或者小。
    在这里插入图片描述中间的检测点和它同尺度的8个相邻点和上下相邻尺度对应的9×2个 点共26个点比较,以确保在尺度空间和二维图像空间都检测到极值点。
  • 去除边缘响应
    由于DoG函数在图像边缘有较强的边缘响应,因此需要排除边缘响应。 DoG函数的峰值点在边缘方向有较大的主曲率,而在垂直边缘的方向有 较小的主曲率。主曲率可以通过计算在该点位置尺度的2×2的Hessian矩 阵得到,导数由采样点相邻差来估计:
    H = [ D x x D x y D x y D y y ] H=\begin{bmatrix} D_{xx}& D_{xy} \\ D_{xy} & D_{yy} \end{bmatrix}
    D x x D_{xx} 表示DOG金字塔中某一尺度的图像x方向求导两次。
    D D 的主曲率和 H H 的特征值成正比。令 α α β β 为特征值,则
    T r ( H ) 2 D e t ( H ) = ( α + β ) 2 α β \frac{Tr(H)^2}{Det(H)}=\frac{(α+β)^2}{αβ}
    d e t ( H ) = α β det(H)=αβ
    t r a c e ( H ) = α + β trace(H)=α+β
    该值在两特征值相等时达最小。Lowe论文中建议阈值T为1.2,即 T r ( H ) 2 D e t ( H ) < T \frac{Tr(H)^2}{Det(H)}<T 时保留关键点,反之剔除。

1.2.9 关键点方向分配

通过尺度不变性求极值点,可以使其具有缩放不变的性质。而利用关键点邻域像素的梯度方向分布特性,可以为每个关键点指定方向参数方向,从而使描述子对图像旋转具有不变性。通过求每个极值点的梯度来为极值点赋予方向。

  • 像素点的梯度表示
    g r a d I ( x , y ) = ( I x , I y ) gradI(x,y)=\begin{pmatrix} \frac{\partial I}{\partial x},\frac{\partial I}{\partial y} \end{pmatrix}
    梯度幅值:
    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
    梯度方向:
    θ ( x , y ) = t a n 1 [ L ( x , y + 1 ) L ( x , y 1 ) L ( x + 1 , y ) L ( x 1 , y ) ] \theta (x,y)=tan^{-1}\begin{bmatrix} \frac{L(x,y+1)-L(x,y-1)}{L(x+1,y)-L(x-1,y)} \end{bmatrix}
  • 方向直方图的生成
    确定关键点的方向采用梯度直方图统计法,统计以关键点为原 点,一定区域内的图像像素点对关键点方向生成所作的贡献。如下图:
    在这里插入图片描述
  • 关键点的主方向与辅方向
    关键点主方向:极值点周围区域梯度直方图的主峰值也是特征点方向 。
    关键点辅方向:在梯度方向直方图中,当存在另一个相当于主峰值80%能量的峰值时,则将这个方向认为是该关键点的辅方向。
    这可以增强匹配的鲁棒性,Lowe的论文指出大概有15%关键点具有 多方向,但这些点对匹配的稳定性至为关键。

1.2.10 关键点描述

下图是一个SIFT描述子事例。其中描述子由2×2×8维向量表征,也即是 2×2个8方向的方向直方图组成。左图的种子点由8×8单元组成。每一个小格 都代表了特征点邻域所在的尺度空间的一个像素,箭头方向代表了像素梯度方 向,箭头长度代表该像素的幅值。然后在4×4的窗口内计算8个方向的梯度方 向直方图。绘制每个梯度方向的累加可形成一个种子点,如右图所示:一个特 征点由4个种子点的信息所组成。
在这里插入图片描述
Lowe实验结果 表明:描述子采用4×4×8=128维向量表征, 综合效果最优 (不变性与独 特性)。
在这里插入图片描述

1.2.11 关键点匹配

分别对模板图(参考图,reference image)和实时图(观测图,observation image)建立关键点描述子集合。目标的识别是通过两点集内关键点描述子的比对来完成。具有128维的关键点描述子的相似 性度量采用欧式距离。
在这里插入图片描述
穷举匹配将原图像中的每一个特征点与目标图像中的每一个特征点进行匹配,如下图所示:
在这里插入图片描述
模板图中关键点描述子: R i = ( r i 1 , r i 2 , . . . , r i 128 ) R_i=(r_{i1},r_{i2},...,r_{i128})
实时图中关键点描述子: S i = ( s i 1 , s i 2 , . . . , s i 128 ) S_i=(s_{i1},s_{i2},...,s_{i128})
任意两描述子相似性度量: d ( R i , S i ) = j = 1 128 ( r i j s i j ) 2 d(R_i,S_i)=\sqrt{\sum_{j=1}^{128}(r_{ij}-s_{ij})^2}
要得到配对的关键点描述子, d ( R i , S i ) d(R_i,S_i) 需满足:
R i S j R i S p T h r e s h o l d \frac{实时图中距离R_i最近的点S_j}{实时图中距离R_i的次最近的点S_p}<Threshold
关键点的匹配可以采用穷举法来完成,但是这样耗费的时间太多,一 般都采用Kd树的数据结构来完成搜索。搜索的内容是以目标图像的关 键点为基准,搜索与目标图像的特征点最邻近的原图像特征点和次邻 近的原图像特征点。Kd树是一个平衡二叉树。
在这里插入图片描述

1.3 SIFT算法实现步骤

SIFT算法的实质可以归为在不同尺度空间上查找特征点(关键点)的问题。

SIFT算法实现特征匹配的流程:

  • 提取关键点.
  • 对关键点附加详细的信息(局部特征),即描述符.
  • 通过特征点(附带上特征向量的关 键点)的两两比较找出相互匹配的若干对特征点,建立景物间的对应关系.
    在这里插入图片描述

2. SIFT算法实验过程

2.1 构造小型数据集

针对SIFT特征提取和检测算法,构造了一个小型数据集进行接下来的实验过程,数据集如下图:
在这里插入图片描述

2.2 SIFT特征提取

针对数据集的每一张图像实现SIFT特征提取并展示,对比Harris检测的效果。

2.2.1 代码实现

# -*- coding: utf-8 -*-
from PIL import Image
from pylab import *
from PCV.localdescriptors import sift
from PCV.localdescriptors import harris
# 添加中文字体支持
from matplotlib.font_manager import FontProperties
font = FontProperties(fname=r"c:\windows\fonts\SimSun.ttc", size=14)
imname='F:\\mr\\JMUclass\\ComputerVison\\CVcode\\test\\test2\\dataset\\1.jpg'
im = array(Image.open(imname).convert('L'))
sift.process_image(imname, 'empire.sift')
l1, d1 = sift.read_features_from_file('empire.sift')
figure()
gray()
subplot(221)
plt.axis('off')
imshow(Image.open(imname))
title(u'原图',fontproperties=font)
subplot(222)
sift.plot_features(im, l1, circle=False)
title(u'SIFT特征',fontproperties=font)
subplot(223)
sift.plot_features(im, l1, circle=True)
title(u'用圆圈表示SIFT特征尺度',fontproperties=font)
# 检测harris角点
harrisim = harris.compute_harris_response(im)
subplot(224)
filtered_coords = harris.get_harris_points(harrisim, 6, 0.1)
imshow(im)
plot([p[1] for p in filtered_coords], [p[0] for p in filtered_coords], '*')
axis('off')
title(u'Harris角点',fontproperties=font)
show()

2.2.2 实验结果

实验结果1
实验结果2
实验结果3
实验结果4
实验结果5
实验结果6
实验结果7
实验结果8
实验结果9
实验结果10
实验结果11
实验结果12
实验结果13
实验结果14
实验结果15

2.2.3 实验结果分析

由以上实验结果可得如下分析:

  • SIFT特征提取和检测算法提取到的特征点明显多于Harris角点检测,由于在一定程度上增加了图像的特征点,从后续的特征匹配的工作角度上考虑可知:SIFT算法明显优于Harris算法。在一定程度上这体现了SIFT特征提取与检测算法的优越性。
  • 在实验过程中,由实验结果中的3、5、7、12、13中可以看出Harris角点检测并未检测到任何特征点,而SIFT算法却极大程度地检测出特征点和特征尺度,根据实验结果11、12等可以看出SIFT提取到的特征点几乎覆盖了图像的主体内容,除了白色地墙壁部分其他部分几乎布满了特征点,这体现了SIFT算法的稳定性和极大的鲁棒性。
  • 根据实验结果7、8、9可以分析出SIFT具有多量性,即使少数的几个物体也可以产生大量SIFT特征向量,图像7、8、9结构简单,涵盖的物体较少,但SIFT算法提取出大量的特征点。
  • 从时间效率上来分析,SIFT特征提取和检测消耗了大量的时间,对于一些特征点丰富且像素点较多的图像的检测时间多达5分钟左右,但其提取特征的准确性不可忽略,大可忽略其时间效率影响。可进行进一步分析,对SIFT算法进行简单优化。

总结SIFT算法和Harris算法的区别如下:

  • Harris特征点提取的精度低和效率不高,运算慢、抗噪能力差以及在实际应用中存在不必要角点簇,Harris特征点检测算法采用高斯滤波进行平滑,图像存在角点信息丢失与偏移的现象。
  • SIFT方法对于旋转、尺度缩放、亮度变化保持不变性,而且对视角变化、仿射变换、噪声也保持一定程度的稳定性,特征点的个数和有效点的比例没有要求。

2.3 SIFT特征匹配

2.2.1 代码实现

from PIL import Image
from pylab import *
import sys
from PCV.localdescriptors import sift

if len(sys.argv) >= 3:
    im1f, im2f = sys.argv[1], sys.argv[2]
else:
    im1f = 'F:\\mr\\JMUclass\\ComputerVison\\CVcode\\test\\test2\\1.jpg'
    im2f = 'F:\\mr\\JMUclass\\ComputerVison\\CVcode\\test\\test2\\2.jpg'
im1 = array(Image.open(im1f))
im2 = array(Image.open(im2f))
sift.process_image(im1f, 'out_sift_1.txt')
l1, d1 = sift.read_features_from_file('out_sift_1.txt')
figure()
gray()
subplot(121)
sift.plot_features(im1, l1, circle=False)
sift.process_image(im2f, 'out_sift_2.txt')
l2, d2 = sift.read_features_from_file('out_sift_2.txt')
subplot(122)
sift.plot_features(im2, l2, circle=False)
# matches = sift.match(d1, d2)
matches = sift.match_twosided(d1, d2)
print ('{} matches'.format(len(matches.nonzero()[0])))
figure()
gray()
sift.plot_matches(im1, im2, l1, l2, matches, show_below=True)
show()

2.2.2 实验结果

1. 实验结果1
控制台输出:

processed tmp.pgm to out_sift_1.txt
processed tmp.pgm to out_sift_2.txt
4078 matches
[Finished in 1109.8s]

可视化匹配效果:
在这里插入图片描述
2. 实验结果2
控制台输出:

processed tmp.pgm to out_sift_1.txt
processed tmp.pgm to out_sift_2.txt
679 matches

可视化匹配效果:
在这里插入图片描述
3. 实验结果3
控制台输出:

processed tmp.pgm to out_sift_1.txt
processed tmp.pgm to out_sift_2.txt
55 matches

可视化匹配效果:
在这里插入图片描述

2.2.3 实验结果分析

由以上三组实验可得以下分析:

  • 在SIFT特征匹配中,针对如上第一个=组实验结果,其运行时间消耗极大,大约执行了约30min,分析可得第一组图像的像素较大,其一张图像共有3024* 4032个像素块,且两张图像的主题一致,其匹配到的特征点共有4078个matches,所以其运行时间消耗大是有原因的。相对来说,第二组和第三组实验中,其像素大小为3024* 3024,相对较小,且其匹配到的特征点的数量是第一组实验的16%和1.2%,所以第二组和第三组的运行时间较快。
  • 针对第一组实验可得:其图像中的建筑物主体几乎完全匹配,由此可以看出SIFT特征匹配的优越表现。
  • 将第一组实验和第二、三组实验进行对比,发现SIFT提取到的特征数量对于特征匹配有一定的影响。第一组实验提取到的特征点多,在后续进行特征匹配的可能性更多,进而获得的matches值较大,而相对于第二、三组实验,其提取到的特征数量较为少,进而得到的matches值也较小。
  • 观察第二组实验结果,其SIFT匹配结果仅为字画的内容匹配较多,而针对于字画的边框并未出现匹配结果,思考可能由于第二组实验中第二张图像对于边框的特征点标注较少,由于其发生旋转后并未寻找到旋转不变性的关键点,从而在匹配过程中得到的关于边框的匹配结果为0。
  • 当特征点不是很多时,经优化的SIFT匹配算法甚至可以达到实时的要求。而且可以很方便的与其他形式的特征向量进行联合。特征点的个数和有效点的比例没有要求。当特征点不是很多时,经优化的SIFT匹配算法甚至可以达到实时的要求。而且可以很方便的与其他形式的特征向量进行联合,SIFT算法也有一些不足。方法通过对特征点构造128维的向量,然后对向量进行匹配,这样图像就得满足足够多的纹理,否则构造出的128维向量区别性就不是太大,容易造成误匹配,极限情况如指纹图像的匹配,星图识别等这类图像特征点周围根本没有什么纹理这时SIFT算法就完全失效了。

2.4 SIFT特征检索并排序

输入一张新的图像,针对数据集中的每一张图像分别与输入图像进行匹配,每一次匹配计算出其matches的值并记录下来。在数据集中的所有图像匹配完成之后,根据matches的大小排序,选取matches值最大的三张图像作为输出。

2.2.1 代码实现

#添加必要的库
import os
import numpy as np
import matplotlib.image as mp
from skimage import img_as_ubyte
from PIL import Image
from pylab import *
import sys
from PCV.localdescriptors import sift

# 添加中文字体支持
from matplotlib.font_manager import FontProperties
font = FontProperties(fname=r"c:\windows\fonts\SimSun.ttc", size=14)
path = "F:\\mr\\JMUclass\\ComputerVison\\CVcode\\test\\test2\\dataset\\"
filelist = os.listdir(path)  # 打开对应的文件夹
total_num = len(filelist)-1  #得到文件夹中图像的个数
matches_array=np.array([0,0,0,0,0,0,0,0,0,0,0,0,0,0,0])
result=np.array([0,0,0])
if len(sys.argv) >= 3:
    im1f, im2f = sys.argv[1], sys.argv[2]
else:
    im1f = 'F:\\mr\\JMUclass\\ComputerVison\\CVcode\\test\\test2\\new.jpg'
im1 = array(Image.open(im1f))
sift.process_image(im1f, 'out_sift_1.txt')
l1, d1 = sift.read_features_from_file('out_sift_1.txt')
figure()
subplot(2,2,1)
plt.axis('off')
title(u'原图',fontproperties=font)
imshow(Image.open(im1f))
for i in range(total_num):
    im2f = path + str(i + 1) + '.jpg' #拼接图像的读取地址
    im2 = array(Image.open(im2f)) 
    sift.process_image(im2f, 'out_sift_'+str(i+2)+'.txt')
    l2, d2 = sift.read_features_from_file('out_sift_'+str(i+2)+'.txt')
    matches = sift.match_twosided(d1, d2)
    matches_array[i] = len(matches.nonzero()[0])
print (matches_array)
for i in range(3):
	a = np.argmax(matches_array)
	im2f = path + str(a+1) + '.jpg' #拼接图像的读取地址
	subplot(2,2,i+2)
	plt.axis('off')
	mstr='matches:'+str(matches_array[a])
	title(mstr,fontproperties=font)
	imshow(Image.open(im2f))
	matches_array[a]=0
show()

2.2.2 实验结果

控制台输出:

processed tmp.pgm to out_sift_1.txt
processed tmp.pgm to out_sift_2.txt
processed tmp.pgm to out_sift_3.txt
processed tmp.pgm to out_sift_4.txt
processed tmp.pgm to out_sift_5.txt
processed tmp.pgm to out_sift_6.txt
processed tmp.pgm to out_sift_7.txt
processed tmp.pgm to out_sift_8.txt
processed tmp.pgm to out_sift_9.txt
processed tmp.pgm to out_sift_10.txt
processed tmp.pgm to out_sift_11.txt
processed tmp.pgm to out_sift_12.txt
processed tmp.pgm to out_sift_13.txt
processed tmp.pgm to out_sift_14.txt
processed tmp.pgm to out_sift_15.txt
processed tmp.pgm to out_sift_16.txt
[2953 1163  197   41    2    1    5    1    3    2    3    3    0    5   1]
[Finished in 1188.2s]

可视化匹配排序结果:

在这里插入图片描述

2.2.3 实验结果分析

  • 在输入一张范围涵盖较大场景的图像与数据集中15张图像进行匹配,进而排序,可得观察到其实验结果的排序与我们目视判读的结果一致,可以得出SIFT特征匹配的准确率是相当高的。其实验效果好与高效提取关键点、准确地对关键点附加详细的信息(局部特征)、通过特征点(附带上特征向量的关 键点)的两两比较找出相互匹配的若干对特征点,建立景物间的对应关系有密不可分地关系。
  • 有一个较大的问题在于该实验出结果的速度很慢,考虑如何对SIFT算法进行改进,有了如下思考:sift特征匹配算法128维的特征描述向量使得处理匹配特征点计算庞大,导致时效性不高。考虑将128维的特征描述向量进行降维处理,并且像素的描述范围增大,或许可以有效减少了匹配的运算次数,缩短图像配准时间。

3. 匹配地理标记图像

3.1 实验代码

# -*- coding: utf-8 -*-
from pylab import *
from PIL import Image
from PCV.localdescriptors import sift
from PCV.tools import imtools
import pydot
import os
os.environ['PATH'] = os.environ['PATH'] + (';C:\\Program Files (x86)\\Graphviz2.38\\bin\\')
""" 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 = "F:/mr/JMUclass/ComputerVison/CVcode/test/test2/dataset"  # set this to the path where you downloaded the panoramio images
path = "F:/mr/JMUclass/ComputerVison/CVcode/test/test2/dataset/"  # path to save thumbnails (pydot needs the full system path)
# list of downloaded filenames
imlist = imtools.get_imlist(download_path)
# print(imlist)
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) + '.png'
            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) + '.png'
            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('whitehouse.png')

3.2 实验结果

控制台输出:

processed tmp.pgm to F:/mr/JMUclass/ComputerVison/CVcode/test/test2/dataset3\1.sift
processed tmp.pgm to F:/mr/JMUclass/ComputerVison/CVcode/test/test2/dataset3\10.sift
processed tmp.pgm to F:/mr/JMUclass/ComputerVison/CVcode/test/test2/dataset3\11.sift
processed tmp.pgm to F:/mr/JMUclass/ComputerVison/CVcode/test/test2/dataset3\12.sift
processed tmp.pgm to F:/mr/JMUclass/ComputerVison/CVcode/test/test2/dataset3\13.sift
processed tmp.pgm to F:/mr/JMUclass/ComputerVison/CVcode/test/test2/dataset3\14.sift
processed tmp.pgm to F:/mr/JMUclass/ComputerVison/CVcode/test/test2/dataset3\15.sift
processed tmp.pgm to F:/mr/JMUclass/ComputerVison/CVcode/test/test2/dataset3\2.sift
processed tmp.pgm to F:/mr/JMUclass/ComputerVison/CVcode/test/test2/dataset3\3.sift
processed tmp.pgm to F:/mr/JMUclass/ComputerVison/CVcode/test/test2/dataset3\4.sift
processed tmp.pgm to F:/mr/JMUclass/ComputerVison/CVcode/test/test2/dataset3\5.sift
processed tmp.pgm to F:/mr/JMUclass/ComputerVison/CVcode/test/test2/dataset3\6.sift
processed tmp.pgm to F:/mr/JMUclass/ComputerVison/CVcode/test/test2/dataset3\7.sift
processed tmp.pgm to F:/mr/JMUclass/ComputerVison/CVcode/test/test2/dataset3\8.sift
processed tmp.pgm to F:/mr/JMUclass/ComputerVison/CVcode/test/test2/dataset3\9.sift
The match scores is: 
 [[214.   1.   0.   0.   1.   0.   0.  39.  11.   5.   1.   1.   1.   0.
    0.]
 [  0. 245.  70.   1.   1.   0.   1.   1.   1.   0.   0.   1.   0.   2.
    1.]
 [  0.   0. 353.   0.   0.   2.   1.   0.   0.   0.   0.   1.   0.   4.
    2.]
 [  0.   0.   0. 344.   1.   0.   0.   0.   0.   0.   0.   0.   0.   0.
    1.]
 [  0.   0.   0.   0. 164.  18.  11.   1.   0.   0.   1.   1.   0.   1.
    0.]
 [  0.   0.   0.   0.   0.  81.   8.   0.   0.   0.   2.   1.   2.   0.
    0.]
 [  0.   0.   0.   0.   0.   0.  46.   1.   0.   0.   0.   2.   0.   3.
    0.]
 [  0.   0.   0.   0.   0.   0.   0. 290.   2.   0.   0.   1.   0.   1.
    0.]
 [  0.   0.   0.   0.   0.   0.   0.   0.  99.  31.   0.   0.   0.   1.
    0.]
 [  0.   0.   0.   0.   0.   0.   0.   0.   0. 202.   0.   0.   0.   0.
    0.]
 [  0.   0.   0.   0.   0.   0.   0.   0.   0.   0. 178.  33.  42.   0.
    0.]
 [  0.   0.   0.   0.   0.   0.   0.   0.   0.   0.   0. 125.   7.   2.
    2.]
 [  0.   0.   0.   0.   0.   0.   0.   0.   0.   0.   0.   0. 132.   1.
    1.]
 [  0.   0.   0.   0.   0.   0.   0.   0.   0.   0.   0.   0.   0.  68.
    7.]
 [  0.   0.   0.   0.   0.   0.   0.   0.   0.   0.   0.   0.   0.   0.
  158.]]
[Finished in 12.8s]

可视化结果:
在这里插入图片描述

3.3 实验结果分析

  • 在进行匹配的时候代码中使用嵌套循环实现,即共进行了15*15次匹配。观察输出的匹配分数矩阵,由于读取图片的顺序是错乱的,分别是以1.jpg,10.jpg,11.jpg,12.jpg,13.jpg,14.jpg,15.jpg,2.jpg,3.jpg,4.jpg,5.jpg,6.jpg,7.jpg,8.jpg,9.jpg的顺序,即在观察分数矩阵的时候行和列的顺序应与上述顺序保持一致。
  • 在控制台的输出结果可以看出一个明显的特点:在矩阵的对角线上,匹配分数都很大。原因是:对角线上的匹配分数其实是在每一轮的循环中,自身与自身进行匹配,匹配分数自然很高,所以出现了这种情况。
  • 可视化的是实验结果可以看出,匹配连接的顺序大致正确,其中有两幅画不是同一幅但产生了一条连接线,主要是由于两幅画的主体内容大部分一致,但一定程度上还是存在较小的错误性。
  • 在创建图像缩略图的时候,根据书本中的介绍将数据集中的图像创建为100* 100的图像出现的结果如实验结果中的截图,由于其出现了一张图像有错误,所以考虑将缩略图的大小略增大,增大为200* 200,结果如下图,观察发现部分连线正确,部分连线产生了很大的错误。所以推测缩略图的大小对匹配结果有较大的影响。
    在这里插入图片描述

3.4 实验遇到的问题及解决

1.出现文件夹读取为空的问题:
在这里插入图片描述
将数据集里的图片的后缀改为小写就可以,比如JPG改为jpg

2.出现运行很慢的问题:

pil_im.thumbnail((100,100))

使用该语句完成缩略图的创建即可。

3.报错"dot" not found in path.:
在这里插入图片描述
解决方法:
先安装pydot:

pip install pydot

然后再下载Graphviz:graphviz-2.38.msi

接着安装graphviz,记住默认的安装路径C:\Program Files (x86)\Graphviz2.38
将Graphviz的bin文件夹的位置配置环境变量path后,添加如下代码:

import os
os.environ['PATH'] = os.environ['PATH'] + (';C:\\Program Files (x86)\\Graphviz2.38\\bin\\')

测试之后还是报错,后来发现,错误提示是找不到“dot”程序,而不是"dot.exe"程序
修改self.prog = 'dot’为self.prog = ‘dot.exe’,之后测试,成功运行。

4. RANSAC算法

RANSAC算法是RANdom SAmple Consensus的缩写,意为随机抽样一致。表面上的意思就是从匹配样本中随机取样,寻找一致的样本点。RANSAC算法是根据一组包含异常数据的样本数据集,计算出数据的数学模型参数,得到有效样本数据的算法。它是在1981年由Fischler和Bolles最先提出。

在利用已有算法进行特征点匹配时,常存在的一个问题就是误匹配的问题,这些误匹配的点对匹配的效果产生很大的影响,所以我们需要利用一定的方法剔除误匹配的特征点,在实际应用中,我们常会用到RANSAC算法来消除两两匹配图像的误匹配点,这个算法现在在图像配准以及拼接上得到了广泛的应用。

RANSAC算法的核心思想就是在匹配的特征点中随机取4个特征点,通过计算和不断迭代,寻找到最优的参数模型,在这个最优模型中,能匹配上的特征点最多。

4.1 算法简介

RANSAC算法是一种简单且有效的去除噪声影响,估计模型的一种方法。与普通的去噪算法不同,RANSAC算法是使用尽可能少的点来估计模型参数,然后尽可能的扩大得到的模型参数的影响范围。
RANSAC算法的具体描述是:给定 N N 个数据点组成的集合 P P ,假设集合中大多数的点都是可以通过一个模型来产生的,且最少 n n 个点( n < N n<N )可以拟合出模型的参数,则可以通过以下的迭代方式拟合该参数。

对下面的操作执行 k k 次:

  1. P P 中随机选择 n n 个数据点;
  2. 用这 n n 个数据点拟合出一个模型 M M
  3. P P 中剩余的数据点,计算每个点与模型 M M 的距离,距离超过阈值的则认定为局外点,不超过阈值的认定为局内点,并记录该模型 M M 所对应的局内点的值 m m
  4. 迭代 k k 次以后,选择 m m 最大的模型 M M 作为拟合的结果。

因为在实际应用中 N N 的值通常会很大,那么从其中任选 n n 个数据点的组合就会很大,如果对所有组合都进行上面的操作运算量就会很大,因此对于 k k 的选择就很重要。通常情况下,只要保证模型估计需要的 n n 个点都是点的概率足够高即可。因此设 w w N N 个数据中局内点的比例, z z 为进行 k k 次选取后,至少有一次选取的 n n 个点都是局内点的概率。则有
z = 1 ( 1 w n ) k z=1-(1-w^n)^k
其中 1 w n 1-w^n 表示一次选取不都是局内点的概率, ( 1 w n ) k (1-w^n)^k 表示 k k 次选取中没有一次都是局内点的概率。 则有
k = l o g ( 1 z ) l o g ( 1 w n ) k=\frac{log(1-z)} {log(1-w^n)}

这里 z z 一般要求满足大于95%即可。

4.2 单应性矩阵

单应性矩阵描述的是针对同一事物,在不同的视角下拍摄的两幅图像之间的关系。假设这两幅图像之间是透视变换,则单应性矩阵也就是透视变换矩阵 H H 定义如下:
H = [ h 11 h 12 h 13 h 21 h 22 h 23 h 31 h 32 1 ] H=\begin{bmatrix} h_{11}& h_{12} & h_{13}\\ h_{21}& h_{22} & h_{23} \\ h_{31}& h_{32} & 1 \end{bmatrix}
则有:
[ x y 1 ] = [ h 11 h 12 h 13 h 21 h 22 h 23 h 31 h 32 1 ] [ x y 1 ] \begin{bmatrix} {x}'\\ {y}'\\ 1 \end{bmatrix}=\begin{bmatrix} h_{11}& h_{12} & h_{13}\\ h_{21}& h_{22} & h_{23} \\ h_{31}& h_{32} & 1 \end{bmatrix}\begin{bmatrix} x\\ y\\ 1 \end{bmatrix}
因此要恢复出 H H 中的8个参数,至少需要4对匹配点,过程如下:

那么就可以每次从所有的匹配点中选出4对,计算单应性矩阵 H H ,然后选出内点个数最多的作为最终的结果。计算距离方法如下:

4.3 RANSAC求解单应矩阵

· RANSAC loop:

  1. 随机选择四对匹配特征
  2. 根据DLT计算单应矩阵 H H (唯一解)
  3. 对所有匹配点,计算映射误差 ε = p i , H p i \varepsilon=\left \| {p_i}',Hp_i\right \|
  4. 根据误差阈值,确定inliers
  5. 针对最大inliers集合,重新计算单应矩阵 H H

4.2 实验代码

该实验设计为先利用SIFT特征匹配对魅族图像进行特征匹配,进而利用RANSAC算法对误匹配点进行剔除,观察实验结果。

本次实验设计分为景深复杂和景深单一的两种场景来分析RANSAC算法的实验结果。

import cv2
import numpy as np
import random

def compute_fundamental(x1, x2):
    n = x1.shape[1]
    if x2.shape[1] != n:
        raise ValueError("Number of points don't match.")

    # build matrix for equations
    A = np.zeros((n, 9))
    for i in range(n):
        A[i] = [x1[0, i] * x2[0, i], x1[0, i] * x2[1, i], x1[0, i] * x2[2, i],
                x1[1, i] * x2[0, i], x1[1, i] * x2[1, i], x1[1, i] * x2[2, i],
                x1[2, i] * x2[0, i], x1[2, i] * x2[1, i], x1[2, i] * x2[2, i]]

    # compute linear least square solution
    U, S, V = np.linalg.svd(A)
    F = V[-1].reshape(3, 3)

    # constrain F
    # make rank 2 by zeroing out last singular value
    U, S, V = np.linalg.svd(F)
    S[2] = 0
    F = np.dot(U, np.dot(np.diag(S), V))

    return F / F[2, 2]


def compute_fundamental_normalized(x1, x2):
    """    Computes the fundamental matrix from corresponding points
        (x1,x2 3*n arrays) using the normalized 8 point algorithm. """

    n = x1.shape[1]
    if x2.shape[1] != n:
        raise ValueError("Number of points don't match.")

    # normalize image coordinates
    x1 = x1 / x1[2]
    mean_1 = np.mean(x1[:2], axis=1)
    S1 = np.sqrt(2) / np.std(x1[:2])
    T1 = np.array([[S1, 0, -S1 * mean_1[0]], [0, S1, -S1 * mean_1[1]], [0, 0, 1]])
    x1 = np.dot(T1, x1)

    x2 = x2 / x2[2]
    mean_2 = np.mean(x2[:2], axis=1)
    S2 = np.sqrt(2) / np.std(x2[:2])
    T2 = np.array([[S2, 0, -S2 * mean_2[0]], [0, S2, -S2 * mean_2[1]], [0, 0, 1]])
    x2 = np.dot(T2, x2)

    # compute F with the normalized coordinates
    F = compute_fundamental(x1, x2)
    # print (F)
    # reverse normalization
    F = np.dot(T1.T, np.dot(F, T2))

    return F / F[2, 2]

def randSeed(good, num =8 ):
    '''
    :param good: 初始的匹配点对
    :param num: 选择随机选取的点对数量
    :return: 8个点对list
    '''
    eight_point = random.sample(good, num)
    return eight_point

def PointCoordinates(eight_points, keypoints1, keypoints2):
    '''
    :param eight_points: 随机八点
    :param keypoints1: 点坐标
    :param keypoints2: 点坐标
    :return:8个点
    '''
    x1 = []
    x2 = []
    tuple_dim = (1.,)
    for i in eight_points:
        tuple_x1 = keypoints1[i[0].queryIdx].pt + tuple_dim
        tuple_x2 = keypoints2[i[0].trainIdx].pt + tuple_dim
        x1.append(tuple_x1)
        x2.append(tuple_x2)
    return np.array(x1, dtype=float), np.array(x2, dtype=float)


def ransac(good, keypoints1, keypoints2, confidence,iter_num):
    Max_num = 0
    good_F = np.zeros([3,3])
    inlier_points = []
    for i in range(iter_num):
        eight_points = randSeed(good)
        x1,x2 = PointCoordinates(eight_points, keypoints1, keypoints2)
        F = compute_fundamental_normalized(x1.T, x2.T)
        num, ransac_good = inlier(F, good, keypoints1, keypoints2, confidence)
        if num > Max_num:
            Max_num = num
            good_F = F
            inlier_points = ransac_good
    print(Max_num, good_F)
    return Max_num, good_F, inlier_points


def computeReprojError(x1, x2, F):
    """
    计算投影误差
    """
    ww = 1.0/(F[2,0]*x1[0]+F[2,1]*x1[1]+F[2,2])
    dx = (F[0,0]*x1[0]+F[0,1]*x1[1]+F[0,2])*ww - x2[0]
    dy = (F[1,0]*x1[0]+F[1,1]*x1[1]+F[1,2])*ww - x2[1]
    return dx*dx + dy*dy

def inlier(F,good, keypoints1,keypoints2,confidence):
    num = 0
    ransac_good = []
    x1, x2 = PointCoordinates(good, keypoints1, keypoints2)
    for i in range(len(x2)):
        line = F.dot(x1[i].T)
        #在对极几何中极线表达式为[A B C],Ax+By+C=0,  方向向量可以表示为[-B,A]
        line_v = np.array([-line[1], line[0]])
        err = h = np.linalg.norm(np.cross(x2[i,:2], line_v)/np.linalg.norm(line_v))
        # err = computeReprojError(x1[i], x2[i], F)
        if abs(err) < confidence:
            ransac_good.append(good[i])
            num += 1
    return num, ransac_good


if __name__ =='__main__':
    im1 = 'image/33.png'
    im2 = 'image/44.png'

    print(cv2.__version__)
    psd_img_1 = cv2.imread(im1, cv2.IMREAD_COLOR)
    psd_img_2 = cv2.imread(im2, cv2.IMREAD_COLOR)
    # 3) SIFT特征计算
    sift = cv2.xfeatures2d.SIFT_create()
    # find the keypoints and descriptors with SIFT
    kp1, des1 = sift.detectAndCompute(psd_img_1, None)
    kp2, des2 = sift.detectAndCompute(psd_img_2, None)

    # FLANN 参数设计
    match = cv2.BFMatcher()
    matches = match.knnMatch(des1, des2, k=2)

    # Apply ratio test
    # 比值测试,首先获取与 A距离最近的点 B (最近)和 C (次近),
    # 只有当 B/C 小于阀值时(0.75)才被认为是匹配,
    # 因为假设匹配是一一对应的,真正的匹配的理想距离为0
    good = []
    for m, n in matches:
        if m.distance < 0.75 * n.distance:
            good.append([m])
    print(good[0][0])

    print("number of feature points:",len(kp1), len(kp2))
    print(type(kp1[good[0][0].queryIdx].pt))
    print("good match num:{} good match points:".format(len(good)))
    for i in good:
        print(i[0].queryIdx, i[0].trainIdx)


    Max_num, good_F, inlier_points = ransac(good, kp1, kp2, confidence=30, iter_num=500)
    # cv2.drawMatchesKnn expects list of lists as matches.
    # img3 = np.ndarray([2, 2])
    # img3 = cv2.drawMatchesKnn(img1, kp1, img2, kp2, good[:10], img3, flags=2)

    # cv2.drawMatchesKnn expects list of lists as matches.

    img3 = cv2.drawMatchesKnn(psd_img_1,kp1,psd_img_2,kp2,good,None,flags=2)
    img4 = cv2.drawMatchesKnn(psd_img_1,kp1,psd_img_2,kp2,inlier_points,None,flags=2)
    cv2.namedWindow('image1', cv2.WINDOW_NORMAL)
    cv2.namedWindow('image2', cv2.WINDOW_NORMAL)
    cv2.imshow("image1",img3)
    cv2.imshow("image2",img4)
    cv2.waitKey(0)#等待按键按下
    cv2.destroyAllWindows()#清除所有窗口

4.3 实验结果及分析

4.3.1 景深单一场景

控制台输出:

3.3.0
<DMatch 00000223D14F2450>
number of feature points: 3629 4108
<class 'tuple'>
good match num:975 good match points:
[51 1314,62 604,73 1314,82 604,99 1314,106 604,141 2859,155 2859,165 3296,169 622,234 1314......]
260 [[ 2.70487221e-07  2.30359993e-06 -3.19941970e-03]
 [-2.24579797e-06 -9.47445524e-09  2.94200972e-03]
 [ 2.83485400e-03 -3.80271280e-03  1.00000000e+00]]

可视化结果:
RANSAC前:

RANSAC后:

实验小结:

  • 该组实验使用景深较为单一的场景图片进行实验,发现其删除匹配线的效果很明显,在进行SIFT特征匹配时,由于画框的颜色材质与天花板以及场景中床头装饰的颜色材质较为一致,所以发生了较多错乱的匹配线。
  • 在经过RANSAC算法过滤后删除了大多数的错配点,但是仔细观察RANSAC后的实验结果,仍发现一条在画框和天花板装饰之间的粉色匹配线;观察画中特征点的匹配效果得到,画中扇形右下角的特征点与左下角的特征点有一条匹配线,两点并非同一点其周围以及其本身相似程度较大产生了匹配线,而RANSAC算法并没有排除这条匹配先;最后观察画中上半部分原有很多匹配线,而RANSAC后画中上半部分已全无匹配线,可知在RANSAC算法进行中可能删除了大部分匹配正确的匹线。由以上可以得出结论:RANSAC算法并非百分之百准确,仍有优化的空间。
  • 思考:在这组实验中结果并没有那么理想,出现了较多以上叙述的RANSAC算法的筛选不正确性,思考如果增加迭代次数,结果是否会优化。由于以往实验的经验,增加迭代次数并非会带来好的结果,对于这一问题将在之后进行多次实验进行观察。

4.3.2 景深复杂场景

控制台输出:

3.3.0
<DMatch 0000013D09470BD0>
number of feature points: 4544 3785
<class 'tuple'>
good match num:396 good match points:[18 190,19 140,25 312,45 129,60 114,62 139,107 129,116 129,127 114,131 139,133 190,142 175,224 241,240 248,264 277,291 1739,400 1462,408 1073,438 1345,503 569,514 547,543 410,550 569,......]  
228 [[ 4.24486619e-06 -8.04672331e-06 -3.21653120e-03]
 [ 7.58870199e-06  1.42993003e-06 -3.03983563e-03]
 [-3.83888038e-05  5.73794118e-03  1.00000000e+00]]

可视化结果:
RANSAC前:

RANSAC后:

实验分析:

  • 该组实验使用景深较为丰富的场景图片进行实验,由可视化结果可以看到大多数误匹配的特征点之间的连线被删除,进行RANSAC后的匹配结果更加整齐,并无原匹配结果的错乱复杂。
  • 在这组实验中更好地展现了RANSAC算法的优势,观察建筑物之间的错匹配已成功删除、天空中存在的错匹配也成功删除、街边路灯与建筑物之间的错匹配也成功删除…,由以上可得RANSAC算法求解模型的准确性较高,将匹配点带入求解地模型其中可以较大程度地检测出其不正确性进而删除匹配线。可以得到结论:RANSAC算法经过迭代得到的模型较为准确,模型的准确性决定着删除误匹配的效果。
  • 在该组实验中,程序运行速度很慢长达5min,后来经过思考知道是由于图像本身像素较大,在进行SIFT匹配时速度较慢,按照比例缩小图像的时候运行速度得到改善,同时发现了图像比例不同得到的结果也有所不同。

4.4 实验总结

RANSAC的优点是它能鲁棒的估计模型参数。例如,它能从包含大量局外点的数据集中估计出高精度的参数。
RANSAC的缺点是它计算参数的迭代次数没有上限;如果设置迭代次数的上限,得到的结果可能不是最优的结果,甚至可能得到错误的结果。RANSAC只有一定的概率得到可信的模型,概率与迭代次数成正比。RANSAC的另一个缺点是它要求设置跟问题相关的阀值。
RANSAC只能从特定的数据集中估计出一个模型,如果存在两个(或多个)模型,RANSAC不能找到别的模型。

4.5 实验中遇到的问题及解决办法

  1. 遇到报错:ValueError: Sample larger than population or is negative
    解决方法:
    开始考虑是否为random.py中的函数发生错误,经过观察可得代码中的带的匹配点的数量为0,将0值传入随机函数中发生错误。进而思考为何匹配点数量为0,经过查看图像详细信息发现两张图像的尺寸不同,使用resize()函数将图像尺寸改为一致再进行实验即可成功运行。
  2. 遇到报错:
    cv2. error: OpenCV(4.1.2) C: \projects lopencv- python \opencv_ contrib \modules \xfeatures2d\src\sift.cpp:1207: error: ( - 213:The function/ feature is notimplemented) This algorithm is patented and is excluded in this configuration; Set OPENCV ENABLE_ NONFREE CMake option and rebuild the library infunction ’ cv: :xfeatures2d: :SIFT : :create
    解决方法:
    在cmd中运行如下命令即可。
pip install opencv-contrib-python==3.3.0.10

5. 实验总结与分析

总体来说,SIFT算法具有以下特性:

  • Sift特征是图像的局部特征,对平移、旋转、尺度缩放、亮度变化、遮挡和噪声等具有良好的不变性,对视觉变化、仿射变换也保持一定程度的稳定性。
  • SIFT算法独特性好,信息量丰富,适用于在海量特征数据库中进行快速、准确的匹配。在实验过程中有明确的体现,尤其在SIFT算法与Harris算法的对比中可以看出SIFT算法检测到的信息量丰富。
  • SIFT算法具有多量性,即使少数的几个物体也可以产生大量Sift特征向量。
  • SIFT速度相对较快,经查阅可得目前有较多论文对SIFT算法进行了较大程度的优化,经优化的Sift匹配算法甚至可以达到实时的要求。
  • SIFT算法的可扩展性强,可以很方便的与其他形式的特征向量进行联合。

SIFT在图像的不变特征提取方面拥有较大的优势,但仍然存在以下不足:

  • 实时性不高,主要是由于其运行时间消耗太大,而对于实时性的问题,需要在几毫秒之内或1s之内出结果,而在本次实验中稍微过于复杂的图像进行SIFT匹配时耗时可达30min,这几乎对实时性的问题无法进行很好的解决。

  • SIFT算法在进行特征提取的过程中,有时特征点较少。由本次实验中的部分实验结果可以体现。

  • SIFT算法对边缘光滑的目标无法准确提取特征点。由于其对图像进行了高斯模糊,所以对于平滑的目标提取其轮廓较为困难。就像上面提到的SIFT特征匹配对两幅字画进行匹配时,只对于图像的主体内容进行匹配,而对字画的边框并无匹配点。

6. 实验中遇到的问题

问题描述如下:

OSError: empire.sift not found

原因分析:
报错提示的代码行为l1, d1 = sift.read_features_from_file(‘empire.sift’),说明没有读取到empire.sift,说明在sift.process_image阶段没有生成sift文件

解决方案:
为了计算图像的SIFT特征,我们需要用到开源工具包VLFeat。下载链接 :“http://www.vlfeat.org/download/
注意:这里应该下载0.9.20版本的才可用,我下载的是vlfeat-0.9.20-bin.tar.gz。下载完后解压)。在下载过程中,由于其为外网,下载速度很慢且会出现多次网络错误中断,多次尝试即可解决,如果不行可以联系我把压缩包发送给你。
在这里插入图片描述
接下来需要进行的操作步骤:

  1. 把vlfeat文件夹bin下win64中的sift.exe和vl.dll这两个文件复制到项目的文件夹中。

  2. 修改Anaconda文件夹下的PCV(我的PCV位置E:\Anaconda3\Lib\site-packages\PCV)文件夹里面的localdescriptors文件夹中的sift.py文件,使用记事本打开,修改其中的cmmd内的路径为:cmmd = str(r"D:\PythonWork\SIFT\sift.exe “+imagename+” --output="+resultname+" "+params) (路径是你项目文件夹中的sift.exe的路径),一定要记得在括号内加r,否则还是会出错。

  3. 之后就可以运行了。如果在运行过程中提示关于print的错误,记得根据错误提醒的文件夹,去修改相应的print语法,3.5的python的print用法是需要加括号。

发布了13 篇原创文章 · 获赞 5 · 访问量 2184

猜你喜欢

转载自blog.csdn.net/weixin_45617915/article/details/104679052