python计算机视觉编程——sift特征提取和ransac减少错配

特征提取

  • 一、什么是sift算法
  • 二、sift算法运用演示
    • 1.1sift算法提取特征点
    • 1.2sift进行两张图像匹配
    • 1.3sift进行图片检索
    • 1.4 检索相似图片
    • 1.5地理信息检索
    • 1.6实验分析总结
      • 1.6.1关于graphviz安装失败
  • 三、图像映射与全景拼接
    • 3.1关于全景拼接技术
    • 3.2图像拼接的原理
    • 3.3 2D图像的变换原理
    • 3.4 如何进行图像变换
    • 3.6 处理错误匹配★
    • 3.7ransac方法★
    • 3.8实验测试
      • 3.8.1实验代码
      • 3.8.2实验结果分析
      • 3.8.3实验小结
      • 3.8.4实验问题

一、什么是sift算法

SIFT,即尺度不变特征变换(Scale-invariant feature transform,SIFT),是用于图像处理领域的一种描述。这种描述具有尺度不变性,可在图像中检测出关键点,是一种局部特征描述子。它是由David Lowe发明的。SIFT在2004年由Lowe完善并经受住了时间的考验。

SIFT特征是基于物体上的一些局部外观的兴趣点而与影像的大小和旋转无关。对于光线、噪声、微视角改变的容忍度也相当高。基于这些特性,它们是高度显著而且相对容易撷取,在母数庞大的特征数据库中,很容易辨识物体而且鲜有误认。使用SIFT特征描述对于部分物体遮蔽的侦测率也相当高,甚至只需要3个以上的SIFT物体特征就足以计算出位置与方位。在现今的电脑硬件速度下和小型的特征数据库条件下,辨识速度可接近即时运算。SIFT特征的信息量大,适合在海量数据库中快速准确匹配。
SIFT算法的特点有:

  1. SIFT特征是图像的局部特征,其对旋转、尺度缩放、亮度变化保持不变性,对视角变化、仿射变换、噪声也保持一定程度的稳定性;

  2. 独特性(Distinctiveness)好,信息量丰富,适用于在海量特征数据库中进行快速、准确的匹配;

  3. 多量性,即使少数的几个物体也可以产生大量的SIFT特征向量;

  4. 高速性,经优化的SIFT匹配算法甚至可以达到实时的要求;

  5. 可扩展性,可以很方便的与其他形式的特征向量进行联合。

SIFT算法可以解决的问题:

目标的自身状态、场景所处的环境和成像器材的成像特性等因素影响图像配准/目标识别跟踪的性能。而SIFT算法在一定程度上可解决:

  1. 目标的旋转、缩放、平移(RST)

  2. 图像仿射/投影变换(视点viewpoint)

  3. 光照影响(illumination)

  4. 目标遮挡(occlusion)

  5. 杂物场景(clutter)

  6. 噪声
    SIFT算法的实质是在不同的尺度空间上查找关键点(特征点),并计算出关键点的方向。SIFT所查找到的关键点是一些十分突出,不会因光照,仿射变换和噪音等因素而变化的点,如角点、边缘点、暗区的亮点及亮区的暗点等。

构建DOG尺度空间

模拟图像数据的多尺度特征,大尺度轮廓特征,小尺度细节特征。通过构建高斯金字塔(每一层用不同的参数σ做高斯模糊(加权)),保证图像在任何尺度都能有对应的特征点,即保证尺度不变性。

什么是尺度空间(scale space )?

尺度空间理论最早于1962年提出,其主要思想是通过 对原始图像进行尺度变换,获得图像多尺度下的空间表示。 从而实现边缘、角点检测和不同分辨率上的特征提取,以 满足特征点的尺度不变性。 关键点检测的相关概念 尺度空间中各尺度图像的模糊程度逐渐变大,能够模拟人在距离目标由近到远时目标 在视网膜上的形成过程。尺度越大图像越模糊。
python计算机视觉编程——sift特征提取和ransac减少错配_第1张图片
…………
关于sift算法使用时所会碰到的相关问题和相关概念为:

  • 特征点
  • 尺度空间
  • 高斯模糊
  • 高斯金字塔
  • DoG函数
  • DoG高斯差分金字塔
  • DOG局部极值检测
  • 关键点检测和匹配

二、sift算法运用演示

1.1sift算法提取特征点

