SIFT特征提取与检测

文章目录

          • 一、SIFT算子介绍
          • 二、SIFT算子特点
          • 三、SIFT算子应用
          • 四、SIFT特征点提取算法
          • 五、SIFT算法特征匹配实验
          • 六、RANSAC算法
            • 1、算法描述
            • 2、RANSAC算法在SIFT特征匹配中的应用
            • 3、算法实现步骤
            • 4、对比实验
            • 5、代码实现
            • 6、实验总结
          • 七、总结

一、SIFT算子介绍

在之前的学习中,我们学习了角点的基本知识,并对Harris角点检测算法应用到了图像的角点检测。
当我们在观察图像中的物体时,物体所在的图像不论背景或者角度如何变换,我们依然能很快的识别出图像中的目标物体。对机器来说,则需要定义图像上的关键特征并且能够根据这些特征搜索到一张新的图片。
SIFT,即尺度不变特征变换(Scale-invariant feature transform,SIFT)。是在计算机视觉任务中特征提取算法。

二、SIFT算子特点

这种描述具有尺度不变性,可在图像中检测出关键点,是一种局部特征描述子。SIFT特征是基于物体上的一些局部外观的兴趣点而与影像的大小和旋转无关。对于光线、噪声、微视角改变的容忍度也相当高。基于这些特性,在母数庞大的特征数据库中,很容易辨识物体而且鲜有误认。
SIFT算法可以解决的问题:
• 目标的旋转、缩放、平移(RST)
• 图像仿射/投影变换(视点viewpoint)
• 弱光照影响(illumination)
• 部分目标遮挡(occlusion)
• 杂物场景(clutter)
• 噪声

三、SIFT算子应用

SIFT算子可以帮助定位图像中的局部特征,应用于计算机视觉应用,例如图像匹配,物体检测,场景检测等。
还可以将通过SIFT生成的关键点用作模型训练期间的图像特征。与边缘特征或单一特征相比,SIFT特征的主要优势在于它们不受图像大小或方向的影响。

四、SIFT特征点提取算法
  1. 构建尺度空间,检测极值点,获得尺度不变性
  • 构建尺度空间——特征点的性质之一就是对尺度的变化保持不变性。因此寻找的特征点要在不同尺度下都能被检测出来.根据文献可知,变换到尺度空间唯一的核函数是高斯函数。因此一个图像的尺度空间定义为: L ( x , y , σ ) L(x,y,σ) Lx,y,σ)是由可变尺度的高斯函数 G ( x , y , σ ) G(x,y,σ) G(x,y,σ)与输入图像 I ( x , y ) I(x,y) Ix,y卷积得到
    即:  L ( x , y , σ ) = G ( x , y , σ ) ∗ I ( x , y ) L(x,y,σ)=G(x,y,σ)*I(x,y) Lx,y,σ)=G(x,y,σ)Ix,y
    其中: G ( x , y , σ ) G(x,y,σ) G(x,y,σ)= 1 2 π σ 2 e − ( x + 2 y 2 ) 2 σ 2 {\frac{1}{2πσ^2}} e^{\frac{-(x_+^2 y^2 )}{2σ^2}} 2πσ21e2σ2(x+2y2)
    ( x , y ) (x,y) (x,y)是尺度坐标,σ大小决定图像的平滑程度,大尺度对应图像的概括特征,小尺度对应图像的细节特征,大的σ对应粗糙尺度(低分辨率),小σ对应高分辨率。为使计算相对高效,真正使用的是差分高斯尺度空间
    D ( x , y , σ ) D(x,y,σ) D(x,y,σ):
    SIFT特征提取与检测_第1张图片

          D是两个相邻的尺度在尺度上相差一个相乘系数k。
    
  • 高斯模糊——降低图像中的噪点,强调了图像的重要特征。进行高斯模糊之和,纹理和次要细节将从图像中删除,并且保留形状和边缘之类的相关信息。

  • 实现DOG——初始图像与不同σ值的高斯函数卷积,得到一垛模糊后图像,然后将这一垛图像临近两两相减得到对应的DOG。一副图像可以产生几组图像,一组图像包括几层图像。s为每组层数一般为3~5层,最后可以将s和k的关系确立为 k = 2 1 / s k=2^{{1}/{s}} k=21/s
    SIFT特征提取与检测_第2张图片

  • 尺度空间检测极值点——DOG上某个像素要和本尺度的8个像素以及上下相邻尺度各9个相邻像素共26个像素值进行比较,以确定是否为局部最大或最小值。如果它是相邻像素中最高或者最低的像素,则确定该像素点是尺度空间的极值点之一。

