(1)针对自己所处的环境,拍摄多张图片(注意要来自不同场景),构造出一个小的数据集(15张以上)
(2)实现数据集中,每张图片的SIFT特征提取,并展示特征点
(3)给定两张图片,计算其SIFT特征匹配结果
(4)给定一张输入的图片,在数据集内部进行检索,输出与其匹配最多的三张图片
语言:python2.7.13 (anaconda2)
平台:pycharm 2018.2
近年来,提高特征描述点检测与描述有了很大的发展,下面我们会看这其中最好的算法之一——SIFT。
SIFT算法可以解决的问题
• 目标的旋转、缩放、平移(RST)
• 图像仿射/投影变换(视点viewpoint)
• 弱光照影响(illumination)
• 部分目标遮挡(occlusion)
• 杂物场景(clutter)
• 噪声
1.检测尺度空间极值
其主要思想是通过对原始图像进行尺度变换,获得图像多尺度下的空间表示。 从而实现边缘、角点检测和不同分辨率上的特征提取,以 满足特征点的尺度不变性。尺度空间中各尺度图像的 模糊程度逐渐变大,能够模拟 人在距离目标由近到远时目标 在视网膜上的形成过程。一个 图像的尺度空间,L(x, y, σ) ,定义为原始图像 I(x, y)与一个可变尺度的2 维高斯函数G(x, y, σ) 卷积运算。
G ( x i , y i , σ ) = 1 2 π σ 2 e x p ( − ( x − x i ) 2 + ( y − y i ) 2 2 σ 2 ) G(x_i,y_i,σ)=\frac {1} {2πσ^2}exp(-\frac {(x-x_i)^2+(y-y_i)^2} {2σ^2}) G(xi,yi,σ)=2πσ21exp(−2σ2(x−xi)2+(y−yi)2)
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)
2.高斯金字塔
高斯金字塔的构建过程可分为两步:
(1)对图像做高斯平滑;
(2)对图像做降采样。
为了让尺度体现其连续性,在简单下采样的基础上加上了高斯滤波。 一幅图像可以产生几组(octave) 图像,一组图像包括几层 (interval)图像。
高斯图像金字塔共o组、s层,
则有: σ ( s ) = σ 0 2 s S σ(s)=σ_02^\frac {s} {S} σ(s)=σ02Ss
σ:尺度空间坐标;
s:sub-level层坐标;
σ 0 σ_0 σ0:初始尺度;
S:每组层数(一般为3~5)
最后可将组内和组间尺度归为: 2 i − 1 ( σ , k σ , k 2 σ , . . . , k n − 1 σ ) 2^{i-1}(σ,kσ,k^2σ,...,k^{n-1}σ) 2i−1(σ,kσ,k2σ,...,kn−1σ)
i:金字塔组数
n:每一组的层数
3.关键点检测DoG
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)
D ( x , y , σ ) = [ G ( x , y , k σ ) − G ( x , y , σ ) ] ∗ I ( x , y ) D(x,y,σ)=[G(x,y,kσ)-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 , σ ) =L(x,y,kσ)-L(x,y,σ) =L(x,y,kσ)−L(x,y,σ)
DoG在计算上只需相邻高斯平滑后图像相减,因此简化了计算!
对应DOG算子,需构建DOG金字塔可以通过高斯差分图 像看出图像上的像素 值变化情况。(如果 没有变化,也就没有 特征。特征必须是变 化尽可能多的点。) DOG图像描绘的是目 标的轮廓。
4.DOG局部极值检测
DoG的局部极值点
特征点是由DOG空间的局部极值点组成的。为了寻找DoG函数的极值点, 每一个像素点要和它所有的相邻点比较,看其是否比它的图像域和尺度域 的相邻点大或者小。中间的检测点和它同尺度的8个相邻点和上下相邻尺度对应的9×2个 点共26个点比较,以确保在尺度空间和二维图像空间都检测到极值点。
去除边缘响应
由于DoG函数在图像边缘有较强的边缘响应,因此需要排除边缘响应。 DoG函数的峰值点在边缘方向有较大的主曲率,而在垂直边缘的方向有 较小的主曲率。主曲率可以通过计算在该点位置尺度的2×2的Hessian矩 阵得到,导数由采样点相邻差来估计:
[ D x x D x y D x y D y y ] \begin{bmatrix} D_{xx} & D_{xy} \\ D_{xy} & D_{yy} \\ \end{bmatrix} [DxxDxyDxyDyy]
Dxx 表示DOG金字塔中某一尺度的图像x方向求导两次。
D的主曲率和H的特征值成正比。令 α ,β为特征值,则
T r ( H ) 2 d e t ( H ) = ( α + β ) 2 α β \frac {Tr(H)^2} {det(H)}=\frac {(α+β)^2} {αβ} det(H)Tr(H)2=αβ(α+β)2
d e t ( H ) = α β det(H)=αβ det(H)=αβ
t r a c e ( H ) = α + β trace(H)=α+β trace(H)=α+β
该值在两特征值相等时达最小。Lowe论文中建议阈值T为1.2,即 T r ( H ) 2 D e t ( H ) < T \frac {Tr(H)^2} {Det(H)}
5.关键点方向分配
通过尺度不变性求极值点,可以使其具有缩放不变的性质。而利 用关键点邻域像素的梯度方向分布特性,可以为每个关键点指定方向参数 方向,从而使描述子对图像旋转具有不变性。
通过求每个极值点的梯度来为极值点赋予方向。
对于在DOG金字塔中检测出的关键点,采集其所在高斯金字塔图像3σ邻域窗口内像素的梯度和方向分布特征。梯度的模值和方向如下:
g r a d I ( x , y ) = ( I ′ ( x ) , I ′ ( y ) ) gradI(x,y)=(I'(x),I'(y)) gradI(x,y)=(I′(x),I′(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 ) = t a n − 1 [ L ( x , y + 1 ) − L ( x , y − 1 ) L ( x + 1 , y ) − L ( x − 1 , y ) ] θ(x,y)=tan^{-1}[\frac {L(x,y+1)-L(x,y-1)} {L(x+1,y)-L(x-1,y)}] θ(x,y)=tan−1[L(x+1,y)−L(x−1,y)L(x,y+1)−L(x,y−1)]
6.方向直方图的生成
确定关键点的方向采用梯度直方图统计法,统计以关键点为原 点,一定区域内的图像像素点对关键点方向生成所作的贡献。确定关键点的方向采用梯度直方图统计法,统计以关键点为原 点,一定区域内的图像像素点对关键点方向生成所作的贡献。
关键点主方向:极值点周围区域梯度直方图的主峰值也是特征点方向 • 关键点辅方向:在梯度方向直方图中,当存在另一个相当于主峰值 80%能量的峰值时,则将这个方向认为是该关键点的辅方向。 这可以增强匹配的鲁棒性,Lowe的论文指出大概有15%关键点具有 多方向,但这些点对匹配的稳定性至为关键。
7.关键点描述
下图是一个SIFT描述子事例。其中描述子由2×2×8维向量表征,也即是 2×2个8方向的方向直方图组成。左图的种子点由8×8单元组成。每一个小格 都代表了特征点邻域所在的尺度空间的一个像素,箭头方向代表了像素梯度方 向,箭头长度代表该像素的幅值。然后在4×4的窗口内计算8个方向的梯度方 向直方图。绘制每个梯度方向的累加可形成一个种子点,如右图所示:一个特 征点由4个种子点的信息所组成。
Lowe实验结果 表明:描述子 采用4×4×8= 128维向量表征, 综合效果最优 (不变性与独 特性)
8.关键点匹配
分别对模板图(参考图,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 , y − s i , y ) 2 d(R_i,S_i)=\sqrt {\sum_{j=1}^{128} {(r_{i,y}-s_{i,y})^2}} d(Ri,Si)=∑j=1128(ri,y−si,y)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}
关键点的匹配可以采用穷举法来完成,但是这样耗费的时间太多,一 般都采用kd树的数据结构来完成搜索。搜索的内容是以目标图像的关 键点为基准,搜索与目标图像的特征点最邻近的原图像特征点和次邻 近的原图像特征点。(Kd树是一个平衡二叉树)
为了计算图像的SIFT特征,我们用开源工具包VLFeat。用Python重新实现SIFT特征提取的全过程不会很高效,而且也超出了本书的范围。VLFeat可以在www.vlfeat.org上下载,它的二进制文件可以用于一些主要的平台。这个库是用C写的,不过我们可以利用它的命令行接口。此外,它还有Matlab接口。
# -*- 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 = 'D:\PyCharm Community Edition 2018.2.3\siftpictures\B1.jpg' #图片路径
im = array(Image.open(imname).convert('L'))
sift.process_image(imname, 'B1.sift')
l1, d1 = sift.read_features_from_file('B1.sift')
figure()
gray()
subplot(131)
sift.plot_features(im, l1, circle=False)
title(u'SIFT特征', fontproperties=font)
subplot(132)
sift.plot_features(im, l1, circle=True)
title(u'用圆圈表示SIFT特征尺度', fontproperties=font)
# 检测harris角点
harrisim = harris.compute_harris_response(im)
subplot(133)
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算法在解决特征匹配的问题上被称为是过去的十年来最为成功的图像局部描述子之一。SIFT算法之所以如此成功、流行,其稳定性扮演至关重要的角色。通过上面17张图的对比,SIFT算法比起Harris算法在图像的尺度、旋转、亮度都展现出它自身强大的稳定性。具体体现如下:
Harris算法
1、对尺度很敏感,不具有尺度不变性
2、提取的角点精度是像素级的
3、需要设计角点匹配算法
sift算法
1、sift特征是图像的局部特征,对旋转、尺度缩放、亮度变化保持不变性,对视角变化、噪声也保持一定程度的稳定性
2、信息量丰富,适用于在海量特征数据库中进行匹配
3、多量性,少数物体也可以产生大量SIFT特征
4、由于运算量巨大,导致算法很慢,仅为100多kb的图片也需要花掉十几秒的时间。
5、网上关于sift优化的算法资料少之又少,有网友说研究出这个优化算法的都去发表论文了而不屑于不会在网上讨论。
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 = '../data/sf_view1.jpg'
# im2f = '../data/sf_view2.jpg'
im1f = 'D:\PyCharm Community Edition 2018.2.3\siftpictures\B4.jpg'
im2f = 'D:\PyCharm Community Edition 2018.2.3\siftpictures\B6.jpg'
# im1f = '../data/climbing_1_small.jpg'
# im2f = '../data/climbing_2_small.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()
由上面两组图能发现,算法能找到许多匹配点。这是因为SIFT算法具有尺度和旋转不变性,即使两张图大小不一样、角度不一致也不会影响匹配结果,而从上一个实验可以得出Harris角点对尺度变化非常敏感,当遇到尺度变化较大时,很多正确特征点无法检测出来。
from PIL import Image
from pylab import *
from PCV.localdescriptors import sift
import matplotlib.pyplot as plt
im1f = 'D:\PyCharm Community Edition 2018.2.3\siftpictures\ex1.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')
arr=[]
arrHash = {}
for i in range(1, 10):
im2f = (r'D:/PyCharm Community Edition 2018.2.3/siftpictures/'+'B'+str(i)+'.jpg')
im2 = array(Image.open(im2f))
sift.process_image(im2f, 'out_sift_2.txt')
l2, d2 = sift.read_features_from_file('out_sift_2.txt')
matches = sift.match_twosided(d1, d2)
length=len(matches.nonzero()[0])
length=int(length)
arr.append(length)
arrHash[length]=im2f
arr.sort()
arr=arr[::-1]
arr=arr[:3]
i=0
plt.figure(figsize=(5,12))
for item in arr:
if(arrHash.get(item)!=None):
img=arrHash.get(item)
im1 = array(Image.open(img))
ax=plt.subplot(511 + i)
ax.set_title('{} matches'.format(item))
plt.axis('off')
imshow(im1)
i = i + 1
plt.show()
通过上图可以看到输入一张图片之后,能够找到三张与它匹配度最高的图片,并且能将匹配点数显示出来。
匹配的时候通过把每张图的特征向量表示出来,然后找到特征向量重合度最高的图片。运行速度很慢,一开始用3MB的图片做测试,跑了十几分钟。后来就把图片改为数百kb。
# -*- coding: utf-8 -*-
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:\PyCharm Community Edition 2018.2.3\geopictures" # set this to the path where you downloaded the panoramio images
path = "D:\PyCharm Community Edition 2018.2.3\geopictures" # 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) + '.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')
选择了两个场景进行角度,尺度,物品的变化,实验结果如上图。拿左边场景做说明,图7和图8从不同的角度进行拍摄,主要体现了sift的旋转不变性。图1和图5从不同距离进行拍摄,体现了sift的尺度不变性。图5和图6桌子上摆放的东西有所不同,但是仍然能够分别得出两张图是在一个地方。上面匹配的时候用的是缩略图,因为原图运行太慢了。后来修改了图片尺寸让他运行的速度加快,虽然sift匹配很精确,但是效率却很低。
RANSAC是“RANdom SAmple Consensus(随机抽样一致)”的缩写。它可以从一组包含“局外点”的观测数据集中,通过迭代方式估计数学模型的参数。它是一种不确定的算法——它有一定的概率得出一个合理的结果;为了提高概率必须提高迭代次数。
RANSAC算法的输入是一组观测数据,一个可以解释或者适应于观测数据的参数化模型,一些可信的参数。
RANSAC通过反复选择数据中的一组随机子集来达成目标。被选取的子集被假设为局内点,并用下述方法进行验证:
1.首先我们先随机假设一小组局内点为初始值。然后用此局内点拟合一个模型,此模型适应于假设的局内点,所有的未知参数都能从假设的局内点计算得出。
2.用1中得到的模型去测试所有的其它数据,如果某个点适用于估计的模型,认为它也是局内点,将局内点扩充。
3.如果有足够多的点被归类为假设的局内点,那么估计的模型就足够合理。
4.然后,用所有假设的局内点去重新估计模型,因为此模型仅仅是在初始的假设的局内点估计的,后续有扩充后,需要更新。
5.最后,通过估计局内点与模型的错误率来评估模型。
整个这个过程为迭代一次,此过程被重复执行固定的次数,每次产生的模型有两个结局:
1、要么因为局内点太少,还不如上一次的模型,而被舍弃,
2、要么因为比现有的模型更好而被选用。
OpenCV中滤除误匹配对采用RANSAC算法寻找一个最佳单应性矩阵H,矩阵大小为3×3。RANSAC目的是找到最优的参数矩阵使得满足该矩阵的数据点个数最多,通常 h 33 = 1 h_{33}=1 h33=1来归一化矩阵。由于单应性矩阵有8个未知参数,至少需要8个线性方程求解,对应到点位置信息上,一组点对可以列出两个方程,则至少包含4组匹配点对。
s [ x ′ y ′ 1 ] = [ h 11 h 12 h 13 h 21 h 22 h 23 h 31 h 32 h 33 ] [ x y 1 ] s\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} & h_{33}\end{bmatrix}\begin{bmatrix} x \\ y \\ 1 \end{bmatrix} s⎣⎡x′y′1⎦⎤=⎣⎡h11h21h31h12h22h32h13h23h33⎦⎤⎣⎡xy1⎦⎤
其中(x,y)表示目标图像角点位置,(x’,y’)为场景图像角点位置,s为尺度参数。
RANSAC算法从匹配数据集中随机抽出4个样本并保证这4个样本之间不共线,计算出单应性矩阵,然后利用这个模型测试所有数据,并计算满足这个模型数据点的个数与投影误差(即代价函数),若此模型为最优模型,则对应的代价函数最小。
∑ i = 1 n ( x i ′ − h 11 x i + h 12 y i + h 13 h 31 x i + h 32 y i + h 33 ) 2 + ( y i ′ − h 21 x i + h 22 y i + h 23 h 31 x i + h 32 y i + h 33 ) 2 \sum_{i=1}^{n} {(x_i'-\frac {h_{11}x_i+h_{12}y_i+h_{13}} {h_{31}x_i+h_{32}y_i+h_{33}})}^2+{(y_i'-\frac {h_{21}x_i+h_{22}y_i+h_{23}} {h_{31}x_i+h_{32}y_i+h_{33}})}^2 ∑i=1n(xi′−h31xi+h32yi+h33h11xi+h12yi+h13)2+(yi′−h31xi+h32yi+h33h21xi+h22yi+h23)2
ransacbasesift.py
# -*- coding: utf-8 -*-
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 = 'D:\PyCharm\siftpictures\B3.jpg'
im2 = 'D:\PyCharm\siftpictures\B4.jpg'
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()#清除所有窗口
代码参考:https://blog.csdn.net/qq_38898129/article/details/93982154
场景一:大景深
原图:
SIFT特征匹配:
经过RANSAC算法后的sift特征匹配:
分析: 由上图观察可得,大景深下,sift算法能找到非常多的匹配点,经过ransac后,匹配点明显减少了很多,但是仍然存在错误(在图中黑框处)。除此之外,经过ransac算法后,许多原本正确的点反而被误删了。虽然图片很杂乱,但仔细看还是能看出在:在不用ransac算法的情况下,匹配错误的点是很多的(图中黑框处)。ransac删除了一些正确的点也删除了一些错误的点,好坏参半,对于景深大且角点丰富的图片是有效果的,但效果并不是很好。
场景二:小景深
原图:
SIFT特征匹配:
经过RANSAC算法后的sift特征匹配:
分析: 由上图观察可得,小景深下通过肉眼简单地观察,sift算法仍存在一部分的错误匹配点(黄色方框)。经过ransac后,黄色方框里面的那些错误匹配点都消失了。观察ransac优化的sift算法,会发现几乎找不到半点错误点了,优化效果非常不错,胜于对景深大的图片的优化。
1、一开始使用vlfeat 0.9.21版本,程序一直报错,之后更换了学姐在群里发的0.9.20版本就可以跑了。
2、sift的尺度不变性是这个算法最精髓的地方,比Harris角点检测更稳定。不管原图尺度是多少,在包含了所有尺度的尺度空间下都能找到那些稳定的极值点,这样就做到了尺度不变。
3、有其中一张图片在寻找角点的时候出现如下警告,使得这张图片的Harris角点检测不出来。
我百度了一下,网上说是图片像素太大,但是我每张图片的像素都是一样的,所以应该不是这个问题。但是是为什么,我也还搞不清楚。
4、通过ransac算法剔除错误点,对于景深大和景深小的图片的有不同的效果,景深小的图片明显效果要好于景深大的图片。但不管是哪种图片,经过ransac算法的sift算法都要优于原本的sift算法。