遇到错误1:
在这里插入图片描述
版本python2X和python3X的版本不兼容,所以修改格式即可
遇到错误2:vlfeat的配置sift.exe
要注意cmmd必须一字不错。
下面为使用sift算法和harris角点检测算法对比场景的结果:

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 = 'E:\\thirddown\computervision\\untitled1\data\\test\\1.jpg'
im = array(Image.open(imname).convert('L'))
sift.process_image(imname, '1.sift')
l1, d1 = sift.read_features_from_file('1.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和Harris角点进行比较,将Harris角点检测的显示在了图像的最后侧。正如你所看到的,这两种算法选择了不同的坐标。

这次实验我收集了20张图片作为数据集:
python计算机视觉编程——sift特征提取和ransac减少错配_第2张图片

python计算机视觉编程——sift特征提取和ransac减少错配_第3张图片
角点检测算法无法显示是因为图片大小过大导致invaid不可读。
第一组数据:
python计算机视觉编程——sift特征提取和ransac减少错配_第4张图片
python计算机视觉编程——sift特征提取和ransac减少错配_第5张图片
python计算机视觉编程——sift特征提取和ransac减少错配_第6张图片
第二组数据:
python计算机视觉编程——sift特征提取和ransac减少错配_第7张图片
python计算机视觉编程——sift特征提取和ransac减少错配_第8张图片
python计算机视觉编程——sift特征提取和ransac减少错配_第9张图片
第三组数据:
python计算机视觉编程——sift特征提取和ransac减少错配_第10张图片
python计算机视觉编程——sift特征提取和ransac减少错配_第11张图片
python计算机视觉编程——sift特征提取和ransac减少错配_第12张图片
从多个样本数据的测试中我们可以发现,sift算法和harris角点检测算法所检测出来的特征点有所不同。我从多个角度拍摄同一场景的数据样本,可以从实验结果中看出,虽然在不同尺度,但是标记的sift很类似,体现其局部特征十分稳定,从结果上来看,sift算法比harris的特征点角点保留更多,且harris角点容易尝试数据丢失,这点sift不会,并且sift的效率很高,程序运行速度快。

第一张图和第二张图片是同一个建筑物的不同尺度,但标记的SIFT点在很大程度上相同,比如门和窗这样细小的部分中标记的位置都相似,可以看出SIFT算法的局部特征非常的稳定。
SIFT和Harris 相比较:从结果来直接看,发现SIFT算法的特征点比Harris角点的特征点保留的更多,它需要建立高斯图像金字塔和高斯差分金字塔之后再检测极值,而Harris角点只是对原图进行角点检测和变化,Harris特征点检测存在角点信息丢失与偏移的现象。在运行程序时,与Harris的效率相比,SIFT的效率有大的提升,程序的运行很快。

1.2sift进行两张图像匹配

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 = 'E:\\thirddown\computervision\data\\test\\7.jpg'

  im2f = 'E:\\thirddown\computervision\data\\test\\8.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()

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()

sift.plot_matches(im1, im2, l1, l2, matches, show_below=True)
show()

第一组:在不同尺度和方位上的匹配
python计算机视觉编程——sift特征提取和ransac减少错配_第13张图片
第二组:同样不同尺度,位置左右平移后:
python计算机视觉编程——sift特征提取和ransac减少错配_第14张图片
python计算机视觉编程——sift特征提取和ransac减少错配_第15张图片
第三组:在不同尺度正侧方位上的另一组数据的测试:
python计算机视觉编程——sift特征提取和ransac减少错配_第16张图片
python计算机视觉编程——sift特征提取和ransac减少错配_第17张图片
从上面个三组不同的测试展示中我们可以看出在尺度不变性方面上sift算法非常具有优越性,远优于harris角点算法,sift的对应非常准确,在拍摄时对同一物体进行缩放,移动多角度拍摄都可以很好找到对应特征点。

接下来我们对不同的场景进行匹配测试:

python计算机视觉编程——sift特征提取和ransac减少错配_第18张图片
python计算机视觉编程——sift特征提取和ransac减少错配_第19张图片
没有可以匹配到的特征值,虽然有色彩类似的楼房但是确实不属于同一楼,所以sift算法效率从实验结果上看很高。

1.3sift进行图片检索

# -*- 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 ="E:/thirddown/computervision/data/test/"  # set this to the path where you downloaded the panoramio images
path = "E:/thirddown/computervision/data/test/"  # 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_jpg('xx.jpg')

1.4 检索相似图片

# -*- coding: utf-8 -*-
from PIL import Image
from pylab import *
from numpy import *
import os
from PCV.localdescriptors import sift
from PCV.tools.imtools import get_imlist # 导入原书的PCV模块
import matplotlib.pyplot as plt # plt 用于显示图片
import matplotlib.image as mpimg # mpimg 用于读取图片
# 匹配最多的三张照片
# 获取project2_data文件夹下的图片文件名(包括后缀名)
filelist = get_imlist(r'E:\\thirddown\computervision\data\\test\\')
# 输入的图片
im1f = r'E:\\thirddown\computervision\data\\test\\test.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')
i=0
num = [0]*30 # 存放匹配值
for infile in filelist: # 对文件夹下的每张图片进行如下操作
    im2 = array(Image.open(infile))
    sift.process_image(infile, 'out_sift_2.txt')
    l2, d2 = sift.read_features_from_file('out_sift_2.txt')
    matches = sift.match_twosided(d1, d2)
    num[i] = len(matches.nonzero()[0])
    i=i+1
    print ('{} matches'.format(num[i-1])) # 输出匹配值
