恒定尺度特征转换
一、尺度空间定义
在一定范围内,无论物体是大还是小,人眼都可以分辨出来,但是计算机则不然,很难将不同大小的同一物体识别出来,所以要让机器能对物体在不同尺度下有一个统一的认知,也就是考虑图像在不同尺度下都存在的特点
尺度空间的获取通常使用高斯模糊来实现
L(x,y,σ)=G(x,y,σ)∗I(x,y),其中G是高斯函数
G(x,y,σ)=2πσ21e2σ2x2+y2,σ标准差,和图像变换幅度成正比,越大图像就越模糊。

1. 多分辨率金字塔

2. 高斯差分金字塔(DOG)

① DOG定义公式
D(x,y,σ)=[G(x,y,kσ)−G(x,y,σ)]∗I(x,y)=L(x,y,kσ)−L(x,y,σ),k不等于1,表示不同参数的高斯滤波
② DOG极值检测
为了寻找尺度空间内部的极值点,每个像素点都要和其图像域(统一尺度空间)和尺度域(相邻的尺度空间)的所有相邻点进行比较,当其大于(或小于)所有相邻点时,该点就是极值点。如下图所示:中间层的监测点要和其所在图像层的 D8 邻域内的 8 个像素点、相邻上下层的 3 * 3 邻域各 9 各像素点进行比较

③ 关键点的精确定位
这些候选点是DOG空间的局部极值点,而且这些极值点均为离散点,精确定位极值点的一种方法是:对尺度空间的DoG函数进行曲线拟合,计算其极值点,从而实现关键点的精确定位

D(Δx,Δy,Δσ)=D(x,y,σ)+[x∂Dy∂Dσ∂D]⎣⎡ΔxΔyΔσ⎦⎤+21[ΔxΔyΔσ]⎣⎡∂x2∂2D∂y∂x∂2D∂σ∂x∂2D∂x∂y∂2D∂y2∂2D∂σ∂y∂2D∂x∂σ∂2D∂y∂σ∂2D∂σ2∂2D⎦⎤⎣⎡ΔxΔyΔσ⎦⎤D(x)=D+∂x∂DTΔx+21ΔxT∂x2∂2DTΔxΔx=−∂x2∂2D−1∂x∂D(x)
3. 消除边界响应
① Hessian矩阵
Hessian矩阵:
H(x,y)=[Dxx(x,y)Dxy(x,y)Dxy(x,y)Dyy(x,y)]Tr(H)=Dxx+Dyy=α+βDet(H)=DxxDyy−(Dxy)2=αβ 令 α=λmax 为最大的特征值, β=λmin 为最小的特征值 Det(H)Tr(H)2=αβ(α+β)2=γ(γ+1)2
Lowe在论文中给出的 γ=10 ,也就是说对于主曲率比值大于10的特征点将被删除。
② 特征点的主方向
每个点 L(x, y) 的梯度的模m (x, y) 以及方向 θ(x,y) :
m(x,y)=[L(x+1,y)−L(x−1,y)]2+[L(x,y+1)−L(x,y−1)]2θ(x,y)=arctanL(x+1,y)−L(x−1,y)L(x,y+1)−L(x,y−1)
每个特征点可以得到三个信息(x,y,σ,θ),也就是位置、尺度和方向。具有多个方向的关键点可以被复制为多份,然后将方向值分别赋给复制后的特征点,一个特征点就产生了多个坐标、尺度相等,但是方向不同的特征点
③ 生成特征描述
在完成关键点的梯度计算之后,使用直方图统计邻域内的梯度和方向。

为了保证特征矢量的旋转不变性,要以特征点为中心,在附近邻域内将坐标轴旋转 θ 角度,也即是将坐标轴旋转为特征点的主方向

[x′y′]=[cosθsinθ−sinθcosθ][xy]
旋转之后的主方向为中心,取大小为 8 * 8的仇口,求每个像素的梯度幅值和方向,箭头方向代表梯度方向,长度代表梯度幅值,然后利用高斯窗口对其进行加权运算,最后在每个 4 * 4的小块上绘制 8 个方向的梯度直方图,计算每个梯度的累加值,即可形成一个种子点,即每个特征由4个种子点组成,每个种子点都有 8 个方向的向量信息

建议对每个关键点都是用 4 * 4 一共 16 个种子来描述,这样的一个关键点就会产生 128 维的 SIFT 特征向量

二、OpenCV SIFT函数
import cv2
import numpy as np
def cv_show(name, image):
cv2.imshow(name, image)
cv2.waitKey(0)
cv2.destroyAllWindows()
img = cv2.imread('Lena.png')
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
cv_show('L', img)
cv2.__version__
'4.5.5'
得到特征点
sift = cv2.SIFT_create()
kp = sift.detect(gray, None)
result = cv2.drawKeypoints(gray, kp, gray)
res = np.hstack([img, result])
cv_show('KeyFeatures', res)

计算特征
kp, des = sift.compute(gray, kp)
print(np.array(kp).shape)
(1100,)
des.shape
(1100, 128)
des[0]
array([ 0., 1., 0., 1., 58., 153., 0., 0., 29., 0., 0.,
1., 15., 69., 2., 24., 42., 0., 0., 0., 0., 0.,
0., 36., 0., 0., 0., 0., 0., 0., 0., 0., 10.,
34., 0., 0., 42., 153., 0., 0., 90., 2., 0., 0.,
54., 153., 9., 19., 153., 17., 0., 0., 1., 13., 5.,
47., 16., 3., 0., 0., 0., 0., 0., 2., 53., 153.,
0., 0., 9., 48., 0., 0., 24., 16., 0., 0., 44.,
153., 27., 16., 153., 13., 0., 0., 3., 54., 34., 68.,
31., 5., 1., 4., 4., 0., 0., 3., 74., 153., 0.,
0., 1., 3., 1., 0., 24., 114., 2., 0., 9., 47.,
11., 3., 23., 3., 0., 1., 4., 23., 26., 49., 6.,
0., 0., 10., 14., 0., 0., 6.], dtype=float32)