全网最详细SIFT算法原理实现

Posted Tc.小浩

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了全网最详细SIFT算法原理实现相关的知识,希望对你有一定的参考价值。

文章目录

一、SIFT算法

1.1什么是SIFT算法?

尺度不变特征转换(SIFT, Scale Invariant Feature Transform)是图像处理领域中的一种局部特征描述算法. 该方法于1999年由加拿大教授David G.Lowe提出,申请了专利,其专利属于英属哥伦比亚大学. SIFT专利在2020年3月17日之后到期,现在只需更新cv版本即可免费使用.

SIFT算法不仅只有尺度不变性,当旋转图像,改变图像亮度,移动拍摄位置时,仍可得到较好的检测效果.

其实,在我们生活中,SIFT算法还是有所应用的,比如,我们手机上的全景拍摄,当我们拿着手机旋转拍摄时,就可以得到一幅全景图,大家想过没有,手机摄像头的视角是确定的,为什么通过旋转拍摄时,角度就变大了呢?其实角度并没有变化,只是我们在旋转拍摄时,拍摄了很多的图像,这些图像相邻之间有重叠部分,把这些图像合在一起,去除重叠部分,就可以得到一幅全景图啦.

1.2SIFT算法特点

  • 具有较好的稳定性和不变形,能够适当旋转、尺度缩放、亮度的变化,能在一定程度上不受视角变化、仿射变换、噪声的干扰。
  • 区分性好,能够在海量特征数据库中进行快速准确的区分信息进行匹配
  • 多属性,就算只有单个物体,也能产生大量特征向量
  • 高速性,能够快速的进行特征向量匹配
  • 可扩展性,能够与其它形式的特征向量进行联合

二、SIFT算法实质

在不同的尺度空间上查找关键点,并计算出关键点的方向。

2.1SIFT算法实现特征匹配主要有以下三个流程:

  1. 提取关键点:关键点是一些十分突出的不会因光照、尺度、旋转等因素而消失的点,比如角点、边缘点、暗区域的亮点以及亮区域的暗点。此步骤是搜索所有尺度空间上的图像位置。通过高斯微分函数来识别潜在的具有尺度和旋转不变的兴趣点。
  2. 定位关键点并确定特征方向:在每个候选的位置上,通过一个拟合精细的模型来确定位置和尺度。关键点的选择依据于它们的稳定程度。然后基于图像局部的梯度方向,分配给每个关键点位置一个或多个方向。所有后面的对图像数据的操作都相对于关键点的方向、尺度和位置进行变换,从而提供对于这些变换的不变性。
  3. 通过各关键点的特征向量,进行两两比较找出相互匹配的若干对特征点,建立景物间的对应关系。

三、SIFT算法原理

3.1图像金字塔

图像金字塔是一种以多分辨率来解释图像的结构,通过对原始图像进行多尺度像素采样的方式,生成N个不同分辨率的图像。把具有最高级别分辨率的图像放在底部,以金字塔形状排列,往上是一系列像素(尺寸)逐渐降低的图像,一直到金字塔的顶部只包含一个像素点的图像,这就构成了传统意义上的图像金字塔。


获得图像金字塔一般包括二个步骤:

  1. 利用低通滤波器平滑图像

  2. 对平滑图像进行抽样(采样)

有两种采样方式——上采样(分辨率逐级升高)和下采样(分辨率逐级降低)

上采样:

下采样:

3.2创建图像高斯金字塔

什么是图像高斯金字塔?

在说高斯金字塔之前,我们先来说一下人的眼睛,我们人眼对世界的感知有两种特性:一是近大远小:同一物体,近处看时感觉比较大,远处看时感觉比较小;二是"模糊":更准确说应该是"粗细",我们看近处,可以看到物体的细节(人会觉得比较清楚),比如一片树叶,近看可以看到该树叶的纹理,远处看只能看到该片的大概轮廓(人会觉得比较模糊). 从频率的角度出发,图像的细节(比如纹理,轮廓等)代表图像的高频成分,图像较平滑区域表示图像的低频成分.

图像高斯金字塔实际上是一种图像的尺度空间(分线性和非线性空间,此处仅讨论线性空间),尺度的概念用来模拟观察者距离物体的远近程度,在模拟物体远近的同时,还得考虑物体的粗细程序.

综上,图像的尺度空间是模拟人眼看到物体的远近程度以及模糊程度.