i=1
figure()
gray()
while i<4: # 循环三次,输出匹配最多的三张图片
    index=num.index(max(num))
    print (index, filelist[index])
    lena = mpimg.imread(filelist[index])  # 读取当前匹配最大值的图片
    # 此时 lena 就已经是一个 np.array 了,可以对它进行任意处理
    subplot(1,3,i)
    plt.imshow(lena)  # 显示图片
    plt.axis('off')  # 不显示坐标轴
    num[index] = 0  #将当前最大值清零
    i=i+1
show()

实验结果如下:
根据代码依次将一张未在数据集中的数据与数据集中的所有数据进行匹配,将所有数据转化为sift文件,最后找出匹配度最高的三组数据。
python计算机视觉编程——sift特征提取和ransac减少错配_第20张图片
python计算机视觉编程——sift特征提取和ransac减少错配_第21张图片
python计算机视觉编程——sift特征提取和ransac减少错配_第22张图片
python计算机视觉编程——sift特征提取和ransac减少错配_第23张图片
分析:从实验结果和我收集的数据中,所匹配的三张图片分别在光线,位移,尺度上和测试图片有所不同,的确相较于其他相似数据,是最接近测试图像的,从实验结果可以看出sift检测在检索相似图片上效果很好,根据匹配的matchs值的大小排序,最总找出最为匹配的数据。

1.5地理信息检索

通过使用pydot和graphviz工具包实现图像地理信息检索的可视化操作,使数据分析变得简单明了。

# -*- 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 = "E:\\thirddown\computervision\data\\test\\"  # set this to the path where you downloaded the panoramio images
path = "E:\\thirddown\computervision\data\\test\\"  # 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 = 10  # 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('whitehouse1.png')

python计算机视觉编程——sift特征提取和ransac减少错配_第24张图片
代码将收集到的图像数据根据匹配的sift特征点进行检索分类,可以将相同场景的图像进行分类,但是因为根据数据量的大小,代码的的运行速度随着数据量越大越慢(我死机了四次…),这边我修改了阈值为5,因为源代码过小的阈值导致所有图片的数据集的无关链接,我从2将其修改为5以后,地理信息的匹配就更准确明了了。

1.6实验分析总结

SIFT特征检测法对旋转、尺度缩放、亮度变化等保持不变性,是非常稳定的局部特征检测法。且优于harris角点。可用于检索相似图片,并且可以将其运用到地理信息的检索当中,通过一张图片找到其标志性的地理信息位置。才这次实验中我遇到了诸多问题,从最开始的vlfeat包的安装,除了网上可查到的,最重要的是cmmd修改目录要在sift.exe后面加上一个空格才能结束分号,这导致我浪费了许多的时间。还有就是关于可视化处理。

1.6.1关于graphviz安装失败

除了现有我可以很容易找到的解决方法都不能解决我的问题,到后来找到一个访问量很低的帖子,除了最开始的pip install graphviz (要在安装的地方设置系统环境变量)和pydot的顺序不能变,还有额外安装pydot-ng都不能解决我的问题。因为最新的pydot1.4.1 没有find_graphviz()这个函数,所以你无论怎么样重新安装都没有用,我的解决方法是去修改pydot.py文件里的大概在1700+行有一条

    def __init__(self, *argsl, **argsd):
        Graph.__init__(self, *argsl, **argsd)

        self.shape_files = list()
        self.formats = [
            'canon', 'cmap', 'cmapx',
            'cmapx_np', 'dia', 'dot',
            'fig', 'gd', 'gd2', 'gif',
            'hpgl', 'imap', 'imap_np', 'ismap',
            'jpe', 'jpeg', 'jpg', 'mif',
            'mp', 'pcl', 'pdf', 'pic', 'plain',
            'plain-ext', 'png', 'ps', 'ps2',
            'svg', 'svgz', 'vml', 'vmlz',
            'vrml', 'vtx', 'wbmp', 'xdot', 'xlib']

        #self.prog = 'dot' 修改这一行,将dot改为绝对地址的dot.exe
        self.prog='dot'
         #self.prog='D:/graphviz/graphviz-2.38/release/bin/dot.exe'

