尺度不变特征转换(Scale-invariant feature transform或SIFT) 是一种电脑视觉的算法用来侦测与描述影像中的局部性特征,它在空间尺度中寻找极值点,并提取出其位置、尺度、旋转不变量。
1999年David G.Lowe教授总结了基于特征不变技术的检测方法,在图像尺度 空间基础上,提出了对图像缩放、旋转保持不变性的图像局部特征描述算子 -SIFT(尺度不变特征变换),该算法在2004年被加以完善。其应用范围包含物体辨识、机器人地图感知与导航、影像缝合、3D模型建立、手势辨识、影像追踪和动作比对。
局部影像特征的描述与侦测可以帮助辨识物体,SIFT 特征是基于物体上的一些局部外观的兴趣点而与影像的大小和旋转无关。对于光线、噪声、些微视角改变的容忍度也相当高。基于这些特性,它们是高度显著而且相对容易撷取,在母数庞大的特征数据库中,很容易辨识物体而且鲜有误认。使用 SIFT特征描述对于部分物体遮蔽的侦测率也相当高,甚至只需要3个以上的SIFT物体特征就足以计算出位置与方位。在现今的电脑硬件速度下和小型的特征数据库条件下,辨识速度可接近即时运算。SIFT特征的信息量大,适合在海量数据库中快速准确匹配。
SIFT算法可以解决的问题:
SIFT中要查找的关键点:
一些十分突出的点不会因光照、尺度、旋转等因素的改变而消失,比如角点、边缘点、暗区域的亮点以及亮区域的暗点。既然两幅图像中有相同的景物,那么使用某种方法分别提取各自的稳定点,这些点之间会有相互对应的匹配点。
尺度空间理论最早于1962年提出,其主要思想是通过 对原始图像进行尺度变换,获得图像多尺度下的空间表示。从而实现边缘、角点检测和不同分辨率上的特征提取,以满足特征点的尺度不变性。
尺度空间中各尺度图像的模糊程度逐渐变大,能够模拟人在距离目标由近到远时目标在视网膜上的形成过程。尺度越大图像越模糊。
一个图像的尺度空间 L ( x , y , σ ) L(x,y,\sigma) L(x,y,σ),定义为一个变化尺度的高斯函数 G ( x , y , σ ) G(x,y,\sigma) G(x,y,σ)与原图像 I ( x , y ) 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) L(x,y,σ)=G(x,y,σ)∗I(x,y) 其中 ∗ * ∗表示卷积运算。
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}} G(x,y,σ)=2Πσ21e2σ2(x−m/2)2+(y−n/2)2
其中, m , n m,n m,n表示高斯模板的维度(由确定)。 ( x , y ) (x, y) (x,y)代表图像的像素位置。 σ \sigma σ是尺度空间因子,值越小表示图像被平滑的越少,相应的尺度也就越小。大尺度对应于图像的概貌特征,小尺度对应于图像的细节特征。
高斯模糊是在Adobe Photoshop等图像处理软件中广泛使用的处理 效果,通常用它来减小图像噪声以及降低细节层次。这种模糊技术生成 的图像的视觉效果是好像经过一个半透明的屏幕观察图像。如下图效果:
图像的金字塔模型是指,将原始图像不断降阶采样,得到一系列大小不一的图像,由大到小,从下到上构成的塔状模型。原图像为金子塔的第一层,每次降采样所得到的新图像为金字塔的一层(每层一张图像),每个金字塔共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)}] n=log2min(M,N)−t,t∈[0,log2min(M,N)]
其中 M , N M,N M,N为原图像的大小, t 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)=σ02s/S
其中,σ为尺度空间坐标; s为sub-level层坐标; σ 0 σ_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) 2i−1(σ,kσ,k2σ,...,kn−1σ)
k = 2 1 / S k=2^{1/S} k=21/S
其中,i为金字塔组数 n为每一组的层数。
用更高效的高斯差分算子代替拉普拉斯算子进行极值检测,公式如下:
L ( x , y , σ ) = G ( x , y , σ ) ∗ I ( x , y ) L(x,y,σ)=G(x,y,\sigma) * I(x,y) L(x,y,σ)=G(x,y,σ)∗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) D(x,y,σ)=[G(x,y,kσ)−G(x,y,σ]∗I(x,y)=L(x,y,kσ)−L(x,y,σ)
由上式可知:DoG在计算上只需相邻高斯平滑后图像相减,因此简化了计算。
对应DOG算子,需构建DOG金字塔。可以通过高斯差分图像看出图像上的像素值变化情况。(如果没有变化,也就没有特征。特征必须是变化尽可能多的点。) DOG图像描绘的是目标的轮廓。
在实际计算时,使用高斯金字塔每组中相邻上下两层图像相减,得到高斯差分图像,如下图所示,进行极值检测。
通过尺度不变性求极值点,可以使其具有缩放不变的性质。而利用关键点邻域像素的梯度方向分布特性,可以为每个关键点指定方向参数方向,从而使描述子对图像旋转具有不变性。通过求每个极值点的梯度来为极值点赋予方向。
下图是一个SIFT描述子事例。其中描述子由2×2×8维向量表征,也即是 2×2个8方向的方向直方图组成。左图的种子点由8×8单元组成。每一个小格 都代表了特征点邻域所在的尺度空间的一个像素,箭头方向代表了像素梯度方 向,箭头长度代表该像素的幅值。然后在4×4的窗口内计算8个方向的梯度方 向直方图。绘制每个梯度方向的累加可形成一个种子点,如右图所示:一个特 征点由4个种子点的信息所组成。
Lowe实验结果 表明:描述子采用4×4×8=128维向量表征, 综合效果最优 (不变性与独 特性)。
分别对模板图(参考图,reference image)和实时图(观测图,observation image)建立关键点描述子集合。目标的识别是通过两点集内关键点描述子的比对来完成。具有128维的关键点描述子的相似 性度量采用欧式距离。
穷举匹配将原图像中的每一个特征点与目标图像中的每一个特征点进行匹配,如下图所示:
模板图中关键点描述子: R i = ( r i 1 , r i 2 , . . . , r i 128 ) R_i=(r_{i1},r_{i2},...,r_{i128}) Ri=(ri1,ri2,...,ri128)
实时图中关键点描述子: S i = ( s i 1 , s i 2 , . . . , s i 128 ) S_i=(s_{i1},s_{i2},...,s_{i128}) Si=(si1,si2,...,si128)
任意两描述子相似性度量: 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(Ri,Si)=∑j=1128(rij−sij)2
要得到配对的关键点描述子, d ( R i , S i ) d(R_i,S_i) d(Ri,Si)需满足:
实 时 图 中 距 离 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 实时图中距离Ri的次最近的点Sp实时图中距离Ri最近的点Sj<Threshold
关键点的匹配可以采用穷举法来完成,但是这样耗费的时间太多,一 般都采用Kd树的数据结构来完成搜索。搜索的内容是以目标图像的关 键点为基准,搜索与目标图像的特征点最邻近的原图像特征点和次邻 近的原图像特征点。Kd树是一个平衡二叉树。
SIFT算法的实质可以归为在不同尺度空间上查找特征点(关键点)的问题。
SIFT算法实现特征匹配的流程:
针对SIFT特征提取和检测算法,构造了一个小型数据集进行接下来的实验过程,数据集如下图:
针对数据集的每一张图像实现SIFT特征提取并展示,对比Harris检测的效果。
# -*- 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()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
由以上实验结果可得如下分析:
总结SIFT算法和Harris算法的区别如下:
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()
1. 实验结果1
控制台输出:
processed tmp.pgm to out_sift_1.txt
processed tmp.pgm to out_sift_2.txt
4078 matches
[Finished in 1109.8s]
processed tmp.pgm to out_sift_1.txt
processed tmp.pgm to out_sift_2.txt
679 matches
processed tmp.pgm to out_sift_1.txt
processed tmp.pgm to out_sift_2.txt
55 matches
由以上三组实验可得以下分析:
输入一张新的图像,针对数据集中的每一张图像分别与输入图像进行匹配,每一次匹配计算出其matches的值并记录下来。在数据集中的所有图像匹配完成之后,根据matches的大小排序,选取matches值最大的三张图像作为输出。
#添加必要的库
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()
控制台输出:
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]
可视化匹配排序结果:
# -*- 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')
控制台输出:
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]
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’,之后测试,成功运行。
RANSAC算法是RANdom SAmple Consensus的缩写,意为随机抽样一致。表面上的意思就是从匹配样本中随机取样,寻找一致的样本点。RANSAC算法是根据一组包含异常数据的样本数据集,计算出数据的数学模型参数,得到有效样本数据的算法。它是在1981年由Fischler和Bolles最先提出。
在利用已有算法进行特征点匹配时,常存在的一个问题就是误匹配的问题,这些误匹配的点对匹配的效果产生很大的影响,所以我们需要利用一定的方法剔除误匹配的特征点,在实际应用中,我们常会用到RANSAC算法来消除两两匹配图像的误匹配点,这个算法现在在图像配准以及拼接上得到了广泛的应用。
RANSAC算法的核心思想就是在匹配的特征点中随机取4个特征点,通过计算和不断迭代,寻找到最优的参数模型,在这个最优模型中,能匹配上的特征点最多。
RANSAC算法是一种简单且有效的去除噪声影响,估计模型的一种方法。与普通的去噪算法不同,RANSAC算法是使用尽可能少的点来估计模型参数,然后尽可能的扩大得到的模型参数的影响范围。
RANSAC算法的具体描述是:给定 N N N个数据点组成的集合 P P P,假设集合中大多数的点都是可以通过一个模型来产生的,且最少 n n n个点( n < N n
对下面的操作执行 k k k次:
因为在实际应用中 N N N的值通常会很大,那么从其中任选 n n n个数据点的组合就会很大,如果对所有组合都进行上面的操作运算量就会很大,因此对于 k k k的选择就很重要。通常情况下,只要保证模型估计需要的 n n n个点都是点的概率足够高即可。因此设 w w w为 N N N个数据中局内点的比例, z z z为进行 k k k次选取后,至少有一次选取的 n n n个点都是局内点的概率。则有
z = 1 − ( 1 − w n ) k z=1-(1-w^n)^k z=1−(1−wn)k
其中 1 − w n 1-w^n 1−wn表示一次选取不都是局内点的概率, ( 1 − w n ) k (1-w^n)^k (1−wn)k表示 k k k次选取中没有一次都是局内点的概率。 则有
k = l o g ( 1 − z ) l o g ( 1 − w n ) k=\frac{log(1-z)} {log(1-w^n)} k=log(1−wn)log(1−z)
这里 z z z一般要求满足大于95%即可。
单应性矩阵描述的是针对同一事物,在不同的视角下拍摄的两幅图像之间的关系。假设这两幅图像之间是透视变换,则单应性矩阵也就是透视变换矩阵 H 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} H=⎣⎡h11h21h31h12h22h32h13h231⎦⎤
则有:
[ 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} ⎣⎡x′y′1⎦⎤=⎣⎡h11h21h31h12h22h32h13h231⎦⎤⎣⎡xy1⎦⎤
因此要恢复出 H H H中的8个参数,至少需要4对匹配点,过程如下:
那么就可以每次从所有的匹配点中选出4对,计算单应性矩阵 H H H,然后选出内点个数最多的作为最终的结果。计算距离方法如下:
· RANSAC loop:
该实验设计为先利用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()#清除所有窗口
控制台输出:
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后:
实验小结:
控制台输出:
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的另一个缺点是它要求设置跟问题相关的阀值。
RANSAC只能从特定的数据集中估计出一个模型,如果存在两个(或多个)模型,RANSAC不能找到别的模型。
pip install opencv-contrib-python==3.3.0.10
总体来说,SIFT算法具有以下特性:
SIFT在图像的不变特征提取方面拥有较大的优势,但仍然存在以下不足:
实时性不高,主要是由于其运行时间消耗太大,而对于实时性的问题,需要在几毫秒之内或1s之内出结果,而在本次实验中稍微过于复杂的图像进行SIFT匹配时耗时可达30min,这几乎对实时性的问题无法进行很好的解决。
SIFT算法在进行特征提取的过程中,有时特征点较少。由本次实验中的部分实验结果可以体现。
SIFT算法对边缘光滑的目标无法准确提取特征点。由于其对图像进行了高斯模糊,所以对于平滑的目标提取其轮廓较为困难。就像上面提到的SIFT特征匹配对两幅字画进行匹配时,只对于图像的主体内容进行匹配,而对字画的边框并无匹配点。
问题描述如下:
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。下载完后解压)。在下载过程中,由于其为外网,下载速度很慢且会出现多次网络错误中断,多次尝试即可解决,如果不行可以联系我把压缩包发送给你。
接下来需要进行的操作步骤:
把vlfeat文件夹bin下win64中的sift.exe和vl.dll这两个文件复制到项目的文件夹中。
修改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,否则还是会出错。
之后就可以运行了。如果在运行过程中提示关于print的错误,记得根据错误提醒的文件夹,去修改相应的print语法,3.5的python的print用法是需要加括号。