SIFT特征提取与检测_第3张图片
  1. 筛选关键点,剔除不稳定的极值点
    此时已经存在了尺度不变的关键点,但是要对这些关键点进行筛选,去掉对比度度的以及处于不理想边缘处的关键点,得到符合要求的特征关键点。

  2. 在特征点处提取特征描述符,为特征点分配方向值
    在此阶段为每个关键点指定一个方向使他们不变,并将该方向纳入关键点子特性描述当中,那么这个关键点就具有了旋转不变性。
    对于已经检测到的特征点,我们可以得到该特征点的尺度值σ ,所以确定该参数可以得到该尺度下的高斯图像:
    在这里插入图片描述
    我们通过每个极值点的梯度来为极值点赋予方向,梯度幅值等于上下两点像素值差的平方加上左右两点像素值差的平方,梯度方向则为上下两点像素值差与左右两点像素值差的商。
    SIFT特征提取与检测_第4张图片
    确定关键点的方向采用梯度直方图统计法,统计以关键点为原 点,一定区域内的图像像素点对关键点方向生成所作的贡献。

  3. 生成特征描述符,利用特征描述符寻找匹配特征点
    在前面我们已经指定了特征关键点的位置、尺度和方向。这样特征点就已经对这些参数变化保持了不变性。这时则要生成一种能够描述这样特征的描述符,尽可能让其对于其他变化也有一定不变性,比如光照和三维视角变化。
    首先在关键点周围采用16×16的邻域。将该16×16区域进一步划分为4×4子块,对于这些子块中的每一个小块,使用幅度和方向生成柱状图。
    SIFT特征提取与检测_第5张图片
    在此阶段,bin的大小增加,只占用8个bin(不是36个)。每一个箭头代表8个bin,箭头的长度定义了幅度。因此,每个关键点总共有128个bin值。

  4. 关键点匹配——为了对这些特征点进行匹配,需要对这些特征点运用适当的比较方法来找到相应关系。
    特征点的匹配可以采用穷举法来完成,但是这样耗费的时间太多,一 般都采用kd树的数据结构来完成搜索。搜索的内容是以目标图像的特征点为基准,搜索与目标图像的特征点最邻近的原图像特征点和次邻 近的原图像特征点。

五、SIFT算法特征匹配实验

实验1:构建图像的数据集
SIFT特征提取与检测_第6张图片
实验2:图片的SIFT特征提取,并展示特征点

SIFT特征提取与检测_第7张图片 SIFT特征提取与检测_第8张图片 SIFT特征提取与检测_第9张图片 SIFT特征提取与检测_第10张图片 SIFT特征提取与检测_第11张图片 SIFT特征提取与检测_第12张图片 SIFT特征提取与检测_第13张图片 SIFT特征提取与检测_第14张图片 SIFT特征提取与检测_第15张图片

小结:SIFT从理论上是一种相似不变量,即对图像尺度变化和旋转是不变量。同时,SIFT算法对图像的复杂变化和光照变化有了较强的适应性,其在构造描述子时,以子区域的统计特性,而不是以的单个像素作为研究对象,提高了局部变形的适应能力,定位的精确度比较高。
实验3:SIFT特征匹配
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 = r'D:\CVphoto\022512.jpg'
  im2f = r'D:\CVphoto\022510.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特征匹配结果:

SIFT特征提取与检测_第16张图片
SIFT特征提取与检测_第17张图片

SIFT特征提取与检测_第18张图片 SIFT特征提取与检测_第19张图片

