计算机视觉--SIFT特征提取与检索

一、SIFT(尺度不变特征变换)介绍

1.1 SIFT原理

      SIFT包括兴趣点检测器和描述子。SIFT描述子具有非常强的稳健性,经常和许多不同的兴趣点检测器结合使用。SIFT特征对于尺度,旋转和亮度都具有不变性,因此,它可用于三维视角和噪声的可靠匹配。
      SIFT 描述子h(x, y,θ)是对特征点附近邻域内高斯图像梯度统计结果的一种表示,它是一个三维的阵列,但通常将它表示成一个矢量。矢量是通过对三维阵列按一定规律进行排列得到的。特征描述子与特征点所在的尺度有关,因此,对梯度的求取应在特征点对应的高斯图像上进行。

1.2 SIFT算法的实现步骤

  1. 构建尺度空间,检测极值点,获得尺度不变性;
  2. 特征点过滤并进行精确定位,剔除不稳定的特征点;
  3. 在特征点处提取特征描述符,为特征点分配方向值;
  4. 生成特征描述子,利用特征描述符寻找匹配点;
  5. 计算变换参数。

1.3 SIFT算法的数学表达

  1. 确定描述子采样区域
      SIFI 描述子h(x, y,θ)是对特征点附近邻域内高斯图像梯度统计结果的一种表示。将特征点附近邻域划分成BpX Bp个子区域,每个子区域的尺寸为mσ个像元,其中,m=3,Bp=4。σ为特征点的尺度值。考虑到实际计算时,需要采用双线性插值,计算的图像区域为mσ(Bp+ 1)。如果再考虑旋转的因素,那么,实际计算的图像区域应大mσ(Bp+ 1)√2。
    在这里插入图片描述
  2. 生成描述子
    2.1 旋转图像至主方向
      为了保证特征矢量具有旋转不变性,需要以特征点为中心,将特征点附近邻域内(mσ(Bp+ 1)√2 x mσ(Bp+ 1)√2)图像梯度的位置和方向旋转一个方向角θ,即将原图像x轴转到与主方向相同的方向。旋转公式如下:在这里插入图片描述
      在特征点附近邻域图像梯度的位置和方向旋转后,再以特征点为中心,在旋转后的图像中取一个mσBp x mσBp大小的图像区域。并将它等间隔划分成Bp X Bp个子区域,每个间隔为mσ像元。
    在这里插入图片描述
    2.2 生成特征向量
      在每子区域内计算8个方向的梯度方向直方图,绘制每个梯度方向的累加值,形成一个种子点。与求特征点主方向时有所不同,此时,每个子区域的梯度方向直方图将0° -360°划分为8个方向范围,每个范围为45°,这样,每个种子点共有8个方向的梯度强度信息。由于存在4X4(Bp X Bp)个子区域,所以,共有4X4X8=128个数据,最终形成128维的SIFT特征矢量。
    在这里插入图片描述
  3. 归一化特征向量
      为了去除光照变化的影响,需对上述生成的特征向量进行归一化处理,在归一化处理后,对特征矢量大于α的要进行截断处理,即大于α的值只取α,然后重新进行一次归一化处理,其目的是为了提高鉴别性。

二、SIFT特征提取与检索实验

2.1 数据集准备

介绍:该数据集取自家乡的田野拍摄,照片之间有些有重合场景,但每张照片都互不相同,共16张。
在这里插入图片描述
在这里插入图片描述

2.2 SIFT特征提取并展示特征点

原理介绍:SIFT是一种检测局部特征的算法,该算法通过求一幅图中的特征点及其有关scale 和 orientation 的描述子得到特征并进行图像特征点匹配,获得了良好效果。

代码实现

# -*- 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 = '../data/empire.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'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角点提取的比较:
在这里插入图片描述
对数据集中每张图片SIFT特征提取,展示特征点:
在这里插入图片描述
在这里插入图片描述

分析

  • 对以上数据集进行SIFT特征提取,可以看出不同尺度、亮度、旋转变化的图片检测出的特征点大致相同,因此,得出结论:SIFT特征是图像的局部特征,其对旋转、尺度缩放、亮度变化保持不变性,对视角变化、仿射变换、噪声也保持一定程度的稳定性。
  • 对比SIFT特征提取与Harris角点检测结果,可以看出Harris角点检测算法提取出的特征点明显低于SIFT算法。对于比较平坦的区域,Harris角点检测算法能检测到的特征点很少甚至没有。故而,由于无法检测到足够的特征点,Harris角点检测算法在特征点有效性、计算时效性以及特征点相似不变性三个方面均不如SIFT算法显的高效。

2.3 SIFT特征匹配

