SIFT特征
Scale-Invariant Feature Transform尺度不变形特征变换,旨在于从图像中提取若干个特征点。
这些点的特征具有尺度不变性和旋转不变性,因而可以用于匹配不同尺度和旋转方向的图像内容。
动机
物体以图像方式呈现时,往往会呈现较大的尺度变化
在机器视觉中,如何对尺度变化不敏感,是决定一个特征好坏的关键问题。
深度学习时代,通过多层卷积和池化处理,可以得到尺度不变形较高的深度特征;
而在前深度学习时代,如何能够提高特征的尺度不变性?
sift特征提取过程
演示站点:http://weitz.de/sift/index.html?size=large
第一步:构建差分高斯金字塔
* 对一张图像采用不同程度的滤波,实现不同程度的模糊
* 对每一张模糊的图像,连续进行下采样,从而为每张图像都得到不同尺度的表达
import cv2
import numpy as np
import matplotlib.pyplot as plt
img = cv2.imread('../../dataset/lena.png', 0)
img = cv2.resize(img, [256, 256])
plt.imshow(img, cmap='gray')
plt.show()
blur1 = cv2.GaussianBlur(img, (5, 5), 0)
blur2 = cv2.GaussianBlur(img, (5, 5), 5)
plt.subplot(1, 2, 1)
plt.imshow(blur1, cmap='gray')
plt.subplot(1, 2, 2)
plt.imshow(blur2, cmap='gray')
然后,对每个图进行金字塔变换。
采用cv2.pyrDown函数进行下采样(即resize为更小的size)
o11 = cv2.pyrDown(blur1)
o12 = cv2.pyrDown(o11)
plt.subplot(1, 2, 1)
plt.imshow(o11, cmap='gray')
plt.subplot(1, 2, 2)
plt.imshow(o12, cmap='gray')
print(blur1.shape, o11.shape, o12.shape)
# (256, 256) (128, 128) (64, 64)
对于每一个子图像,都与其上一级做差分,得到差的图像。
例如,o12是o11的上级图像,我们需要对这两张图像做差。
尺寸不一致怎么办?先对小图像进行上采样
o12x = cv2.pyrUp(o12)
d12 = cv2.absdiff(o11, o12x)
plt.subplot(1, 4, 1)
plt.imshow(o11, cmap='gray')
plt.subplot(1, 4, 2)
plt.imshow(o12, cmap='gray')
plt.subplot(1, 4, 3)
plt.imshow(o12x, cmap='gray')
plt.subplot(1, 4, 4)
plt.imshow(d12, cmap='gray')
那么对每一张图像都进行一次差分,并组成金字塔的形式,则称为了差分高斯金字塔
总结 difference of gaussian, DoG计算过程:
* 对图片采用不同的高斯方差进行滤波,从而得到不同模糊程度的图像
* 对每一张图像,建立一个octave
- 对图像进行连续多层的下采样
* 对于每个octave,都进行上下之间的做差,得到dog
第二步,寻找关键点
极值点为:在某个区域里,具有最大或者最小值的点。
这里的极值点,不仅仅是同一层级图像中的极值点,也包括了不同尺度图像相同区域中的极值点。
如果我们把一个octvate视为一个tensor,那么我们就需要找出在所有$3*3*3$区域内的极值点。
即对于任意一个点来说,如果他比相临的26个值都大或者小,就认为是一个潜在的极值点。
如果只对单层的图像求取极值点,那么就是获得边缘。
对于上下两层的图像求取极值点,则是说明,
**无论放大还是缩小图像,该极值点都会被消除,说明这是一个关键的点**
然而,除了关键点之外,噪声也会作为局部区域的极值存在。
我们将这些极值点(由噪声引起的)视为不稳定的极值点。
如何去除这些噪声?
目前为止,我们已经获取了不同尺度上的关键点。
可以认为我们已经具有了具有尺度不变性的特征。
第三步:获取旋转不变性
首先,对任意关键点,先旋转图像,让关键点的梯度方向和平面直角坐标系的y轴重合 ;
其次,以关键点为圆心,获得16尺寸大小的区域;
在该区域内,划分成4的格子,并对每个像素的梯度方向进行统计,共8个方向;
将每个子区域中的梯度带权叠加,形成一个8维向量;
共16个子区域,因此特征总长度为8\*16=128;
这128维的向量,就是该关键点的sift特征。
与CNN相比:看CNN的训练数据,这种方式的尺度不变性通常更加好(因为cnn依赖于数据集的训练,如果数据集中全都是大尺度图片,则训练出来的对小尺度图片提取特征不好)
sift算子的opencv实现
img = cv2.imread("../../dataset/lena.png", 0)
# plt.imshow(img)
sift = cv2.SIFT_create()
key_point = sift.detect(img, None)
print(len(key_point))# 1083
img=cv2.drawKeypoints(img,key_point,img)
plt.imshow(img)
SIFT算子的匹配和应用
import cv2
import matplotlib.pyplot as plt
img_a = cv2.imread("../../dataset/box_in_scene.png", 0)
img_b = cv2.imread("../../dataset/box.png", 0)
plt.subplot(1,2,1)
plt.imshow(img_a)
plt.subplot(1,2,2)
plt.imshow(img_b)
sift = cv2.SIFT_create()
kp_a, des_a = sift.detectAndCompute(img_a, None)
print(len(kp_a), len(des_a), des_a[0].shape) # 969 969 (128,)
kp_b, des_b = sift.detectAndCompute(img_b, None)
print(len(kp_b), len(des_b), des_b[0].shape) # 604 604 (128,)
# 暴力匹配
bf = cv2.BFMatcher(crossCheck=True)
matches = bf.match(des_a, des_b)
matches = sorted(matches, key=lambda x: x.distance)
# 标出排在最前面的10个关键点(排在前面表示距离越短,相似度越高)
result = cv2.drawMatches(img_a, kp_a, img_b, kp_b, matches[:10], None, flags=2)
plt.imshow(result)
总结:遇到图像匹配等问题时,可以先考虑sift算子,可以避免深度学习繁琐的训练过程!
【动手学计算机视觉】第七讲:传统目标检测之SIFT特征 - 知乎
surf特征
特征提取算法
对图像来说,提取特征的基本要求在于三个要点:
- 尺度不变性
- 旋转不变性
- 平移不变性
那么对于这个特征,应当包含三个方面的内容:
- 位置信息
- 方向信息
- 特征描述
对于sift特征来说:
- 位置信息表征了图像中的关键匹配点位置
- 方向信息则为特征添加了旋转不变性
- 特征描述则是为特征添加了尺度不变性:因为关键点是从不同尺度中图像获取,并且从对应的16*16的对应尺度空间上获取特征。
surf对于sift的改进
sift特征提取算法中,关键点的提取和筛选需要耗费大量的计算资源,surf则是对关键点的提取方法提出了改进,从而获得更加快速和稳健的特征表达。
surf特征的基本步骤
surf特征的基本步骤
基本原理:
-
利用hessian矩阵,特征点检测过程
-
通过不同尺寸的box filter来构建金字塔
-
使用haar小波变换来确定特征点的方式
基本概念
- 积分图
图像积分就是对一定区域内的像素求和。
积分图中每个点的值,为该点至(0, 0)点的像素值之和。
为了节省计算资源,积分图可以用增量的方式计算。
即每个像素点不需要计算重合区域。
积分图的好处在于:积分图中任意矩形内的像素值之和,只需要确定四个角的像素即可(两个也)
- 盒子滤波
对固定大小的矩形区域,计算该区域内所有像素的平均值,并作为中心像素的值。
高斯滤波需要计算每个像素值的加权和,但结合积分图和盒子滤波,就能通过查表运算来完成对不同尺度图像的滤波。
- hessian矩阵构建
对不同尺度的图像构建hessian变换
hessian变换是指,通过三种不同的滤波模板,对图像进行滤波
而后以行列式的方式将其融合,得到最终的hessian矩阵图像
- haar小波响应
在surf算法中,haar小波响应运算用于计算图像区域的特征向量。
对每个检测到得到特征点,周围4×4的区域将被用于计算该特征点的特征向量。
例如,对于一个20×20的区域,首先将其划分为4×4的子块
1 2 3 4
2 3 4 5
3 4 5 6
4 5 6 7
而后,按照行列分别求和,得到
sum_rows: [10 14 18 22]
sum_cols: [10 14 18 22]
可根据差别计算小波响应值
H1 = sum_rows[0] + sum_rows[2] - sum_rows[1] - sum_rows[3] = -4
H2 = sum_cols[0] + sum_cols[2] - sum_cols[1] - sum_cols[3] = -4
H3 = sum_rows[0] + sum_cols[2] - sum_rows[1] - sum_cols[3]
H4 = sum_cols[0] + sum_rows[2] - sum_cols[1] - sum_rows[3]
基本步骤
- 获取空间金字塔
先对图像采用不同尺度的高斯模糊,而后采用不同尺度的hessian变换获取图像的差分金字塔
其中高斯模糊的过程用box filter获取,并通过积分图进行加速。
只改变尺度不改变图像的shape,得到的不同尺度图像的尺寸是一致的。
载sift中,同一个octave中的图像是逐渐缩小的,但surf中,同一个octave的图像是一样大的。
在这个过程中,积分图用来加速
- 特征点检测
同sift一样,特征点检测依然在同一个octave中的上下三层矩阵进行
选取27个相临像素后,如果当前中心点是一个极大值,那么就保留,否则去除
不同点在于:
-
sift使用差分高斯图像作为输入
-
surf使用hessian变换作为输入
相比sift,此处surf有三个主要优势:
1. 计算效率更高
2. 方向选择更加准确
3. 更加鲁棒
同样地,surf也会产生产生较多的噪声点,因此同样需要筛选。
- 确定主方向
为了保证特征点具有旋转不变形,也需要为每个特征点分配一个主要方向。
具体上,以特征点为中心,六倍标准差为半径的圆形区域,对图像进行haar小波响应运算。
Harr特征值反应了图像灰度变化的情况,那么这个主方向就是描述那些灰度变化特别剧烈的区域方向。
以特征点为中心,张角为π/3的扇形滑动,计算窗口内的Harr小波响应值dx、dy的累加
具体上:
1. 确定一个尺度,并确定关键点的方向区间。
2. 将关键点周围的区域划分为多个子区域,例如4x4的矩形子区域。
3. 在每个子区域内,通过Haar小波响应计算dx、dy的累加值,并通过高斯加权对每个子区域内的像素进行加权。
4. 根据计算得到的所有累加值,统计不同方向的响应值,并计算加权和,以确定关键点的主方向。
- surf特征描述
生成特征描述需要两个步骤:
- 旋转方向
- 描述特征
对每个特征点,先获取20×20的区域,然后进行旋转
在图像上使用水平和垂直的haar模板求的响应,然后根据主方向旋转dx和dy与主方向保持一致
而后,将图像划分16个区域,分别计算haar响应。
每个haar响应为4个数值,因此surf算子得到的特征共有64个特征。
surf特征的opencv实现
需要安装低版本的opencv
import cv2 as cv
import numpy as np
import argparse
img1 = cv.imread("../../dataset/lena.png", cv.IMREAD_GRAYSCALE)
img2 = cv.imread("../../dataset/lena.png", cv.IMREAD_GRAYSCALE)
#-- Step 1: Detect the keypoints using SURF Detector, compute the descriptors
minHessian = 400
detector = cv.xfeatures2d_SURF.create(hessianThreshold=minHessian)
keypoints1, descriptors1 = detector.detectAndCompute(img1, None)
keypoints2, descriptors2 = detector.detectAndCompute(img2, None)
#-- Step 2: Matching descriptor vectors with a brute force matcher
# Since SURF is a floating-point descriptor NORM_L2 is used
matcher = cv.DescriptorMatcher_create(cv.DescriptorMatcher_BRUTEFORCE)
matches = matcher.match(descriptors1, descriptors2)
#-- Draw matches
img_matches = np.empty((max(img1.shape[0], img2.shape[0]), img1.shape[1]+img2.shape[1], 3), dtype=np.uint8)
cv.drawMatches(img1, keypoints1, img2, keypoints2, matches, img_matches)
#-- Show detected matches
cv.imshow('Matches', img_matches)
cv.waitKey()
sift和surf优劣:
从尺度、旋转和平移不变性和速度方面评价。
尺度:sift好
旋转:surf好
平移:差不多
速度:surf比sift快3倍
ORB特征
ORB: Oriented FAST Rotated Brief
FAST算法 + brief算法
fast算法
什么是关键点?
只要与周围足够多的像素相差较大,即可能为关键点
基本步骤:
1. 海选:当一个像素和周围大部分相随都不同,则认为是一个候选点。
2. 精选:将聚集在一起的像素选出最不同的那个,作为该小区域的关键点
海选步骤:
对图像中的任意点x(i,j), 像素值为Ix.
以r为圆心,半径为3,确定一个圆,圆的边上,共有16个像素点,记为{Ix1,...,Ix16}
进而,确定一个阈值t,如果存在n个点满足Ix−Ixi>t或Ix−Ixi<−t,则认为是一个候选点。
通常n选择9,11,12等。
先与上下左右四个像素作差,如果超过3个满足条件再与周围16个作差比较(节省时间)
当然,fast算法按照上述步骤,显然需要大量计算。因此实际中,会选择一种快速算法如下
对比上下左右四个点,如果四个点有3个满足条件,则继续对该点进行16像素检测;
否则,认为该点不是一个关键点。
精选步骤:
海选步骤中会存在很大的问题: 关键点太多
因此,利用极大值抑制算法来实现精选
具体上,
-
每个点计算一个和周围16个像素之间的差分和为V
-
在一定范围内的所有点,只保留具有最大值v的点
由上述过程筛选的特征点存在什么缺点?
-
单一尺度
-
缺少方向
如何解决尺度不变性问题?
图像金字塔
构建图像金字塔,先模糊,后降采样,对每层金字塔做特征点检测,所有特征点集合作为图像的特征点
Brief算法--特征点的描述
brief算法的核心思想:将特征转化为一个二进制的表达。
brief算法的基本作用:描述特征点周围的像素变化
基本步骤
-
高斯滤波平滑
-
以特征点为中心,选取S大小的窗口
2.1 随机选取窗口内的一对点,比较二者像素的大小,进行如下二进制赋值
如果第一个像素大于第二个像素,则为1 如果第二个像素小于第一个像素,则为0
2.2 重复上述过程,选择n个点进行描述比对,即可得到N个二进制的描述
其中,比较重要的是如何选取像素对。
选择多少对,就有多个个二进制数,也就是描述特征的维度。
通常,特征是512,256,128,64维等等(自己选择,与前两个不同:sift128维,surf64维)
biref的不足及改进
算法虽然在速度和存储上优势明显,但是不具有旋转不变性、对噪声不够鲁棒等问题。
对噪声问题上,可以对图像进行平滑滤波;
或者是,在随机取点的时候,不再取单个点,而是取一个区域进行计算。从而可以克服噪声问题。
steer brief 结合fast中求取的方向,来旋转图像后提取特征。考虑到效率,采用先选择随机坐标,然后进行特征提取的方式进行。可以获得一些旋转不变性。
rBrief 利用机器学习的方法选择数据点。可以获得更高的性能。
关于特征的匹配
既然是二进制特征,如何评价两个特征是否相似?
汉明距离:统计两个二进制特征中不同的比特位数量。
应用:
import cv2
import matplotlib.pyplot as plt
img1 = cv2.imread('E:/notebook/1.jpg', 0)
img2 = cv2.imread('E:/notebook/2.png', 0)
# 参考图片太大了(太过于关注细小的细节,把图片放大)
img2_ = cv2.resize(img2, (100, 200))
plt.subplot(1, 3, 1)
plt.imshow(img1, cmap='gray')
plt.subplot(1, 3, 2)
plt.imshow(img2, cmap='gray')
plt.subplot(1, 3, 3)
plt.imshow(img2_, cmap='gray')
plt.show()
orb = cv2.ORB_create()
kp1, des1 = orb.detectAndCompute(img1, None)
kp2, des2 = orb.detectAndCompute(img2_, None)
# img_kp1 = cv2.drawKeypoints(img1, kp1, None, color=(0,255,0), flags=cv2.DRAW_MATCHES_FLAGS_DRAW_RICH_KEYPOINTS)
# 显示位置和方向(且大小不同的○)
img_kp1 = cv2.drawKeypoints(img1, kp1, None, color=(0,255,0), flags=0)
img_kp2 = cv2.drawKeypoints(img2_, kp2, None, color=(0,255,0), flags=0)
plt.subplot(1, 2, 1)
plt.imshow(img_kp1, cmap='gray')
plt.subplot(1, 2, 2)
plt.imshow(img_kp2, cmap='gray')
plt.show()
bf = cv2.BFMatcher_create(cv2.NORM_HAMMING, crossCheck=True)
bf = cv2.BFMatcher_create(crossCheck=True)
matches = bf.match(des1, des2)
matchImg = cv2.drawMatches(img1, kp1, img2_, kp2, matches, None)
plt.imshow(matchImg)
plt.show()