小结:通过上一小节的学习,可以发现Harris角点的匹配数很多,SIFT匹配点数量少一些。
SIFT匹配点因为有通过当两幅图像的SIFT特征向量生成后,下一步我们采用关键点特征向量的欧式距离来作为两幅图像中关键点的相似性判定度量。取图像1中的某个关键点,并找出其与图像2中欧式距离最近的前两个关键点,在这两个关键点中,如果最近的距离除以次近的距离少于某个比例阈值,则接受这一对匹配点。降低这个比例阈值,SIFT匹配点数目会减少,但更加稳定。
筛选去掉低对比度或非常靠近边缘的关健点,所以在sift的特征匹配中,匹配的关健点虽然数量减少,但是准确度提高很多。

实验4:给定数据集,进行图像检索,输出与其匹配度最高的三张图片

# -*- coding: utf-8 -*-

from PIL import Image

from pylab import *

from numpy import *

import os

from PCV.tools.imtools import get_imlist  # 导入原书的PCV模块

import matplotlib.pyplot as plt  # plt 用于显示图片

import matplotlib.image as mpimg  # mpimg 用于读取图片


def process_image(imagename, resultname, params="--edge-thresh 10 --peak-thresh 5"):
    """ 处理一幅图像,然后将结果保存在文件中"""

    if imagename[-3:] != 'pgm':
        # 创建一个pgm文件

        im = Image.open(imagename).convert('L')

        im.save('tmp.pgm')

        imagename = 'tmp.pgm'

    cmmd = str("sift " + imagename + " --output=" + resultname + " " + params)

    os.system(cmmd)

    print 'processed', imagename, 'to', resultname


def read_features_from_file(filename):
    """读取特征属性值,然后将其以矩阵的形式返回"""

    f = loadtxt(filename)

    return f[:, :4], f[:, 4:]  # 特征位置,描述子


def write_featrues_to_file(filename, locs, desc):
    """将特征位置和描述子保存到文件中"""

    savetxt(filename, hstack((locs, desc)))


def plot_features(im, locs, circle=False):
    """显示带有特征的图像

       输入:im(数组图像),locs(每个特征的行、列、尺度和朝向)"""

    def draw_circle(c, r):

        t = arange(0, 1.01, .01) * 2 * pi

        x = r * cos(t) + c[0]

        y = r * sin(t) + c[1]

        plot(x, y, 'b', linewidth=2)

    imshow(im)

    if circle:

        for p in locs:
            draw_circle(p[:2], p[2])

    else:

        plot(locs[:, 0], locs[:, 1], 'ob')

    axis('off')


def match(desc1, desc2):
    """对于第一幅图像中的每个描述子,选取其在第二幅图像中的匹配

    输入:desc1(第一幅图像中的描述子),desc2(第二幅图像中的描述子)"""

    desc1 = array([d / linalg.norm(d) for d in desc1])

    desc2 = array([d / linalg.norm(d) for d in desc2])

    dist_ratio = 0.6

    desc1_size = desc1.shape

    matchscores = zeros((desc1_size[0], 1), 'int')

    desc2t = desc2.T  # 预先计算矩阵转置

    for i in range(desc1_size[0]):

        dotprods = dot(desc1[i, :], desc2t)  # 向量点乘

        dotprods = 0.9999 * dotprods

        # 反余弦和反排序,返回第二幅图像中特征的索引

        indx = argsort(arccos(dotprods))

        # 检查最近邻的角度是否小于dist_ratio乘以第二近邻的角度

        if arccos(dotprods)[indx[0]] < dist_ratio * arccos(dotprods)[indx[1]]:
            matchscores[i] = int(indx[0])

    return matchscores


def match_twosided(desc1, desc2):
    """双向对称版本的match()"""

    matches_12 = match(desc1, desc2)

    matches_21 = match(desc2, desc1)

    ndx_12 = matches_12.nonzero()[0]

    # 去除不对称的匹配

    for n in ndx_12:

        if matches_21[int(matches_12[n])] != n:
            matches_12[n] = 0

    return matches_12