图像高斯金字塔就考虑了这两个方面:① 图像的远近程度;② 图像的模糊程度(理解为粗细更好).

那该怎么模拟图像的远近程度呢?

采样法(上采样,下采样)

比如一幅图像,对于每一行,隔一个像素点取一个像素点,那么最后得到的图像就是原图像的行和列各1/2. 这属于下采样的一种.

那该怎么模拟图像的粗细程序呢

采用高斯核对图像进行平滑处理,因为高斯卷积核是实现尺度变换的唯一线性核.

上面,我们从一个感性的角度去理解高斯金字塔的形成过程,现在我们来理性分析高斯金字塔的创建过程。

高斯金字塔式在Sift算子中提出来的概念,首先高斯金字塔并不是一个金字塔,而是有很多组(Octave)金字塔构成,并且每组金字塔都包含若干层(Interval)。

高斯金字塔构建过程:

  1. 先将原图像扩大一倍之后作为高斯金字塔的第1组第1层,将第1组第1层图像经高斯卷积(其实就是高斯平滑或称高斯滤波)之后作为第1组金字塔的第2层,高斯卷积函数为:

    对于参数σ,在Sift算子中取的是固定值1.6。

  2. 将σ乘以一个比例系数k,等到一个新的平滑因子σ=k*σ,用它来平滑第1组第2层图像,结果图像作为第3层。

  3. 如此这般重复,最后得到L层图像,在同一组中,每一层图像的尺寸都是一样的,只是平滑系数不一样。它们对应的平滑系数分别为:0,σ,kσ,k2σ,k3σ……k^(L-2)σ。

  4. 将第1组倒数第三层图像作比例因子为2的降采样,得到的图像作为第2组的第1层,然后对第2组的第1层图像做平滑因子为σ的高斯平滑,得到第2组的第2层,就像步骤2中一样,如此得到第2组的L层图像,同组内它们的尺寸是一样的,对应的平滑系数分别为:0,σ,kσ,k2σ,k3σ……k^(L-2)σ。但是在尺寸方面第2组是第1组图像的一半。

这样反复执行,就可以得到一共O组,每组L层,共计O*L个图像,这些图像一起就构成了高斯金字塔,结构如下:


在同一组内,不同层图像的尺寸是一样的,后一层图像的高斯平滑因子σ是前一层图像平滑因子的k倍;

在不同组内,后一组第一个图像是前一组倒数第三个图像的二分之一采样,图像大小是前一组的一半;

高斯金字塔图像效果如下,分别是第1组的4层和第2组的4层:


3.3高斯金字塔创建总图


其中


式(1)中,M 为原始图像的行高;N 为原始图像的列宽;O 为图像高斯金字塔的组数.


式(2)中,n 为待提取图像特征的图像数;S 为图像高斯金字塔每组的层数.

注:n所代表的意思可能有人不太理解,这里详细说一下.
(1) 假设高斯金字塔每组有S = 5层,则高斯差分金字塔就有S-1 = 4,
那我们只能在高斯差分金字塔每组的中间2层图像求极值(边界是没有极值的),
所以n = 2

(2) 假设高斯金字塔每组有S = 6层,则高斯差分金字塔就有S-1 = 5,
那我们只能在高斯差分金字塔每组的中间3层图像求极值,所以n = 3

(3) 假设高斯金字塔每组有S = 7层,则高斯差分金字塔就有S-1 = 6,
那我们只能在高斯差分金字塔每组的中间4层图像求极值,所以n = 4

为了方便计算,从0开始记录组数或层数

(3)中,o 为组索引序号,r 为层索引序号,σ (o, r ) 为对应的图像的高斯模糊系数.

σ 0 σ_0 σ0为高斯模糊初始值,David G.Lowe 教授刚开始设置为1.6,考虑相机实际已对图像进行σ=0.5的模糊处理,故实际:

通过式(3),可以计算对应图像金字塔中的高斯模糊系数,如下:

第0组,第0层:

第0组,第1层:

第0组,第2层:

第1组,第0层:

第1组,第1层:

第1组,第2层:


第2组,第0层:

第2组,第1层:

第2组,第2层:


由上述计算,我们知道

① 每一组内,相邻层之间的高斯模糊系统相差 2 1 / n 2^1/n 21/n
② 第0组第0层,第1组第第0层,第2组第0层,…,的高斯模糊系数分别为 σ 0 , 2 σ 0 , 4 σ 0 , . . . σ_0,2σ_0,4σ_0,... σ0,2σ0,4σ0,... ;
③ 下一组的第0层为上一组倒数第3层降采样所得,无须进行高斯模糊操作.