原理介绍:SIFT特征匹配算法是一种基于尺度空间的、对图像缩放、旋转甚至仿射变换保持不变性的特征匹配算法。SIFT特征匹配算法分两个阶段来实现:第1阶段是SIFT特征的生成,即从多幅待匹配图像中提取出对尺度缩放、旋转、亮度变化无关的特征向量; 第2阶段是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 = '../data/c3.jpg'
  im2f = '../data/c4.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()

结果展示

1)有匹配特征点:
如下图,特征点匹配数量为75.
在这里插入图片描述
2)无匹配特征点:
如下图,特征点匹配数量为0.
在这里插入图片描述

分析

  • Sift匹配是一种电脑视觉的算法用来侦测与描述影像中的局性特征,它在空间尺度中寻找极值点,并提取出其位置、尺度、旋转不变量。
  • SIFT特征是基于物体上的一些局部外观的兴趣点而与影像的大小和旋转无关。对于光线、噪声、些微视角改变的容忍度高。从实验结果 1)可以看出,两幅图片中绝大多数的匹配特征点都被检测出来,可知SIFT匹配的准确度高。
  • 在实验过程中,也出现过两张图片有同样场景,但很少甚至没有检测出匹配特征点的情况,初步判断是误差难以避免,SIFT算法也会存在错配、误配现象。

2.4 数据集内部检索匹配最多的图片

需要实现:给定一张输入的图片,在数据集内部进行检索,输出与其匹配最多的三张图片。
原理介绍:将输入图片与数据集内每张图片进行两两SIFT特征匹配,将匹配值最高的三张图片输出。

代码实现

# -*- 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('../data/field/')

# 输入的图片
im1f = 'c3.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()

结果展示

在这里插入图片描述

分析

  • 将输入图片与数据集中图片两两进行特征匹配后的匹配数保存在num中,要找到与输入图片匹配特征点最多的图只要找num矩阵中数值最大的三个位置即可。
  • 从结果可看出输入图片匹配到的三张图片与之重合度较高,图片中有一个明显的重叠物体是丝瓜架,从整个数据集中观察,匹配精确度高。
  • SIFT特征的信息量大,适合在海量数据库中快速准确匹配,准确度也相当高。

三、总结

实验内容总结

扫描二维码关注公众号,回复: 9683293 查看本文章
  • SIFT特征不只具有尺度不变性,即使改变旋转角度,图像亮度或拍摄视角,仍然能够得到好的检测效果。
  • SIFT独特性好,信息量丰富,适合于在海量特征数据库中进行快速、准确的匹配。
  • SIFT有多量性,即使少数的几个物体也可以产生大量的SIFT特征向量。
  • 相比于Harris角点检测,SIFT特征提取在特征点有效性、计算时效性以及特征点相似不变性几个方面都较为高效。

实验过程总结
问题:import sift时出错。
解决:安装vlfeat,具体步骤为——
1.下载0.2.20版本的vlfeat.
2.把vIfeat文件夹 下win64中的sift.exe和v.dIl这两个文件复制到项目的文件夹中.
3.修改Anaconda文件夹下的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用法是需要加括号。

问题:报错"dot" not found in path
解决:首先注意安装pydot过程——
(找了很多种方法,很多网址上的描述都不一样,我自己是这么做的:)

  1. 安装graphviz软件,地址在: graphviz下载.选择下载后缀为.msi的文件,下载后双击安装。
  2. 把安装后的graphviz软件的bin目录设为环境变量(保险起见,用户变量和系统变量的Path都要新建,具体如下)。
    在这里插入图片描述
  3. 安装pydot:pip install pydot
    在这里插入图片描述(按道理到这应该就安好了,但是我还是会报错,所以又尝试了其他方法,下面的操作可能需要也可能不需要,这边一起写了)
  4. 在Anaconda Prompt中执行以下三个语句:①pip install graphvizpip install pydotpip install pydot-ng
    在这里插入图片描述
    发现这时pydot包已经成功导入,但是运行可视化接连的图片代码会出现"dot" not found in path的错误。
    解决:①修改pydot.py中的代码——
    将Dot类中的"dot"改成"dot.exe"
    在这里插入图片描述
    ②回到我们的代码,在代码开头处添加下载的graphviz的bin目录位置,如下
import os
os.environ['PATH'] = os.environ['PATH'] + (';d:\\Alike\\python_dw\\graphviz_dw\\bin\\')

注意:pydot的Node节点添加图片时,图片的路径需要为绝对路径,且分隔符为/。
在这里插入图片描述

发布了16 篇原创文章 · 获赞 5 · 访问量 2872

猜你喜欢

转载自blog.csdn.net/qq_41784565/article/details/104726166