局部图像描述子
本章将介绍两种局部图像描述子:Harris与SIFT。介绍完二者之后还会有一个实验。
Harris角点检测器
首先我们看到这一小节的名字的时候,可能会疑惑角点是什么?下面是其定义:
- 角点是图像中具有显著角度变化的位置或特征点
说人话就是图像中那些看起来像是物体边缘相交或拐角的地方。这些点有助于进行下一步的处理,比方说确定物体边缘之类的任务。
由于opencv已经提供了封装好的模块以供使用。因此我们此处只简单介绍其原理,代码方面使用opencv来测试,就不跟着书上走了。
原理
我们先口头描述下要怎么做:
- 利用矩形窗在图像上移动,若窗内包含有角点,则窗口向各个方向移动时,窗内的灰度值都会发生变化。从而达到检测图像角点的目的。如果像素周围显示存在多于一个方向的边,我们认为该点为兴趣点。该点就称为角点。
更具体的原理的话可以参见下面这篇文章,我自认为不可能讲的比这位作者更好:Harris特征点检测
代码
import cv2
import numpy as np
import matplotlib.pyplot as plt
# 读取输入图像
img = cv2.imread('img.png', 0)
# 执行Harris角点检测
blockSize = 2 # 角点检测中考虑的邻域大小
ksize = 3 # Sobel算子的孔径大小
k = 0.04 # Harris角点检测方程中的参数
threshold = 0.01 # 角点标记的阈值
dst = cv2.cornerHarris(img, blockSize, ksize, k)
# 标记角点
dst = cv2.dilate(dst, None)
corners = np.argwhere(dst > threshold * dst.max()) # 获取角点的坐标
# 将OpenCV图像转换为适用于plt.imshow()的格式
img_rgb = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
# 显示结果
plt.imshow(img_rgb)
plt.title('Harris Corners')
plt.axis('off')
# 在图像上绘制红色的角点
for corner in corners:
x, y = corner[1], corner[0] # OpenCV坐标与plt.imshow()坐标的顺序不同
plt.plot(x, y, 'ro', markersize=3)
plt.show()
运行结果:
点比较少是因为我们设置的阈值比较高,修改阈值可以得到更多的角点(下图为threshold = 0.001 ):
Harris角点检测算法优点:
- 旋转不变性,旋转转过一定角度但是其形状保持不变(特征值保持不变)。
- 对于图像灰度的仿射变化具有部分的不变性,但是由于仅仅使用了图像的一介导数,对于图像灰度平移变化不变;对于图像灰度尺度变化不变。
既然说具有旋转不变性,那么让我们来测试一下:
可以看到检测的角点基本没有变化,这应该是因为这个算子是通过计算图像中每个像素点的自相关矩阵,然后通过特征值分析来确定角点。然而自相关矩阵的特征值表示了局部图像区域的亮度变化情况。由于旋转操作不会改变图像的亮度变化情况,因此即使图像被旋转了,自相关矩阵的特征值仍然保持不变。
Harris角点检测算法缺点:
3. 它对尺度很敏感,不具备几何尺度不变性。
4. 提取的角点是像素级的。
这个没必要测试了,我们前面的参数有规定核的大小,缩放图像等于改变核的大小,因此肯定会对尺度敏感。
SIFT
描绘子
SITF中文名称为尺度不变特征变换。
SIFT描述子的主要优点是具有较好的尺度不变性(这就要比前面的这个描述子要好了)和旋转不变性,且对光照变化和噪声具有一定的鲁棒性。
SIFT的主要计算步骤如下:
- 尺度空间构建:使用高斯滤波器构建尺度空间金字塔,得到不同尺度的图像。
- 关键点检测:在尺度空间中检测稳定的关键点,通常使用高斯差分金字塔和极值点检测方法。
- 方向分配:对于每个关键点,计算其主导方向,以便后续的旋转不变性。
- 局部特征计算:以关键点为中心,将其周围划分为小的子区域(例如16x16或者4x4的小区域),计算每个子区域内的梯度方向和大小。
- 特征描述:将局部特征组织成一个向量表示,通常使用直方图统计梯度方向的分布。向量的维度可以根据需求设定。
- 描述子归一化:对特征向量进行归一化,使其对光照变化具有一定的鲁棒性。
需要特别说明的是SIFT特征是使用高斯差分函数来定位兴趣点的:
D
(
x
,
σ
)
=
[
G
κ
σ
(
x
)
−
G
σ
(
x
)
]
∗
I
(
x
)
=
[
G
κ
σ
−
G
σ
]
∗
I
=
I
κ
σ
−
I
σ
D(\mathbf{x},\sigma)=[G_{\kappa\sigma}(\mathbf{x})-G_{\sigma}(\mathbf{x})]*I(\mathbf{x})=[G_{\kappa\sigma}-G_{\sigma}]*I=I_{\kappa\sigma}-I_{\sigma}
D(x,σ)=[Gκσ(x)−Gσ(x)]∗I(x)=[Gκσ−Gσ]∗I=Iκσ−Iσ
其中, G σ G_{\sigma} Gσ 是上一章中介绍的二维高斯核, I σ I_{\sigma} Iσ 是使用 G σ G_{\sigma} Gσ模糊的灰度图像, κ \kappa κ是决定相差尺度的常数。兴趣点是在图像位置和尺度变化下 D ( x , σ ) D(\mathbf{x},\sigma) D(x,σ)的最大值和最小值点。这些候选位置点通过滤波去除不稳定点。基于一些准则,比如认为低对比度和位于边上的点不是兴趣点,我们可以去除一些候选兴趣点。
上面讨论的兴趣点(关键点)位置描述子给出了兴趣点的位置和尺度信息。为了实现旋转不变性,基于每个点周围图像梯度的方向和大小,SIFT 描述子又引入了参考方向。SIFT 描述子使用主方向描述参考方向。主方向使用方向直方图(以大小为权重)来度量。
代码
import cv2
import matplotlib.pyplot as plt
def sift_demo():
# 读取图片
img = cv2.imread("img.jpg")
# 转换为灰度图
img = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# 创建sift对象
sift = cv2.xfeatures2d.SIFT_create()
# 检测关键点
kp = sift.detect(img, None)
# 计算描述符
kp, des = sift.compute(img, kp)
# 可视化,由于是远程,所以用matplotlib
img = cv2.drawKeypoints(img, kp, img)
plt.imshow(img)
plt.show()
if __name__ == "__main__":
sift_demo()
从这张图可能看不出来,看一下原始的图片
SIFT算法的实质是在不同的尺度空间上查找关键点(特征点),并计算出关键点的方向。SIFT所查找到的关键点是一些十分突出,不会因光照,仿射变换和噪音等因素而变化的点,如角点、边缘点、暗区的亮点及亮区的暗点等。
匹配
前文提到,SIFT具有一定的不变性,那么用它来做不同视角的匹配自然是极好的。
直接上代码:
import cv2
import matplotlib.pyplot as plt
def sift_kp(image):
gray_image = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
sift = cv2.xfeatures2d.SIFT_create()
kp, des = sift.detectAndCompute(gray_image, None)
img = cv2.drawKeypoints(image, kp, image, flags=cv2.DRAW_MATCHES_FLAGS_DRAW_RICH_KEYPOINTS)
return img, kp, des
def sift_match(img1, img2):
img1, kp1, des1 = sift_kp(img1)
img2, kp2, des2 = sift_kp(img2)
bf = cv2.BFMatcher()
matches = bf.knnMatch(des1, des2, k=2)
good = []
for m, n in matches:
if m.distance < 0.75 * n.distance:
good.append([m])
img3 = cv2.drawMatchesKnn(img1, kp1, img2, kp2, good, None, flags=2)
plt.imshow(img3)
plt.show()
img1 = cv2.imread('img.png')
img2 = cv2.imread('img.png')
# 转为RGB
img1 = cv2.cvtColor(img1, cv2.COLOR_BGR2RGB)
img2 = cv2.cvtColor(img2, cv2.COLOR_BGR2RGB)
# img2旋转90度
img2 = cv2.rotate(img2, cv2.ROTATE_90_CLOCKWISE)
sift_match(img1, img2)
结果分析:可以看到每个点都匹配正确了,但是这是为什么呢?我们可以看到sift_kp(image)函数返回了三个变量,img不必多说,另外两个变量分别是点的位置、点的特征。其中点的特征包含了特定区域的信息(前面有提到过)。也正是因为这个,我们才可以在sift_match(img1, img2)中去匹配点,进而得到最后的结果。
实验
课本上原本是以一个实验结束,可惜接口已经关闭再加上最近比较忙,因此实验暂时搁置,如果后面遇到了合适的数据集再来补全。