总的过程,如图2所示:

四、尺度空间

图像的尺度空间解决的问题是如何对图像在所有尺度下描述的问题。

在高斯金字塔中一共生成O组L层不同尺度的图像,这两个量合起来(O,L)就构成了高斯金字塔的尺度空间,也就是说以高斯金字塔的组O作为二维坐标系的一个坐标,不同层L作为另一个坐标,则给定的一组坐标(O,L)就可以唯一确定高斯金字塔中的一幅图像。

尺度空间的形象表述:


上图中尺度空间中k前的系数n表示的是第一组图像尺寸是当前组图像尺寸的n倍

五、高斯差分金字塔

创建好图像高斯金字塔后,每一组内的相邻层相减可以得到高斯差分金字塔(DoG, Difference of Gaussian),是后期检测图像极值点的前提,如图2所示:

DOG金字塔的第1组第1层是由高斯金字塔的第1组第2层减第1组第1层得到的。以此类推,逐组逐层生成每一个差分图像,所有差分图像构成差分金字塔。概括为DOG金字塔的第o组第l层图像是有高斯金字塔的第o组第l+1层减第o组第l层得到的。

每一组在层数上,DOG金字塔比高斯金字塔少一层。后续Sift特征点的提取都是在DOG金字塔上进行的。
DOG金字塔的显示效果如下:
下边对这些DOG图像进行归一化,可有很明显的看到差分图像所蕴含的特征,并且有一些特征是在不同模糊程度、不同尺度下都存在的,这些特征正是Sift所要提取的“稳定”特征:

5.1极值点(Key points)的精确定位

阈值化

其中,T = 0.04,可人为设定其值;n为待提取特征的图像数;abs(val)为图像的像素值. 设定像素阈值,为了去除一些噪点或其它一些不稳定像素点.

在高斯差分金字塔中寻找极值点

特征点是由DOG空间的局部极值点组成的。为了寻找DoG函数的极值点,每一个像素点要和它所有的相邻点比较,看其是否比它的图像域和尺度域的相邻点大或者小。

如下图所示:在高斯差分金字塔中寻找极值点,除了考虑x,y方向的点,还要考虑σ 方向的点,所以判断一个像素点是否为极值点,要与周围的26个点进行比较.

注:
① 如果高斯差分金字塔每组有3层,则只能在中间1层图像寻 找极值点,
两端的图像不连续,没有极值点.

② 如果高斯差分金字塔每组有5层,则只能在中间3层图像寻找极值点.

依此类推…

当我们检测到极值点之后,会发现一个问题,高斯差分金字塔是离散的(因为尺度空间和像素点都是离散的),所以找到的极值点不太准确的,很大可能在真正极值点附近,如图4所示,为了找到更高亚像素位置精度的极值点,需利用泰勒展开式.


更正极值点位置

在检测到的极值点处,作三元二阶泰勒展开:

f(x)对x进行求导:

令导数为零

带入f(x),可得

注:
上述求解的结束标志:达到一定的迭代次数.

求解亚像素精度极值点时,当所得解超出离散极值点一定范围舍去,
因为泰勒展开只是在离散点附近能够较好的拟合原函数.

舍去低对比度的点

若|f(x)|<T/n,则舍去点X

去除边缘效应

本质上要去掉DoG局部曲率非常不对称的像素. 一个定义不好的高斯差分算子的极值在横跨边缘的地方有较大的主曲率,而在垂直边缘的方向有较小的主曲率。主曲率通过一个2×2的海森矩阵(Hessian Matrix)H求出,D的主曲率和H的特征值成正比,令α 为较大特征值,β 为较小的特征值.


注:最终得到像素点坐标(x,y)可以不是整数,σ可以不是在高斯金字塔的某一层上.

5.2确定关键点(极值点)方向

1、通过尺度不变性求极值点,需要利用图像的局部特征为给每一个关键点分配一个基准方向,使描述子对图像旋转具有不变性。对于在DOG金字塔中检测出的关键点,采集其所在高斯金字塔图像3σ邻域窗口内像素的梯度和方向分布特征。梯度的模值和方向如下:

