1 1 1. 总的来说Harris检测算法不具有图像的尺度不变性,于是1999年David G.Lowe总结出了基于特征不变技术的检测方法,在图像尺度空间基础上,提出了对图象缩放,旋转不变性的图像局部特征描述算子——SIFT(尺度不变特征变换),并且在2004年加以完善。
2 2 2. 详细来说SIFT特征是基于物体上的一些局部外观的兴趣点而与影像的大小和旋转无关。对于光线、噪声、微视角改变的容忍度也相当高。基于这些特性,它们是高度显著而且相对容易撷取,在母数庞大的特征数据库中,很容易辨识物体而且鲜有误认。使用SIFT特征描述对于部分物体遮蔽的侦测率也相当高,甚至只需要3个以上的SIFT物体特征就足以计算出位置与方位。在现今的电脑硬件速度下和小型的特征数据库条件下,辨识速度可接近即时运算。SIFT特征的信息量大,适合在海量数据库中快速准确匹配。
1 1 1. 在尺度空间中提取关键点
2 2 2. 对关键点附加详细的信息(局部特征)
3 3 3. 通过特征点(附带上特征向量的关键点的两两比较找出相互匹配的若干对特征点,建立景物间的对应关系)
SIFT特征检测的步骤:
1.尺度空间的极值检测:搜索所有尺度空间上的图像,通过高斯微分函数来识别潜在的对尺度和旋转不变的兴趣点。
2.特征点定位:在每个候选的位置上,通过一个拟合精细模型来确定位置尺度,关键点的选取依据他们的稳定程度。
3.特征方向赋值: 基于图像局部的梯度方向,分配给每个关键点位置一个或多个方向,后续的所有操作都是对于关键点的方向、尺度和位置进行变换,从而提供这些特征的不变性。
4.特征点描述: 在每个特征点周围的邻域内,在选定的尺度上测量图像的局部梯度,这些梯度被变换成一种表示,这种表示允许比较大的局部形状的变形和光照变换。
1 1 1 特征点:SIFT的特征点指的是一些十分突出的点不会因光照尺度旋转等因素的改变而丢失,比如角点,边缘点,暗区域的亮点以及亮区域的暗点。
2 2 2 尺度空间:尺度空间的主要思想是通过对原始图像进行尺度变换,获得图像多尺度下的空间表示。从而实现边缘丶角点检测和不同分辨率上的特征提取,从而满足特征点的尺度不变性。
3 3 3 关键点检测:根据文献《Scale-space theory: A basic tool for analysing structures at different scales》可知,高斯核是唯一可以产生多尺度空间的核,一个图像的尺度空间, L ( x , y , σ ) L(x, y, σ) L(x,y,σ)定义为原始图像 I ( x , y ) I(x, y) I(x,y)与一个可变尺度的2维高斯函数 G ( x , y , σ ) G(x, y, σ) G(x,y,σ)卷积运算。
G ( x , y , σ ) = 12 π σ 2 e x p ( − ( x 2 + y 2 ) / 2 σ 2 ) G(x,y,σ)=12πσ2exp(−(x2+y2)/2σ2) G(x,y,σ)=12πσ2exp(−(x2+y2)/2σ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)
4 4 4 高斯模糊:高斯模糊是在Adobe Photoshop等图像处理软件中广泛使用的处理 效果,通常用它来减小图像噪声以及降低细节层次。这种模糊技术生成 的图像的视觉效果是好像经过一个半透明的屏幕观察图像。
5 5 5 高斯金字塔:高斯金子塔的构建过程可分为两步:
(1)对图像做高斯平滑;
(2)对图像做降采样。
为了让尺度体现其连续性,在简单下采样的基础上加上了高斯滤波。一幅图像可以产生几组图像,一组图像包括几层图像。
高斯图像金字塔共有o组和s层,则有:
σ ( s ) = σ o 2 s / S σ(s)=σo2^s/S σ(s)=σo2s/S
σ σ σ——尺度空间坐标; s s s——sub-level层坐标; σ o σo σo——初始尺度; S S S——每组层数(一般为3~5)
6 6 6 DoG(Difference of Gaussian)函数:
由高斯金字塔构造出来,他的第一组第一层是由高斯金字塔的第一组第二层减第一组第一层,他的第一组第二层是由高斯金字塔的第一组第三层减第一组第二层得到,每组都这样就生成了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图像描绘的是目标的轮廓。
7 7 7 DOG局部极值检测:
特征点是由DOG空间的局部极值点组成的。为了寻找DoG函数的极值点, 每一个像素点要和它所有的相邻点比较,看其是否比它的图像域和尺度域 的相邻点大或者小。
中间的检测点和它同尺度的8个相邻点和上下相邻尺度对应的 9 × 2 9×2 9×2个 点共26个点比较,以确保在尺度空间和二维图像空间都检测到极值点。
由于DoG函数在图像边缘有较强的边缘响应,因此需要排除边缘响应。DoG函数的峰值点在边缘方向有较大的主曲率,而在垂直边缘的方向有 较小的主曲率。主曲率可以通过计算在该点位置尺度的 2 × 2 2×2 2×2的Hessian矩 阵得到,导数由采样点相邻差来估计:
上式中, D D D值可以通过求取邻近点像素的差分得到。H的特征值与D的主曲率成正比例。我们可以避免求取具体的特征值,因为我们只关心特征值的比例。令 α = λ m a x α=λmax α=λmax为最大的特征值, β = λ m i n β=λmin β=λmin为最小的特征值,那么,我们通过H矩阵直迹计算它们的和,通过H矩阵的行列式计算它们的乘积:
如果 γ γ γ为最大特征值与最小特征值之间的比例,那么 α = γ β α=γβ α=γβ,这样便有
上式的结果只与两个特征值的比例有关,而与具体特征值无关。当两个特征值相等时, ( γ + 1 ) / 2 γ (γ+1)/2γ (γ+1)/2γ的值最小,随着γ的增加, ( γ + 1 ) / 2 γ (γ+1)/2γ (γ+1)/2γ的值也增加。所以要想检查主曲率的比例小于某一阈值γ,只要检查下式是否成立:
L o w e Lowe Lowe在论文中给出的 γ γ γ=10。也就是说对于主曲率比值大于10的特征点将被删除。
8 8 8 关键点方向分配:
通过尺度不变性求极值点,可以使其具有缩放不变的性质。而利用关键点邻域像素的梯度方向分布特性,可以为每个关键点指定方向参数 方向,从而使描述子对图像旋转具有不变性
确定关键点的方向采用梯度直方图统计法,统计以关键点为原点,一定区域内的图像像素点对关键点方向生成所作的贡献。
关键点主方向:极值点周围区域梯度直方图的主峰值也是特征点方向
关键点辅方向:在梯度方向直方图中,当存在另一个相当于主峰值80%能量的峰值时,则将这个方向认为是该关键点的辅方向。这可以增强匹配的鲁棒性,Lowe的论文指出大概有15%关键点具有 多方向,但这些点对匹配的稳定性至为关键。
9 9 9 关键点描述:
下图是一个SIFT描述子事例。其中描述子由2×2×8维向量表征,也即是 2×2个8方向的方向直方图组成。左图的种子点由8×8单元组成。每一个小格 都代表了特征点邻域所在的尺度空间的一个像素,箭头方向代表了像素梯度方 向,箭头长度代表该像素的幅值。然后在4×4的窗口内计算8个方向的梯度方向直方图。绘制每个梯度方向的累加可形成一个种子点,如右图所示:一个特征点由4个种子点的信息所组成。
Lowe实验结果表明:描述子采用 4 × 4 × 8 = 128 4×4×8=128 4×4×8=128 维向量表征,综合效果最优 (不变性与独特性)。
10 10 10 关键点匹配:
分别对模板图(参考图,reference image)和实时图(观测图,observation image)建立关键点描述子集合。目标的识别是通过两点集内关键点描述子的比对来完成。具有128维的关键点描述子的相似性度量采用欧式距离。
使用开源工具包 VLFeat 提供的二进制文件来计算图像的 SIFT特征 。这里附上VLFeat 工具包链接http://www.vlfeat.org/
配置过程:
1.把vlfeat文件夹下win64中的sift.exe和vl.dll这两个文件复制到项目的文件夹中(我的保存后的路径为 E:\PCV-master\PCV\localdescriptors)
2.修改PCV文件夹里面的localdescriptors文件夹中的sift.py文件(我的文件路径为E:\PCV-master\PCV\localdescriptors),修改其中的cmmd内的路径cmmd=str(r"E:\win64vlfeat\sift.exe“+imagename+” --output="+resultname+" "+params) (路径是你项目文件夹中的sift.exe的路径)
至此之后便配置完成!
代码:
# -*- 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 = 'Harris_Data/2.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(131)
sift.plot_features(im, l1, circle=False)
title(u'(a)SIFT特征', fontproperties=font)
subplot(132)
sift.plot_features(im, l1, circle=True)
title(u'(b)用圆圈表示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'(c)Harris角点', fontproperties=font)
show()
结果显示:
分析:为了将sift和Harris角点进行比较,将Harris角点检测的显示在了图像的最后侧。正如你所看到的,这两种算法选择了不同的坐标。SIFT对象会使用DoG检测关键点,并且对每个关键点周围的区域计算特征向量,由方法名称我们就可以知道,它主要包括两个操作:检测和计算,操作的返回值是关键点信息和描述符,最后在图像上绘制关键点,并用imshow函数显示这幅图像。
对于将一幅图像中的特征匹配到另一幅图像的特征,一种稳健的准则(同样是由 Lowe 提出的)是使用这两个特征距离和两个最匹配特征距离的比率。相比于图像中的其他特征,该准则保证能够找到足够相似的唯一特征。使用该方法可以使错误的匹配数降低。
代码:
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/crans_1_small.jpg'
im2f = '../data/crans_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算法的实质可以归为在不同尺度空间上查找特征点的问题,并计算出关键点的方向。每个特征点周围的纹理都是不一样的,也就是特征点周围的每个点的梯度方向都是独特的。于是我们借用梯度直方图来描述每个特征点周围领域的梯度值。Lowe用的是128维向量进行描述,根据尺度实际情况选用正方形的长度(一般情况选用长16*16)。如果要检测的物体发生了旋转,那么他周围的纹理也会发生旋转,并且是同步的,所以我们要找到这个旋转的角度,让统计128维直方图时候也根据这个角度做旋转,这样不管怎么旋转,特征点周围的纹理描述出来都是一致的。这就是旋转不变性!
# -*- coding: utf-8 -*-
from pylab import *
from PIL import Image
from PCV.localdescriptors import sift
from PCV.tools import imtools
import pydot
download_path ="G:\\壁纸\\lufugong" # set this to the path where you downloaded the panoramio images
path = "G:\\壁纸\\lufugong" # 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])
print(featlist)
imname = 'G:\\壁纸\\lufugong\\13.jpg'
im = array(Image.open(imname).convert('L'))
sift.process_image(imname, 'empire.sift')
l1, d1 = sift.read_features_from_file('empire.sift')
matchscores = zeros((nbr_images, nbr_images))
arr=[]
for i in range(nbr_images):
print('comparing 12', imlist[i])
# l1, d1 = sift.read_features_from_file(featlist[i])
l2, d2 = sift.read_features_from_file(featlist[i])
matches = sift.match_twosided(d1, d2)
nbr_matches = sum(matches > 0)
arr.append(nbr_matches)
# arr2[i]=dict{nbr_matches:imlist[i]}
print('number of matches = ', nbr_matches)
# matchscores[i, j] = nbr_matches
arr2=arr[:] #切片浅拷贝 arr2=arr深拷贝
arr2.sort(reverse = True)
print(arr)
print(arr2)
i=1
while (i<4):
for j in range(nbr_images):
if(arr[j]==arr2[i]):
print(imlist[j])
figure()
axis('off')
img=array(Image.open(imlist[j]))
imshow(img)
break
i=i+1
分析: 我们这里输入一张图片在数据集中检索然后输出最高匹配度的三张图片。基本原理是将输入的图片与数据集一一比对,比对结果用数组进行保存,然后再比对各个结果然后输出结果最大的三组照片。并且可以得出SIFT在大量数据中运行比较快。其次这组测试数据分别用了不同的角度尺度进行拍摄,并且结果也很精确由此可见SIFT算法的尺度旋转不变性。
代码:
from pylab import *
from PIL import Image
from PCV.localdescriptors import sift
from PCV.tools import imtools
import pydot
download_path = "G:\\maplestory\\lucid"
path = "G:\\maplestory\\lucid"
imlist = imtools.get_imlist(download_path)
nbr_images = len(imlist)
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
# 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]
结果显示:
我们将每对图像间的匹配特征数保存在 matchscores 数组中使用该 matchscores 矩阵作为图像间简单的距离度量方式(具有相似内容的图像间拥有更多的匹配特征数),下面我们可以使用相似的视觉内容来将这些图像连接起来。
首先通过图像间是否具有匹配的局部描述子来定义图像间的连接,然后可视化这些连接情况。为了完成可视化,我们可以在图中显示这些图像,图的边代表连接。 可以使用pydot http://code.google.com/p/pydot/,该工具包是功能强大的GraphViz 图形库的Python 接口。
为了创建显示可能图像组的图,如果匹配的数目高于一个阈值,我们使用边来连接相应的图像节点。为了得到图中的图像,需要使用图像的全路径(在下面例子中,使用 path 变量表示0)。为了使图像看起来漂亮,我们需要将每幅图像尺度化为缩略图形式,缩略图的最大边为 100 像素。
代码:
# -*- coding: utf-8 -*-
from pylab import *
from PIL import Image
from PCV.localdescriptors import sift
from PCV.tools import imtools
import pydot
# download_path = "G:\\maplestory\\lucid\\"
# path = "G:\\maplestory\\lucid\\"
download_path = "G:\\壁纸\\lufugong"
path = "G:\\壁纸\\lufugong"
imlist = imtools.get_imlist(download_path)
nbr_images = len(imlist)
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
# 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_jpg('G:\\壁纸\\lufugong\\lufugong.jpg')
# g.write_jpg('G:\\maplestory\\lucid.jpg')
结果:
在本次测试中,我使用的是sift特征匹配,在特征匹配之后,我们使用RANSAC算法求解得到单应性矩阵。其基本思想是,数据中包含正确的点和噪声点,合理的模型能够在描述正确数据点的同时摒弃噪声点,在这里的用途就是排除掉不符合大部分几何变换的匹配,而后使正确的点进行匹配,通过单应矩阵来对齐两张图片的内容。
本实验分为1景深单一2景深丰富相继进行对算法的测试:
图片中红框标注的是SIFT特征匹配错误的点,但是RANSAC算法通过迭代的方式求解单应性矩阵并找到正确的直线来拟合带有噪声的数据。也就是说,给定一组数据点集中包含正确的点和噪声点,RANSAC能找到一条直线来挑选出正确的点,同时摒弃噪声点。
拼接后结果:
可以观察出拼接后存在的问题,矩形框标注的地方有明显的褶皱这是因为连接图像出现了边缘效应况。可以看出RANSAC算法只是将图像进行简单的扭曲,导致图像拼接得非常生硬。圆形框标注存在鬼影现象,可能是因为图片存在歪斜或两张图片的平面与平面之间景深不同,直接将两张图片进行映射变换会导致图片中部分物体有重影现象即鬼影现象,
2.景深丰富
数据集:
接下来用SIFT特征与经过RANSAC算法筛选后的SIFT进行对比
测试结果:
SIFT
RANSAC
在景深丰富的情况下,SIFT特征匹配在近景产生的匹配点较多,且有一些错误匹配点,远景产生的匹配点较少。进行RANSAC算法剔除之后,留下了的匹配点比较精准,但是仍然存在错误匹配点,存在正确的匹配被剔除,如图亭子的的尖端部分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 = 'E:/test2/test2/house1.jpg'
im2 = 'E:/test2/test2/house2.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() # 清除所有窗口
SIFT算法优点:
SIFT 特征包括兴趣点检测器和描述子。SIFT描述子具有非常强的稳健性,这在很大程度上也是SIFT特征能够成功和 流行的主要原因。自从SIFT特征的出现,许多其他本质上使用相同描述子的方法也相继出现。现在,SIFT描述符经常和许多不同的兴趣点检测器相结合使用(有些情况下是区域检测器),有时甚至在整幅图像上密集地使用。SIFT特征对于尺度、 旋转和亮度都具有不变性,因此,它可以用于三维视角和噪声的可靠匹配。