编程语言
首页 > 编程语言> > OpenCV+Python实现SIFT匹配地理标记图像

OpenCV+Python实现SIFT匹配地理标记图像

作者:互联网

OpenCV+Python实现SIFT匹配地理标记图像

1. 什么是SIFT

SIFT的全称是Scale Invariant Feature Transform,尺度不变特征变换,2004年,不列颠哥伦比亚大学的D.Lowe在他的论文“Distinctive Image Features from Scale-Invariant Keypoints”(尺度不变关键点中的独特图像特征)中提出了一种新的尺度不变特征变换(SIFT)算法,该算法提取关键点并计算其描述符。
SIFT特征对旋转、尺度缩放、亮度变化等保持不变性,是一种非常稳定的局部特征。

1.1 SIFT算法解决的问题

以往的角点检测算法,例如Harris角点检测器可以做到旋转不变(rotation-invariant),即无论图像发生旋转,算法依然可以找到相同的角。但是当图像发生缩放之后,原先的角可能就发生了变化,算法就无法检测出角点。例如下图,在同一检测窗口中放大小窗口中小图像中的角时,该角是平坦的。为了解决这个问题,我们需要一种尺度不变(或者叫缩放不变)算法。

在这里插入图片描述

1.2 SIFT算法的步骤

1.尺度空间极值检测
如上图,对于一个图像我们不能使用同样大小的检测窗口来检测不同放缩比例的关键点。对一个“小”角点来说是没问题啦,但要检测更“大”的角点,我们需要更大的窗口。为此,使用尺度空间过滤。

在其中,不同的σ值的图像对应的高斯-拉普拉斯算子被算出来。LoG(Laplacian of Gaussian,详见高斯拉普拉斯算子-知乎就是前文的高斯-拉普拉斯算子)作为一个斑点检测器,能检测出由于σ变化引起的不同大小下的斑点。

简单的说,σ 就是一个放缩比例的参数。例如,在上图中,有较小 σ 值的高斯内核对一个“小”角点返回一个较大的值,然而较大的 σ值的高斯内核适合较“大”的角点。所以,我们可以在放缩空间中求出局部最大值,它给我们返回一个(x,y,σ)值的列表,它告诉我们,在(x,y)点 σ 放缩比的情况下,有一个潜在的关键点。
原文:https://blog.csdn.net/ssybc/article/details/86628410

但是LoG比较消耗性能,因此 SIFT 算法使用了高斯差分算子(Difference of Gaussian,高斯差分算子),它是LoG的近似值。

高斯差分算子可以通过具有两个不同的图像的高斯模糊的差来获得σ。令他们为 σ 和 kσ,这个过程为不同图像的高斯金字塔的阶进行了处理。
如下图所示:
高斯差分算子
算出了 DoG 之后,图像就开始在放缩空间中搜索局部极值。比方说,图像中的某像素点用来和它周围的8个邻居比较。

例如,将一幅图像中的一个像素与它的8个相邻像素以及下一个比例中的9个像素和前一个比例中的9个像素进行比较,如果它是局部的极大值,它就是一个潜在的关键点。基本上意思就是说那个关键点,在此比例下是最有代表性的。
如下图所示:
DOG

对于不同的参数,论文给出了一些经验数据,可以概括为,阶数=4,放缩等级数=5,初始σ=1.6,k=\sqrt{2}等作为最优值。
原文:https://blog.csdn.net/ssybc/article/details/86628410

2.关键点定位
一旦潜在关键点的位置被算出来了,为了得到更精确的结果,它们必须被再精选一次。他们使用放缩空间中的泰勒级数展开式,得到了更精确的极值点位置。并且如果该极值处的强度小于阈值(该论文为0.03),则不判定为关键点。

这个阈值在OpenCV里叫做contrastThreshold。