2、本算法采用梯度直方图统计法,统计以关键点为原点,一定区域内的图像像素点确定关键点方向。在完成关键点的梯度计算后,使用直方图统计邻域内像素的梯度和方向。梯度直方图将0~360度的方向范围分为36个柱,其中每柱10度。如下图所示,直方图的峰值方向代表了关键点的主方向,方向直方图的峰值则代表了该特征点处邻域梯度的方向,以直方图中最大值作为该关键点的主方向。为了增强匹配的鲁棒性,只保留峰值大于主方向峰值80%的方向作为该关键点的辅方向。

统计以特征点为圆心,以该特征点所在的高斯图像的尺度的1.5倍为半径的圆内的所有的像素的梯度方向及其梯度幅值,并做1.5σ的高斯滤波(高斯加权,离圆心也就是关键点近的幅值所占权重较高).

5.3关键点描述

上述过程,只是找到关键点并确定了其方向,但SIFT算法核心用途在于图像的匹配,我们需要对关键点进行数学层面的特征描述,也就是构建关键点描述符.

1、确定计算描述子所需的图像区域

描述子梯度方向直方图由关键点所在尺度的高斯图像计算产生. 图像区域的半径通过下式(17)计算:

d=4,代表划分4x4个子块

2、将坐标移到关键点方向

关键点所在的半径区域,移至关键点方向,如下图所示

3、生成关键点描述符

将区域划分为4x4的子块,对每一个子块进行8个方向的直方图统计操作,获得每个方向的梯度幅值,总共可以组成128维描述向量。

对于每一个关键点,都拥有位置、尺度以及方向三个信息。为每个关键点建立一个描述符,用一组向量将这个关键点描述出来,使其不随各种变化而改变,比如光照变化、视角变化等等。这个描述子不但包括关键点,也包含关键点周围对其有贡献的像素点,并且描述符应该有较高的独特性,以便于提高特征点正确匹配的概率。

实验表明:描述子采用4x4x8=128维向量表示,效果最优

5.4关键点匹配

1、分别对模板图(参考图,reference image)和实时图(观测图,observation image)建立关键点描述子集合。目标的识别是通过两点集内关键点描述子的比对来完成。具有128维的关键点描述子的相似性度量采用欧式距离。
2、匹配可采取穷举法完成,但所花费的时间太多。所以一般采用kd树的数据结构来完成搜索。搜索的内容是以目标图像的关键点为基准,搜索与目标图像的特征点最邻近的原图像特征点和次邻近的原图像特征点。
Kd树如下如所示,是个平衡二叉树

六、sift代码

import cv2
import numpy as np
import matplotlib.pyplot as plt

#1、读取图像
img=cv2.imread('cat.jpg')
cat=cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)

#2、sift关键点检测
#sift实例化对象
sift=cv2.xfeatures2d.SIFT_create()

# 2.2关键点检测:kp关键点信息包括方向,尺度,位置信息,des是关键点的描述符
kp,des=sift.detectAndCompute(cat,None)

# 2.3在图像上绘制关键点的检测结果
cv2.drawKeypoints(img,kp,img,flags=cv2.DRAW_MATCHES_FLAGS_DRAW_RICH_KEYPOINTS)

#3图像显示
plt.figure(figsize=(8,6),dpi=100)
plt.imshow(img[:,:,::-1]),plt.title('sift')
plt.xticks([]),plt.yticks([])
plt.show()

七、总结

SIFT特征具有稳定性和不变性,在图像处理和计算机视觉领域有着很重要的作用,其本身也是非常复杂的,由于接触SIFT不是很久,对其中的相关知识了解还很不足,经多方查阅参考,写得此文,内容还不够详尽,望多多见谅。以下是SIFT算法的粗略总结。
1、DoG尺度空间的极值检测。
2、删除不稳定的极值点。
3、确定特征点的主方向
4、生成特征点的描述子进行关键点匹配。

参考文章
https://blog.csdn.net/qq_37374643/article/details/88606351
https://zhuanlan.zhihu.com/p/343522892?ivk_sa=1024320u

图像配准SIFT算法原理及二图配准拼接

前言

本篇开始,将进入图像配准领域的研究。
图像拼接主要有SIFT, BRISK, ORB, AKAZE等传统机器学习算法以及SuperPoint等深度学习算法,在后续将一一进行研究和实验。本篇主要来研究SIFT算法的原理和应用。

SIFT算法原理

算法概述