将上面代码的最后一行修改为dot.exe的绝对地址,不可以直接将dot改为dot.exe这样。最后运行代码我没有提示错误。
总之这次实验经历很有趣,学到了很多关于python的冷门知识。

三、图像映射与全景拼接

顾名思义,图像映射是全景拼接技术的基础,我们有时为了更好的艺术呈现或者信息采集需要,要将环绕长条状的广角镜头放在一张图片里,这里为了弥补一些基础设备的不足,全景拼接技术孕育而生,它可以将两幅或者多幅具有重叠区域的图像合并成一张大图。

3.1关于全景拼接技术

全景拼接技术的基础流程:

1. 针对某个场景拍摄多张/序列图像
2. 计算第二张图像与第一张图像之间的变换关系
3. 将第二张图像叠加到第一张图像的坐标系中
4. 变换后的融合/合成
5. 在多图场景中,重复上述过程

根据所学的知识在分解步骤2,3是主要步骤,在这之中我们需要将采集到的多张图片中的特征点提取出来,生成描述子,再将多张图片作特征匹配处理。如何处理多张图片拍摄后需要进行映射变换是我们主要解决的技术壁垒。

3.2图像拼接的原理

关于其几何解释:全景融合将图像被投影到共同的拼接平面上,在拼接平面上实现全景融合。在拼接应用中,可以简化理解为2D图像通过平移伸缩等变换后叠加的过程。
python计算机视觉编程——sift特征提取和ransac减少错配_第25张图片

3.3 2D图像的变换原理

那么在拼接之前我们实现图像的映射变换主要有如下几种模型:

  • translation(位移)
  • rotation(旋转)
  • scale(尺度/大小)
  • affine(仿射)
  • perspective(透视)

python计算机视觉编程——sift特征提取和ransac减少错配_第26张图片

通过数学模型来找到实现这些模型所需的具体参数。我们主要通过图像滤波图像变换两种方法来较好的实现多张图片的拼接。
图像滤波:即改变图像的像素点取值范围只改变灰度相关数据关联。
在这里插入图片描述
图像变换:改变图像的坐标取值范围,即在后期处理图像时对图像作平移,旋转,映射等处理。
在这里插入图片描述
根据其数学原理如下:
python计算机视觉编程——sift特征提取和ransac减少错配_第27张图片
表格中圈出的即为我们为了将得到的图像进行对应变换所需要用到的未知量。在求解位置量的过程中,我们主要采用的方法为最小二乘法。通过数学原理可得到,如单应性变化需要至少4对匹配特征才能求解出所有未知量。

最小二乘法:最小二乘法(又称最小平方法)是一种数学优化技术。它通过最小化误差的平方和寻找数据的最佳函数匹配。利用最小二乘法可以简便地求得未知的数据,并使得这些求得的数据与实际数据之间误差的平方和为最小。最小二乘法还可用于曲线拟合。其他一些优化问题也可通过最小化能量或最大化熵用最小二乘法来表达。

3.4 如何进行图像变换

给定变换模型 x’= h(x) ,以及输入图像f(x), 我们要如何根据f(x)计算变换后的图像 g(x’) = f(h(x))?
python计算机视觉编程——sift特征提取和ransac减少错配_第28张图片

  1. 前向映射
  2. 逆向映射

前向映射:对于 f(x) 中的每个像素 x,根据变换模型计算相应的映射坐标x’ = h(x),并将x的像素值赋给 g(x’)。当像素在两个像素点之间时采用近邻插值。
python计算机视觉编程——sift特征提取和ransac减少错配_第29张图片
逆向映射:对于 g(x’)中的每个像素 x’,根据变换模型计算相应的映射坐标 x = h-1(x’),并将x的像素值赋给g(x’),当特征点来自两个像素之间时采用线性插值或者双线性插值。
python计算机视觉编程——sift特征提取和ransac减少错配_第30张图片
我们主要的像素插值方法有:
3. 最近邻插值
4. 线性插值
5. 双线性插值
6. 三线性插值
7. ……
图像映射的流程:

① 针对两张/多张图像提取特征
② 特征匹配
③ 根据图像变换特点,选取合适的变换结构
④ 根据DLT等方法计算变换结构
⑤ 采用正向/逆向映射,利用插值方式实现图像映射变换

3.6 处理错误匹配★