DoG 算法对于边缘也有很高的响应,所以我们得把边缘除去。对此,使用了一个与哈里斯角点检测相似的概念。他们使用一个 2x2 的黑塞矩阵(H)(译者附:黑塞矩阵)来计算主曲率。由Harris角检测器可知,对于边,一个特征值远大于另一个特征值。

这里用了一个简单的函数,如果该比例大于在OpenCV里被称为edgeThreshold的阈值,该关键点会被抛弃。在论文中该阈值被设为10。

因此,它消除了任何低对比度的关键点和边缘关键点,剩下的是强烈的兴趣点。

3. 方向指定
现在为每个关键点指定一个方向,以实现图像旋转的不变性。邻域取围绕关键点位置的区域,大小取决于放缩比例。在那个区域计算出梯度大小和梯度方向。
一个有着36个抽屉、覆盖360度的方向直方图被创建了出来(它通过梯度幅值和高斯加权圆形窗口来加上了权重,其中σ取关键点的1.5倍放缩比)。取该直方图中最高的峰值,以及任何在最高峰值80%之上的峰值点,都用于计算方向。它创建的关键点具有相同的位置和放缩比,但方向不同。有利于匹配的稳定性。

4. 关键点描述
现在关键点的描述符已经创建好了。取围绕关键点的一个 16x16 邻域,它被分成16个4x4大小的子块。对于每个子块,创建8抽屉的方向直方图,这样总共就有了128个可以用的有值得抽屉。它用一个向量来表示,形成了关键点描述符。除此之外,还采取了一些措施来提升对抗光照变化、旋转等的健壮性。

5. 关键点匹配
两张图像之间的关键点,通过识别它们之间的最近的邻居来匹配。但在某些情况之下,第二相近的匹配度有可能和最相近的匹配度非常接近。这有可能是因为噪音或者其他某些原因引起的。如果是那样的话,取次接近的和最接近的比值。如果比值大于0.8,它们就被丢弃掉。在该论文中使用的这种方案,丢弃掉了大约 90% 的错误匹配,但只丢掉了5%的正确匹配。

这就是SIFT算法的一个简单的摘要。想要理解更多的细节,强烈推荐你阅读原论文。

2. SIFT实际应用

2.1 OpenCV中的SIFT

这是根据Opencv文档中的示例写的一个演示代码,和原文不同的地方就是SIFT调用方式:“sift = cv.SIFT_create()” 需要改为 “sift = cv.xfeatures2d.SIFT_create()”

import cv2 as cv

img = cv.imread(r'test_pic\soc.jpg',1)
sift = cv.xfeatures2d.SIFT_create()
kp = sift.detect(img,None)
img=cv.drawKeypoints(img,kp,img)
cv.imwrite(r'test_pic\sift_keypoints.jpg',img)

结果如下:
在这里插入图片描述

2.SIFT特征匹配

好了,你已经知道了SIFT的基本原理,开始实现SIFT特征匹配吧!(笑)

实现特征匹配我们需要通过SIFT查找得到关键点和特征向量,

import cv2 as cv
import matplotlib.pyplot as plt

img1 = cv.imread(r'test_pic\cool1.jpg')
img2 = cv.imread(r'test_pic\cool2.jpg')
# 创建SIFT特征点检测
sift = cv.xfeatures2d.SIFT_create()
# 计算关键点和特征符
kp1, des1 = sift.detectAndCompute(img1, None)
kp2, des2 = sift.detectAndCompute(img2, None)

# BFmatcher with default parms
bf = cv.BFMatcher(cv.NORM_L2)
matches = bf.knnMatch(des1, des2, k=2) 
# 储存特征向量匹配最好的优质匹配点
goodMatchs = []
for m, n in matches:
    if m.distance < 0.7 * n.distance:
        goodMatchs.append(m)
        
pic3 = cv.drawMatches(img1=img1, keypoints1=kp1, img2=img2, keypoints2=kp2, matches1to2=goodMatchs, outImg=None)
cv.imwrite(r'test_pic\compare.jpg',pic3)