def appendimages(im1, im2):
    """返回将两幅图像并排拼接成的一幅新图像"""

    # 选取具有最少行数的图像,然后填充足够的空行

    rows1 = im1.shape[0]

    rows2 = im2.shape[0]

    if rows1 < rows2:

        im1 = concatenate((im1, zeros((rows2 - rows1, im1.shape[1]))), axis=0)

    elif rows1 > rows2:

        im2 = concatenate((im2, zeros((rows1 - rows2, im2.shape[1]))), axis=0)

    return concatenate((im1, im2), axis=1)


def plot_matches(im1, im2, locs1, locs2, matchscores, show_below=True):
    """ 显示一幅带有连接匹配之间连线的图片

        输入:im1, im2(数组图像), locs1,locs2(特征位置),matchscores(match()的输出),

        show_below(如果图像应该显示在匹配的下方)

    """

    im3 = appendimages(im1, im2)

    if show_below:
        im3 = vstack((im3, im3))

    imshow(im3)

    cols1 = im1.shape[1]

    for i in range(len(matchscores)):

        if matchscores[i] > 0:
            plot([locs1[i, 0], locs2[matchscores[i, 0], 0] + cols1], [locs1[i, 1], locs2[matchscores[i, 0], 1]], 'c')

    axis('off')


# 获取project2_data文件夹下的图片文件名(包括后缀名)

filelist = get_imlist(r'D:\CVphoto\0306')

# 输入的图片

im1f = r'D:\CVphoto\0306\03062.jpg'

im1 = array(Image.open(im1f))

process_image(im1f, 'out_sift_1.txt')

l1, d1 = read_features_from_file('out_sift_1.txt')

i = 0

num = [0] * 30  # 存放匹配值

for infile in filelist:  # 对文件夹下的每张图片进行如下操作

    im2 = array(Image.open(infile))

    process_image(infile, 'out_sift_2.txt')

    l2, d2 = read_features_from_file('out_sift_2.txt')

    matches = match_twosided(d1, d2)

    num[i] = len(matches.nonzero()[0])

    i = i + 1

    print '{} matches'.format(num[i - 1])  # 输出匹配值

i = 1

figure()

while i < 4:  # 循环三次,输出匹配最多的三张图片

    index = num.index(max(num))

    print index, filelist[index]

    lena = mpimg.imread(filelist[index])  # 读取当前匹配最大值的图片

    # 此时 lena 就已经是一个 np.array 了,可以对它进行任意处理

    # lena.shape  # (512, 512, 3)

    subplot(1, 3, i)

    plt.imshow(lena)  # 显示图片

    plt.axis('off')  # 不显示坐标轴

    num[index] = 0  # 将当前最大值清零

    i = i + 1

show()

实验结果
SIFT特征提取与检测_第20张图片

匹配结果:
SIFT特征提取与检测_第21张图片

小结:输入图片和数据集中图片匹配的特征点匹配值存放在num中,可以通过对num进行排序后取最大值得到匹配值最多的图像。
SIFT特征检索是基于内容的图像检索技术是基于图像自身的内容特征来检索图像,这免去人为标注图像的过程。基于内容的图像检索技术是采用某种算法来提取图像中的特征(SIFT,SURF,CNN等),并将特征存储起来,组成图像特征数据库。当需要检索图像时,采用相同的特征提取技术提取出待检索图像的特征,并根据某种相似性准则计算得到特征数据库中图像与待检索图像的相关度,最后通过由大到小排序,得到与待检索图像最相关的图像,实现图像检索。
SIFT算法是提取特征的一个重要算法,该算法对图像的扭曲,光照变化,视角变化,尺度旋转都具有不变性。SIFT算法提取的图像特征点数不是固定值,维度是统一的128维。
实验5:地理标记图像匹配
实验代码:

# -*- 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 = r'D:\CVphoto\0306'  # set this to the path where you downloaded the panoramio images
path = r'D:\CVphoto\0306' # 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('house.png')

实验结果:
SIFT特征提取与检测_第22张图片