SIFT(Scale-invariant feature transform)又称尺度不变特征转换,此算法由David Lowe在1999年所发表,2004年完善总结。
SIFT主要是用来提取图像中的关键点。相比于其它角点检测算法(如Harris和shi-toms),SIFT算法具有角度和尺度不变性,换句话说就是不容易受到图像平移、旋转、缩放和噪声的影响。

关键点和尺度空间

关键点是指在不同尺度空间的图像下检测出的具有方向信息的局部极值点。
尺度空间理论最早在1962年提出,其主要思想是通过对原始图像进 行尺度变换,获得图像多尺度下的尺度空间表示序列。
SIFT算法就是利用同一幅图像在不同尺度空间的关系来提取关键点。
尺度空间中各尺度图像的模糊程度逐渐变大,能够模拟人在距离目标由近到远时目标在视网膜上的形成过程。

算法步骤

1.通过高斯卷积构建图像金字塔

根据文献《Scale-space theory: A basic tool for analysing structures at different scales》可知,高斯核是唯一可以产生
多尺度空间的核。因此使用高斯函数和原图像进行卷积,具体公式如下图所示:


式中,G(x,y, σ \\sigma σ)为高斯函数,I(x,y)表示原图像。

根据 σ \\sigma σ的不同取值,可以构建出不同尺度空间的图像,这样就形成了一组图像。
之后,再对这些图像进行降采样,这样就形成图像金字塔

上一组图像的底层是由前一组图像的倒数第二层图像隔点采样生成的。

2.构建高斯差分金字塔

创建好图像高斯金字塔后,每一组内的相邻层相减可以得到高斯差分金字塔(DoG, Difference of Gaussian),如下图所示。

由于DOG是通过相邻层相减得到,因此层数会比高斯图像金字塔少一层。

3.关键点定位

得到DOG之后,就可以在正数第二层和倒数第二层的范围中寻找极值点(第一层和最后一层无法和相邻两层进行比较)。
此时将某个点和周围26个点进行比较,比较的示意图如下图所示,图中x为比较的点,这幅图中x首先和相邻的8个点比较,然后和上下两个尺度的9*2=18个点进行比较,总共需要比较26个点。
如果该点符合极小值或极大值,则此点为离散空间中的极值点。

离散空间中的极值点并不是真正的极值点,因此通过离散值插值的方式,可以找到真正的极值点,这一步从公式角度较为复杂,原理大致如下图所示:

4.关键点方向赋值

通过尺度不变性求极值点,可以使其具有缩放不变的性质;为了让其具有图像旋转不变性,需要对每个关键点方向进行赋值。
每一个像素点的梯度方向和幅值计算公式如下:

然而直接计算一个点的方向可能会存在误差,因此选取关键点附近的一块领域,对领域内每一个点的方向进行统计。

将0-360度分成8个方向,每45度为一个方向,形成8个方向的柱状图,峰值代表关键点方向,大于峰值80%的作为辅方向,示意图如下:

5.关键点描述

关键点描述的目的是在关键点计算后,用一组向量将这个关键点描述出来,用来作为目标匹配的依据。

SIFT采用4x4x8共128维向量作为特征点,取特征点周围8x8的像素范围进行梯度方向统计和高斯加权(蓝色圆圈表示加权范围),每4x4的窗口生成8个方向。箭头方向代表了像素梯度方向,箭头长度代表该像素的幅值。每个4x4的窗口形成一个种子点,一个特征点由4个种子点的信息所组成。

SIFT算法实践

下面进入到SIFT的编程实践,OpenCV的提供了非常方便的调用接口。
不同版本的OpenCV接口可能会略有区别,下面使用的OpenCV版本为4.5.4.60。

关键点检测

下面这段程序实现了一幅图片的关键点检测。

import cv2

# 读取图像,转灰度图进行检测
img = cv2.imread('Hall_1.jpg')
img_gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)

# sift实例化对象
sift = cv2.SIFT_create()

# 关键点检测
keypoint = sift.detect(img_gray)

# 关键点信息查看
# print(keypoint)  # [<KeyPoint 000001872E1E2960>, <KeyPoint 000001872E1E2B10>]
original_kp_set = (int(i.pt[0]), int(i.pt[1])) for i in keypoint  # pt查看关键点坐标
print(original_kp_set)

# 在图像上绘制关键点的检测结果
cv2.drawKeypoints(img, keypoint, img, flags=cv2.DRAW_MATCHES_FLAGS_DRAW_RICH_KEYPOINTS)