结果如下:
Compare,png

3.SIFT算法匹配地理标记图像

1.匹配地理标记图像

匹配地理标记图像

2.代码

1.前期准备一下缩略图
将缩略图的保存和地理标记图像匹配的主体功能分开的原因是:OpenCV的imwrite()函数会在py文件运行结束后,才将图片保存到指定路径,这将导致pydotplus和graphviz画出的无向图找不到缩略图而报错:
warning!save_pic.py:

import os
import cv2 as cv
from pylab import *

maxsize = (100, 100)  # 定义缩略图的大小
path = r'E:\PycharmProjects\SIFT\pic\result\pic'

# 读取整个文件夹的图片
def read_path(pathname):
    imgname_list = os.listdir(pathname)
    img_list = []
    i = 0
    # 图片列表
    for imgname in imgname_list:
        if imgname.endswith('.jpg'):
            img = cv.imread(pathname + '/' + imgname)
            img_n = cv.resize(img, maxsize, cv.INTER_AREA)
            filename = path + str(i) + '.png'
            cv.imwrite(filename, img_n)  # need temporary files of the right size
            i = i + 1
            print(i)
    return img_list

list = read_path(r'E:\PycharmProjects\SIFT\pic')
print(list)

2.主要的代码如下
SIFT.py:

import os
import cv2 as cv
from pylab import *
import pydotplus as pydot

# 读取整个文件夹的图片
def read_path(pathname):
    imgname_list = os.listdir(pathname)
    img_list = []
    # 图片列表
    for imgname in imgname_list:
        if imgname.endswith('.jpg'):
            img = cv.imread(pathname + '/' + imgname)
            img_list.append(img)
    return img_list


img_list = read_path(r'E:\PycharmProjects\SIFT\pic')
nbr_images = len(img_list)
match_scores = zeros((nbr_images, nbr_images))

for i in range(nbr_images):
    for j in range(i, nbr_images):  # only compute upper triangle
        print('comparing ', i, j)
        sift = cv.xfeatures2d.SIFT_create()
        kp1, des1 = sift.detectAndCompute(img_list[i], None)
        kp2, des2 = sift.detectAndCompute(img_list[j], None)
        # BFMatch匹配
        bf = cv.BFMatcher(cv.NORM_L2)
        matches = bf.knnMatch(des1, des2, k=2)
        # 储存差距小的优秀匹配点
        goodMatches = []
        for m, n in matches:
            if m.distance < 0.5 * n.distance:
                goodMatches.append(m)
        # 计算优秀匹配点的和
        nbr_matches = len(goodMatches)
        # 向match_scores赋值
        print('number of matches = ', nbr_matches)
        match_scores[i, j] = nbr_matches

# 复制
for i in range(nbr_images):
    for j in range(i + 1, nbr_images):  # no need to copy diagonal 不用复制自我匹配的对角线
        match_scores[j, i] = match_scores[i, j]
# 可视化
threshold = 2  #  至少2个以上匹配点就可以算是联系
g = pydot.Dot(graph_type='graph')  # 不需要有向图
maxsize = (100, 100)  # 定义缩略图的大小
path = r'E:\PycharmProjects\SIFT\pic\result\pic'
#两两配对
for i in range(nbr_images):
    for j in range(i + 1, nbr_images):
        if match_scores[i, j] > threshold:
            filename = path + str(i) + '.png'
            g.add_node(pydot.Node(str(i), fontcolor='transparent', shape='rectangle', image=filename))
            filename = path + str(j) + '.png'
            g.add_node(pydot.Node(str(j), fontcolor='transparent', shape='rectangle', image=filename))
            g.add_edge(pydot.Edge(str(i), str(j)))
#绘制S地理标记SIFT匹配图
g.write_jpg('sift.jpg')

3.分步解释

标签:匹配,img,Python,list,OpenCV,SIFT,cv,关键点
来源: https://blog.csdn.net/Ohayo33/article/details/115283888