小结:总共拍摄了十五张图片,将使用局部描述算子对同一地理位置拍摄的图像进行分类。为了对匹配后的图像进行连接可视化,我们可以在一个图中用线将他们链接起来。
SIFT特征提取与检测_第23张图片
因为选用的图像为同一处建筑的不同尺度,不同光照和不同角度,SIFT算法的匹配比较有效,对这些图像很很有效的被匹配出来,但是对于纹理比较相近,例如云彩,阴影等比较多的图片,对于建筑物的匹配会有影响。
当纹理比较多,图像中障碍物比较多时,图像分类的效果不佳,因为我的这组数据集图片过于相似,所以分类效果不佳。实验结果不是很理想。

重新建立数据集实验结果:

增加了其他的建筑物,适当减少原数据集中过于相似的图片。
SIFT特征提取与检测_第24张图片
这次的匹配中连线减少了很多,可以很明显的观察到两张与原图像集并不匹配的建筑连在了一起,说明SIFT算法在地理建筑匹配中的确有很大优势。
注意安装顺序:
1.我们需要先下载安装graphviz(graphviz-2.28.0.msi或者压缩包都可以)
2.配置环境变量,将D:…\Graphviz\bin添加到环境变量的path中。
3.在cmd后输入pip install pydot进行pydot的安装下载。
出现的问题:
1.SyntaxError: Non-ASCII character ‘\xe5’
解决办法:注释出现了中文,将路径的“/”改成“\”
2.File “D:\anaconda2\lib\site-packages\pydot.py”, line 1817, in write
修改set_prog函数


    def set_prog(self, prog):

        """Sets the default program.



        Sets the default program in charge of processing

        the dot file into a graph.

        """

        path = r'path/to/your/dot/exe/file'# 例如我的:F:\Program File\Anaconda\envs\python3.5\Lib\site-packages\bin

        prog  = os.path.join(path, prog)

        prog += '.exe'

        #self.prog = prog

        return prog

修改creat函数


        if prog is None:

            prog = self.prog

        assert prog is not None

        prog = self.set_prog('dot') #调用函数

六、RANSAC算法
1、算法描述

RANSAC(RAndom SAmple Consensus,随机采样一致)算法是从一组含有“外点”(outliers)的数据中正确估计数学模型参数的迭代算法。“外点”一般指的是数据中的噪声,比如说匹配过程中的误匹配点和估计曲线中的离群点。所以,RANSAC也是一种“外点”检测算法。RANSAC算法是一种不确定算法,它只能在一种概率下产生结果,而且这个概率会随着迭代次数的增大而增大。
对于RANSAC算法来说是一个基本的假设就是数据是由“内点”(inliers)和“外点”(outliers)。内点就是组成模型参数的数据,外点就是不适合模型的数据。

2、RANSAC算法在SIFT特征匹配中的应用

特征匹配:根据不同图片上提取到特征点进行匹配。本次实验使用的是SIFT特征点提取。
特征匹配还需要完成的一步是计算单应性矩阵。单应性矩阵是一种8自由度的模型。每个特征点可以构造2个方程,因此只需要4个匹配点对即可,一般得到匹配点多于4个,这时可以使用最小二乘法提高精度。
此时得到的匹配点中有“内点”和“外点”,内点数量越多,得到结果更加准确,RANSAC算法就是用来区分“内点”和“外点”。
RANSAC算法核心思想是抽取 n n n次中至少有一次四个匹配点全部正确的概率是

p ( H i s c o r r e c t ) = 1 − ( 1 − p i r ) n p(Hiscorrect)=1-(1-p{_{i}}^{r})^{n} p(Hiscorrect)=1(1pir)n

p i pi pi是内点概率,
p i = n i n l i n e r s / ( n i n l i n e r s + n o u t l i n e r s ) pi=n_{inliners}/(n_{inliners}+n_{outliners}) pi=ninliners/(ninliners+noutliners)
r r r是每次抽出的点的数量,这里是4

p ( H i s c o r r e c t ) p(Hiscorrect) p(Hiscorrect)可以推出:

n = l o g ( 1 − p ) l o g ( 1 − p i r ) n=\frac{log(1-p)}{log(1-p{_{i}}^{r})} n=log(1pir)log(1p)