# 显示图像
cv2.imshow("img", img)
cv2.waitKey()

sift.detect会返回一个KeyPoint对象,该类型有以下这些属性:

  • pt(x,y):关键点的点坐标;
  • size():该关键点邻域直径大小;
  • angle:角度,表示关键点的方向,值为[零,三百六十),负值表示不使用。
  • response:响应强度

运行之后,结果如下图所示:

配准拼接

示例代码

下面是一个两幅图像配准拼接的示例,先放代码[1]:

import time
import cv2
import numpy as np


class Stitcher:
    # 拼接函数
    def stitch(self, images, ratio=0.75, reprojThresh=4.0, showMatches=False):
        # 获取输入图片
        (imageB, imageA) = images
        # 检测A、B图片的SIFT关键特征点,并计算特征描述子
        (kpsA, featuresA) = self.detectAndDescribe(imageA)
        (kpsB, featuresB) = self.detectAndDescribe(imageB)

        # 匹配两张图片的所有特征点,返回匹配结果
        M = self.matchKeypoints(kpsA, kpsB, featuresA, featuresB, ratio, reprojThresh)

        # 如果返回结果为空,没有匹配成功的特征点,退出算法
        if M is None:
            return None

        # 否则,提取匹配结果
        # H是3x3视角变换矩阵
        (matches, H, status) = M
        # 将图片A进行视角变换,result是变换后图片
        result = cv2.warpPerspective(imageA, H, (imageA.shape[1] + imageB.shape[1], imageA.shape[0]))

        # 融合
        for r in range(result.shape[0]):
            left = 0
            for c in range(result.shape[1] // 2):
                if result[r, c].any():  # overlap
                    if left == 0:
                        left = c
                    alpha = (c - left) / (result.shape[1] // 2 - left)
                    result[r, c] = imageB[r, c] * (1 - alpha) + result[r, c] * alpha
                else:
                    result[r, c] = imageB[r, c]

        # 将图片B传入result图片最左端
        #         result[0:imageB.shape[0], 0:imageB.shape[1]] = imageB

        # 检测是否需要显示图片匹配
        if showMatches:
            # 生成匹配图片
            vis = self.drawMatches(imageA, imageB, kpsA, kpsB, matches, status)
            # 返回结果
            return (result, vis)

        # 返回匹配结果
        return result

    def detectAndDescribe(self, image):
        # 将彩色图片转换成灰度图
        gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)

        # 建立SIFT生成器
        descriptor = cv2.SIFT_create()
        # 检测SIFT特征点,并计算描述子
        (kps, features) = descriptor.detectAndCompute(gray, None)

        # 将结果转换成NumPy数组
        kps = np.float32([kp.pt for kp in kps])

        # 返回特征点集,及对应的描述特征
        return kps, features

    def matchKeypoints(self, kpsA, kpsB, featuresA, featuresB, ratio, reprojThresh):
        # 建立暴力匹配器
        matcher = cv2.DescriptorMatcher_create("BruteForce")

        # 使用KNN检测来自A、B图的SIFT特征匹配对,K=2
        rawMatches = matcher.knnMatch(featuresA, featuresB, 2)

        matches = []
        for m in rawMatches:
            # 当最近距离跟次近距离的比值小于ratio值时,保留此匹配对
            if len(m) == 2 and m[0].distance < m[1].distance * ratio:
                # 存储两个点在featuresA, featuresB中的索引值
                matches.append((m[0].trainIdx, m[0].queryIdx))

        # 当筛选后的匹配对大于4时,计算视角变换矩阵
        if len(matches) > 4:
            # 获取匹配对的点坐标
            ptsA = np.float32([kpsA[i] for (_, i) in matches])
            ptsB = np.float32([kpsB[i] for (i, _) in matches])

            # 计算视角变换矩阵
            (H, status) = cv2.findHomography(ptsA, ptsB, cv2.RANSAC, reprojThresh)

            # 返回结果
            return (matches, H, status)

        # 如果匹配对小于4时,返回None
        return None

    def drawMatches(self, imageA, imageB, kpsA, kpsB, matches, status):
        # 初始化可视化图片,将A、B图左右连接到一起
        (hA, wA) = imageA.shape[:2]
        (hB, wB) = imageB.shape[:2]
        vis = np.zeros((max(hA, hB), wA + wB, 3), dtype="uint8")
        vis[0:hA, 0:wA] = imageA
        vis[0:hB, wA:] = imageB

        # 联合遍历,画出匹配对
        for ((trainIdx, queryIdx), s) in zip(matches, status):
            # 当点对匹配成功时,画到可视化图上
            if s == 1:
                # 画出匹配对
                ptA = (int(kpsA[queryIdx][0]), int(kpsA[queryIdx][1]))
                ptB = (int(kpsB[trainIdx][0]) + wA, int(kpsB[trainIdx][1]))
                cv2.line(vis, ptA, ptB, (0, 255, 0), 1)

        # 返回可视化结果
        return vis


if __name__ == '__main__':
    start_time = time.time()
    # 读取拼接图片
    imageA = cv2.imread("Hall_1.jpg")
    imageB = cv2.imread("Hall_2.jpg")

    # 把图片拼接成全景图
    stitcher = Stitcher()
    (result, vis) = stitcher.stitch([imageA, imageB], showMatches=True)
    cv2.imwrite("img1.png", vis)
    cv2.imwrite("img2.png", result)
    end_time = time.time()
    print("共耗时" + str(end_time - start_time))

特征匹配结果:

拼接结果:

代码细节解析

相比于关键点检测的任务,图像配准任务是在前者的基础上加入图像关键点匹配和图像融合的过程。下面从代码运行的角度进行过程分析:

  1. 首先读取两张图片,将彩色图片转换为灰度图
  2. 检测A、B图片的SIFT关键特征点,并计算特征描述子。
    在上面的示例中,使用cv2.SIFT_create().detect来得到关键点对象,这里使用的是cv2.SIFT_create().detectAndCompute这个函数接口,该函数会返回两个值,第一个值是各关键点的坐标,第二个值是关键点描述向量,如原理部分所述,SIFT算法采用128维来描述一个关键点,因此该值的size为(关键点个数,128)。
  3. 设置匹配器,这里采用的匹配器为BruteForce,BruteForce也称暴力匹配算法,即从主串头开始,依次选取和模拟串等长的子串,挨个字符匹配,如果匹配失败,立马检索下一个子串。
  4. 匹配策略采用KNN算法,其中K值取2,匹配时,设置了一个阈值ratio,默认值是0.75,如果两个关键点描述向量的欧式距离之比小于0.75,则匹配成功。
  5. 如果匹配成功的关键点大于4,则计算视角变换矩阵,这里使用了cv2.findHomography这个函数,参数设置使用RANSAC方法,返回变换矩阵H(3行x3列)和状态向量(status)(1表示匹配成功,0表示匹配失败)
  6. 将第二幅图进行视角变换,这里用到这样一个函数cv2.warpPerspective,根据变换矩阵进行仿射变换。单独将变换后的此图拿出来如下图所示:
  7. 最后是图像融合,因为第二幅图已经根据图一进行了仿射变换,因此,只需要将图一部分连接上去即可,问题在于如果两部分是重合的(overleap),则重合的部分亮度会明显增强。因此,这一部分主要采用了一个像素点遍历循环,只有第二幅图空缺的像素点位置被第一幅图进行填充。

    填充完成后,就得到了整幅拼接的图像。

总结

整个算法在图像尺寸不大时,配准拼接速度较快。但是当图像尺寸较大时(几千x几千),速度明显较慢。一方面是Sift特征提取速度还有待提升,其次像素点的匹配是暴力匹配,效率不高;最后是图像融合是直接采用像素点遍历,速度很慢,后续有待改进。

参考

  1. 利用 SIFT 实现图像拼接 https://blog.csdn.net/itnerd/article/details/89157849
  2. OpenCV中KeyPoint类 https://blog.csdn.net/qq_41598072/article/details/99302777
  3. SIFT角点检测算法原理 https://www.bilibili.com/video/BV1kS4y1g7Ge
  4. 全网最详细SIFT算法原理实现 https://blog.csdn.net/weixin_48167570/article/details/123704075
  5. 基于OpenCV全景拼接(Python)https://cloud.tencent.com/developer/article/1178958

以上是关于全网最详细SIFT算法原理实现的主要内容,如果未能解决你的问题,请参考以下文章

Python实现sift算法

python-应用OpenCV和Python进行SIFT算法的实现

python—sift特征提取

图像配准SIFT算法原理及二图配准拼接

图像配准SIFT算法原理及二图配准拼接

图像配准SIFT算法原理及二图配准拼接