在提取特征值的过程中我们可以知道无论使用什么样的方法都会找到一些错误的干扰匹配项。特别在拼接技术上,错误的匹配会对拼接造成很大的干扰。这里通过直线拟合技术来减小误差。
直线拟合技术的步骤:

  • 1.给定若干二维空间中的点,求直线 y=ax+b ,使得该直线对空间点的拟合误差最小。
  • 2.随机选择两个点,根据该点构造直线,给定阈值,计算inliers数量

python计算机视觉编程——sift特征提取和ransac减少错配_第31张图片
python计算机视觉编程——sift特征提取和ransac减少错配_第32张图片
不断迭代循环以后循环迭代,找到其中inliers最大的点集再利用最小二乘法来拟合直线。

3.7ransac方法★

RANSAC是“RANdom SAmple Consensus(随机抽样一致)”的缩写。它可以从一组包含“局外点”的观测数据集中,通过迭代方式估计数学模型的参数。它是一种不确定的算法——它有一定的概率得出一个合理的结果;为了提高概率必须提高迭代次数。该算法最早由Fischler和Bolles于1981年提出。
RANSAC的基本假设是:
(1)数据由“局内点”组成,例如:数据的分布可以用一些模型参数来解释;
(2)“局外点”是不能适应该模型的数据;
(3)除此之外的数据属于噪声。
局外点产生的原因有:噪声的极值;错误的测量方法;对数据的错误假设。
RANSAC也做了以下假设:给定一组(通常很小的)局内点,存在一个可以估计模型参数的过程;而该模型能够解释或者适用于局内点。

我们这里采用的为RANSAC求解单应性矩阵

  1. 随机选择四对匹配特征
  2. 根据DLT计算单应矩阵 H (唯一解)
  3. 对所有匹配点,计算映射误差在这里插入图片描述
  4. 根据误差阈值,确定inliers(例如3-5像素)计算完毕后返回第一步重新计算直到取到最合适的inliers集合
  5. 针对最大inliers集合,重新计算单应矩阵 H

接下来是例子展示。

3.8实验测试

3.8.1实验代码

# -*- 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 = 'E:\\thirddown\computervision\data\\ransac\IMG_9379.jpg'
    im2 = 'E:\\thirddown\computervision\data\\ransac\IMG_9380.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()  # 清除所有窗口

3.8.2实验结果分析

分别从采集到的图像分为景深丰富和景深单一两个方面进行测试。
①景深丰富


从运行结果来分析,我们选取的景深丰富的图像找到的特征匹配点
较为复杂,通过sift特征匹配而产生的一些错配情况,通过ransac算法进行了剔除,从两张实验结果的对比来看删除了许多错误匹配,使得整体匹配点较为整齐,删除的匹配点多,使得匹配不杂乱,但是也仍然有些错配现象。
②景深单一


从上面的实验结果来看,删除错配后的特征匹配的整体线条更为整齐,且删除的错配点正确率较高,算法速度较快,但是错配也仍然存在,因为算法局限性导致不能最优解,同时也应为选取建筑物的原因使得整体排线更加整齐,匹配不会过度冗余。

3.8.3实验小结

通过实验测试我们可以看出即使是使用了RANSAC算法消除错配,仍然会有一些错配情况,但是其的确起到了消除鲁棒性的作用,很好的优化了算法。RANSAC的优点是它能鲁棒的估计模型参数。例如,它能从包含大量局外点的数据集中估计出高精度的参数。但是从算法本身来看,ransac算法计算式它的参数迭代次数是没有上限的,即如果我们通过设置阈值来确定其迭代次数的上限值,我们得到的结果可能并不能达到最优,甚至产生错误,该算法得到的模型的正确率与迭代次数呈正相关。同时对于一个图像匹配中的多模型重叠,ransac的局限性就体现出来了,因为它只能估计出一个模型来对错配进行处理。

3.8.4实验问题

运行openvc时出现:module ‘cv2.cv2’ has no attribute 'xfeatures2d
解决方法:这个问题是由于这个算法已经申请了专利,而OpenCV4.x版本下没有使用该算法的权限。所以需要降低OpenCV的版本。
在cmd进行操作,先卸载原有版本:
pip uninstall opencv-python
pip uninstall opencv-contrib-python
然后安装新版本:

 pip install opencv_python==3.4.2.16 
 pip install opencv-contrib-python==3.4.2.16

安装时加镜像会快很多
pip install opencv-python -i https://pypi.tuna.tsinghua.edu.cn/simple

在这里插入代码片

这样会减少超时可能即read timed out

当opencv成功安装后再pycharm中无法找到对应的包。
具体操作参考一下blog
https://blog.csdn.net/qq_36735489/article/details/82110972

你可能感兴趣的:(python计算机视觉编程——sift特征提取和ransac减少错配)