“内点”的概率 p i pi pi通常是一个先验值。然后 p p p是我们希望RANSAC得到正确模型的概率。如果事先不知道 p i pi pi的值,可以使用自适应迭代次数的方法。也就是一开始设定一个无穷大的迭代次数,然后每次更新模型参数估计的时候,用当前的“内点”比值当成 p i pi pi来估算出迭代次数,当某次得到“内点”的数目占有的比例 p i pi pi大于等于95%则停止,选择 p i pi pi最大的基础矩阵作为最终的结果。
在本次实验中, p p p表示4个点组成的随机样本中至少有一次没有外点的概率,代入公式中求出 n n n的值(即迭代次数N,N的初始值应该取一个较大值)。

3、算法实现步骤

核心思想:在带有外点的数据中尽可能找出所有内点,然后利用内点拟合出最佳的模型。
初始化 p p p , 内点的阈值 η \eta η, 迭代次数 N N N ,进行迭代,迭代次数为 N N N.

  1. 选择出可以估计出模型的最小数据集;(对于直线拟合就是两个点,对于计算Homography矩阵就是四个点),随机选取4个匹配点对,要求三个不能共线
  2. 根据最小二乘法/直线线性变换计算(DLT)单应性矩阵 H H H.
  3. 对所有的匹配点对:计算重投影误差: ​ d = ∥ u u i − H u j ∥ d=\left \| uu_{i}-H_{u_{j}^{}}\right \| d=uuiHuj,如果误差小于阈值​ η \eta η ,则为内点,反之为外点.
  4. 计算出内点概率 p i pi pi ​,比较当前模型和之前推出的最好的模型的“内点”数的数量,记录最大“内点”数的模型参数和“内点”数,并更新迭代次数 N N N.
  5. 由迭代循环得到最大的内点数计算单应性矩阵 H H H.
4、对比实验
  1. 实验一:
    描述:景深单一,图片内有很多相似的物品,例如相同的门和窗栏。
    sift特征提取匹配点:
    利用RANSAC算法剔除错误匹配点:
    小结:通过对比可以发现,图片中存在很多周围领域灰度值相似值高的匹配点(相似的门框),同时一些干扰的树木对匹配的影响很大,SIFT特征匹配出现了很多错误匹配点。RAMSAC算法进行剔除错误匹配点的效果很好,去除了部分树木的干扰,三层建筑中,两层的特征匹配的都很正确,对于标识牌和标语的匹配都很正确,因为门和窗栏的形状颜色一模一样,所以,存在一些窗栏的混乱匹配也无可厚非。

  2. 实验二:
    描述:景深单一,图片中没有太多干扰和相似事物。
    sift特征提取匹配点:

       利用RANSAC算法剔除错误匹配点:
      小结:在景深单一,特征点比较明显的情况下,RANSAC算法剔除错误匹配点的效果很好。因为图像没有旋转和太大的角度变换, SIFT特征匹配能够匹配出较多的匹配点,但是利用RANSAC算法之后,可以明显看到有的匹配正确的点也会被删除掉,留下的少部分的匹配点完全正确。 所以经过筛选后的匹配点虽然位置很精准,但是同时也减弱了SIFT特征匹配的效果。
  1. 实验三:
    描述:景深丰富,图片内容复杂,有远近差异。
    sift特征提取匹配点:

    利用RANSAC算法剔除错误匹配点:

    小结:在景深丰富的情况下,SIFT特征匹配在近景产生的匹配点较多,且有一些错误匹配点,远景产生的匹配点较少。进行RANSAC算法剔除之后,留下了的匹配点比较精准,但是仍然存在错误匹配点,并不是100%的正确。和实验二一样,RANSAC算法删除了一些正确的匹配点,剩下的匹配点数量较少。
  2. 总结:
    RANSAC优点:它能从包含大量局外点的数据集中估计出高精度的参数。
    RANSAC缺点:计算参数的迭代次数没有上限;如果设置迭代次数的上限,得到的结果可能不是最优的结果,甚至可能得到错误的结果。
    RANSAC只有一定的概率得到可信的模型,概率与迭代次数成正比。
    RANSAC可以去除大部分的错误的匹配点但是也有删掉正确匹配点的可能。
5、代码实现
# -*- 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 = '.....'
    im2 = '.....'



    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()#清除所有窗口
6、实验总结

遇到的问题
    1.报错误:
sift = cv2.xfeatures2d.SIFT_create() AttributeError: 'module' object has no attribute 'xfeatures2d'
    解决方法:安装opencv-contrib-python
注意opencv的版本!!!如果想要使用 cv.xfeatures2d.SIFT_create()函数应适当降低opencv的版本,可以直接在cmd中输入以下执行命令:

pip install opencv-contrib-python==3.4.0.12 -i https://pypi.tuna.tsinghua.edu.cn/simple some-package

如果不能安装成功,最好卸载掉之前的版本再安装这个版本,经过测试opencv-contrib-python-3.4.0.12和opencv-contrib-python-3.4.2.16这两个版本下是可以使用的。
至于其他版本,可以随缘试一试。。。
    2.报错误:
kp1, des1 = sift.detectAndCompute(psd_img_1, None) error: C:\projects\opencvpython\opencv_contrib\modules\xfeatures2d\src\sift.cpp:1121: error: (-5) image is empty or has incorrect depth (!=CV_8U) in function cv::xfeatures2d::SIFT_Impl::detectAndCompute
    解决方法:
图像为空或深度不正确,图像没有读入进去,路径出现了问题,按照之前读入图片的方式即可,不必要改成cv2.imread(…)
注意图片在文件夹的位置,路径一定要写对!!!

七、总结
  1. 出现的问题:empire.sift文件为空
    解决方法:Python 2实现SIFT匹配需要下载 VLFeat 工具包,建议下载vlfeat-0.9.20-bin.tar.gz,解压之后,需要将vlfeat-0.9.20/bin/win64文件夹下的sift.exe和vl.dll复制到当前项目目录下。
  2. 出现的问题:无opencv的工具包
    解决方法:Opencv的版本需要和python的版本对应,我的python版本是anaconda2下的python2,所以需要安装64位的opencv_python-2.4.13.7-cp27-cp27m-win_amd64.whl(cp27的意思是python2.7.x,如果是python3则需要安装opencv_python-3.4.5+contrib-cp37-cp37m-win_amd64.whl)
    如果在opencv 的官网
    https://www.lfd.uci.edu/~gohlke/pythonlibs/
    下载速度比较慢可以去找国内的镜像文件。
    将whl文件移动到anaconda\Lib\site-package这个文件夹下面,再在cmd里面cd到该目录下面执行语句pip install opencv_python-2.4.13.7-cp27-cp27m-win_amd64.whl
    在这里插入图片描述
  3. 出现的问题:sift = cv2.xfeatures2d.SIFT_create()不可执行
    需要先下载opencv-python3.4.2.16这个版本才能执行这个函数
    在下载opencv时最好下载+contrib这个部分的,这个版本里面有更多的图像处理功能。
  4. 实验总结:在本次实验中对利用sift算法对图像的特征点进行提取和检测,通过实验我们可以发现,sift算法对于特征点的检测的准确性高于harris算法,不但对于旋转、尺度缩放、亮度变化保持不变性,而且对视角变化、仿射变换、噪声也保持一定程度的稳定性。且该方法对特征点的个数和有效点的比例没有要求。当特征点不是很多时,经优化的SIFT匹配算法甚至可以达到实时的要求。而且可以很方便的与其他形式的特征向量进行联合。
    但是sift特征点在识别对称图形,或者没有纹理的图片时,容易造成误差。因为其是方法通过对特征点构造128维的向量,然后对向量进行匹配,这样图像就得满足足够多的纹理,否则构造出的128维向量区别性就不是太大,极限情况如指纹图像的匹配,星图识别等这类图像特征点周围根本没有什么纹理的时候匹配效果不佳。

你可能感兴趣的:(SIFT特征提取与检测)