查看全课大纲(第 16 / 93 节)
- 1.计算机视觉概述
- 2.计算机视觉应用场景
- 3.困难与挑战
- 4.开源库介绍
- 5.图像显示与存储
- 6.CLAHE
- 7.腐蚀膨胀与开闭运算
- 8.滤波与边缘填充
- 9.各种滤波
- 10.图像金字塔
- 11.傅里叶变换
- 12.短时傅里叶变换与小波变换
- 13.颜色特征与颜色直方图
- 14.几何特征_边缘
- 15.几何特征-角点与斑点
- 16.局部特征-SIFT特征描述子
- 17.局部特征-SURF特征描述子
- 18.局部特征-ORB特征描述子
- 19.其他特征描述子
- 20.传统图像分割技术
- 21.GraphCut
- 22.GrabCut
- 23.人脸检测的传统方法
- 24.行人检测的传统方法HOG+SVM
- 25.行人检测DPM
- 26.人工神经网络的架构
- 27.目标函数与梯度下降
- 28.误差反向传播
- 29.可手算的神经网络
- 30.改进目标函数与克服梯度消散
- 31.神经网络之传统神经网络与深度网络的差异
- 32.各种梯度下降算法
- 33.如何选择合适的梯度下降算法
- 34.避免过适应
- 35.CNN的架构
- 36.CNN的误差反向传播_卷积层
- 37.CNN的误差反向传播_池化层
- 38.分类所用的数据集
- 39.Alexnet详解
- 40.VGG详解
- 41.GoogLeNet_v1
- 42.BatchNormalization
- 43.GoogLeNet_v3v4
- 44.ResNet
- 45.ResNeXt
- 46.CNN设计准则
- 47.图像检索与推土机距离
- 48.基于颜色相似性
- 49.基于形状相似性
- 50.基于纹理相似性
- 51.基于局部特征相似性
- 52.DL的二值哈希编码
- 53.高维空间中检索KDTree
- 54.高维空间中检索LSH
- 55.目标识别任务综述
- 56.RCNN
- 57.SPP-Net
- 58.Fast R-CNN
- 59.Faster R-CNN
- 60.R-CNN系列模型回顾
- 61.R-FCN的设计思路
- 62.R-FCN的实现
- 63.YOLOv1
- 64.YOLOv2_9000
- 65.YOLOv3
- 66.图像分割应用场景与数据集
- 67.上采样的实现方式
- 68.FCN网络架构的由来
- 69.FCN构建细节
- 70.DeepLabv1
- 71.CRF
- 72.DeepLabv2
- 73.DeepLabv3
- 74.特定的医学影像分割任务
- 75.U-Net
- 76.3D U-Net
- 77.V-Net
- 78.DenseNet
- 79.病理切片分析任务及数据集
- 80.病理切片分析
- 81.图说任务及其传统处理方法
- 82.深度语言模型与RNN
- 83.LSTM及其变种
- 84.NIC模型
- 85.SAT模型
- 86.图说的性能指标
- 87.判别模型与生成模型
- 88.KL散度与JS散度
- 89.VAE
- 90.GAN的基本原理
- 91.DCGAN与CGAN
- 92.Wasserstein GAN
- 93.SRGAN
局部特征-SIFT特征描述子
小象实战讲义 · 基于深度学习的计算机视觉
在计算机视觉中,如何让计算机在不同角度、光照、尺度下识别出同一个物体,是一个核心挑战。本节将介绍一种经典的局部特征描述子——SIFT(Scale-Invariant Feature Transform,尺度不变特征变换)。它通过寻找图像中稳定的关键点,并生成高维的特征向量来描述这些点,从而实现对图像的旋转、缩放、亮度变化等具有不变性的匹配。学完本节,你将理解SIFT特征的基本原理、构建步骤及其优缺点。
💡 核心导读
- 局部特征与不变性:理解为何需要寻找对旋转、缩放、亮度变化不敏感的局部特征点。
- SIFT核心步骤:掌握从构建高斯金字塔、计算DoG空间、寻找极值点,到生成128维描述子的完整流程。
- 尺度空间与关键点:学习如何在不同尺度下寻找稳定的斑点(Blob)作为关键点。
- 方向估计与描述子生成:了解如何通过梯度直方图确定关键点主方向,并生成旋转不变的128维特征向量。
- SIFT的优缺点:认识SIFT特征强大的不变性与独特性,同时也了解其计算复杂度高的缺点。
什么是局部特征与SIFT?
局部特征是指图像中一些稳定的、具有区分性的点(或区域),即使图像发生旋转、缩放、亮度变化甚至部分遮挡,这些点及其周围的特征信息也能在很大程度上保持不变。理想的特征描述子应具备良好的不变性(抗几何与光度变化)、独特性(不同点特征差异大)和充足的数量(少量物体也能产生大量特征点)。
SIFT特征描述子正是基于尺度空间不变性而设计的。它的特点可以概括为:
- 良好的不变性:对旋转、尺度缩放、平移、亮度变化、噪声等具有鲁棒性。
- 独特性好:信息量丰富,适合在海量特征库中进行快速准确的匹配。
- 数量充足:即使是少数物体也能产生大量SIFT特征点。
- 计算效率:需要说明的是,原始的SIFT算法计算量较大,其“计算快”的特点通常需要借助硬件加速才能达到实时性。在实际应用中,它比后续的SURF、ORB等特征要慢。
SIFT特征计算的基本原理与步骤
SIFT特征计算的核心思想是:在尺度空间中寻找稳定的极值点作为关键点,然后计算该点邻域内的梯度方向直方图,形成特征描述子。
其计算主要分为四个步骤:
- 在DoG尺度空间中获取极值点(关键点)。
- 对关键点进行精细化处理(位置插值、剔除低对比度点、去除边缘点)。
- 关键点的方向估计。
- 关键点描述子的生成。
步骤一:构建尺度空间与DoG
从LoG到DoG
最理想的尺度空间特征检测器是高斯拉普拉斯(Laplacian of Gaussian, LoG),其极值点对应图像中的斑点(Blob),稳定性好。但LoG的计算量非常大。SIFT作者发现,高斯差分(Difference of Gaussians, DoG) 可以很好地近似LoG,且计算上只需简单的图像减法。
直观理解:对同一图像用两个不同标准差( 和 )的高斯核进行模糊,得到两幅模糊程度不同的图像,两者相减得到的是一个带通响应,它近似于该尺度下的 LoG。LoG 的零值对应边缘,极值对应斑点。因此,在DoG空间中寻找极值点,本质上近似于在LoG空间中寻找稳定的斑点。
import numpy as np
import cv2
# 为了演示DoG的概念,我们构造一个简单的5x5图像块,中心亮,四周暗。
img = np.array([[10, 10, 10, 10, 10],
[10, 60, 60, 60, 10],
[10, 60, 90, 60, 10],
[10, 60, 60, 60, 10],
[10, 10, 10, 10, 10]], dtype=np.float32)
# 使用两个不同大小的高斯核进行模糊
# 注意:为了在小图像上看到效果,这里使用较小的核和标准差。
blur1 = cv2.GaussianBlur(img, (3, 3), sigmaX=0.5)
blur2 = cv2.GaussianBlur(img, (3, 3), sigmaX=1.0)
# 计算高斯差分 (DoG)
dog = blur2 - blur1
print("原始图像中心区域:")
print(img[1:4, 1:4])
print("\n模糊1 (σ=0.5) 中心区域:")
print(blur1[1:4, 1:4].round(2))
print("\n模糊2 (σ=1.0) 中心区域:")
print(blur2[1:4, 1:4].round(2))
print("\nDoG (模糊2 - 模糊1) 中心区域:")
print(dog[1:4, 1:4].round(2))原始图像中心区域:
[[60. 60. 60.]
[60. 90. 60.]
[60. 60. 60.]]
模糊1 (σ=0.5) 中心区域:
[[50.26 57.19 50.26]
[57.19 78.58 57.19]
[50.26 57.19 50.26]]
模糊2 (σ=1.0) 中心区域:
[[38.6 50.01 38.6 ]
[50.01 66.13 50.01]
[38.6 50.01 38.6 ]]
DoG (模糊2 - 模糊1) 中心区域:
[[-11.65 -7.18 -11.65]
[ -7.18 -12.46 -7.18]
[-11.65 -7.18 -11.65]]可以看到,DoG结果在中心区域为负值,这反映了从较清晰图像到较模糊图像,中心亮斑的“损失”,这个差异在斑点区域最为显著。
高斯金字塔与Octave
为了检测不同尺度的特征,SIFT构建了一个高斯金字塔。与传统金字塔不同,SIFT金字塔的每一层(相同分辨率)不是一张图,而是用一组递增的参数进行高斯模糊得到的一组图像,这组图像称为一个Octave(八度)。
Octave之间的关系是:下一个Octave(分辨率减半)的底层图像,由当前Octave中从下往上数的倒数第三张图像进行降采样(隔点采样)得到。这样设计的妙处在于,当前Octave倒数第三张图像的模糊尺度恰好是底层图像的2倍(假设尺度因子k按递增)。降采样后,新Octave底层图像在原图坐标下的等效尺度为,而在自身像素坐标下仍记作,尺度就这样连续翻倍。
假设我们在每个Octave内想要在s个尺度层上寻找极值点。为了在DoG空间中进行极值比较(需要上下各一层),DoG图像需要s+2层。进而,生成DoG所需的高斯模糊图像就需要s+3层。通常s=3,所以每个Octave有6层高斯模糊图像,产生5层DoG图像。
步骤二:寻找DoG空间极值点并精细化
极值点检测
关键点初步定义为DoG尺度空间中的局部极值点。检测时,每一个采样点需要与其同尺度的8个邻域点、以及上一尺度和下一尺度的各9个邻域点(共26个点)进行比较,只有当其是这26个点中的最大值或最小值时,才被保留为候选关键点。这保证了关键点在三维空间中都是极值。
关键点精细化
初步找到的极值点位于离散的像素网格和尺度采样上,并不精确。此外,DoG算子对边缘响应较强,一些不稳定的边缘点需要剔除。
- 位置与尺度插值:通过拟合三维二次函数来精确定位关键点的亚像素位置和尺度。这使得特征点对尺度和位置的微小变化更稳定。
- 剔除低对比度点:将候选点的DoG响应幅值(对比度)与阈值比较(Lowe 原文取 0.03),低于阈值的点易受噪声影响,直接剔除,以增强匹配的稳定性与抗噪能力。
- 去除边缘响应:利用关键点处的Hessian矩阵计算主曲率。边缘在某个方向上有很大的主曲率,而在垂直方向很小。通过设定阈值,剔除那些主曲率比值过大的点(即边缘点),保留类似斑点的稳定区域。
经过这些步骤,我们得到了图像中精确的、稳定的关键点,每个点有其位置、尺度和所在的Octave信息。
关键点方向估计与描述子生成
方向估计(赋予旋转不变性)
为了使描述子具有旋转不变性,需要为每个关键点分配一个主方向。
- 计算梯度幅值与方向:在以关键点为中心、半径为的邻域内,计算每个像素的梯度幅值和方向。 其中为关键点所在尺度的高斯模糊图像(实际实现常用 保留方向所在象限)。
- 生成梯度方向直方图:将360度的方向范围划分为36个柱(每柱10度),用梯度幅值作为权重进行累加。直方图的峰值代表了该关键点邻域梯度的主方向。
- 确定主方向与辅方向:将直方图中最高峰值对应的方向作为主方向。同时,为了增强匹配的鲁棒性,任何达到最高峰值80%幅值的方向,都会被创建为一个新的关键点(拥有该辅方向)。因此,一个位置和尺度可能对应多个方向的关键点。
生成128维SIFT描述子
确定了主方向后,就可以构建旋转不变的描述子了。
- 坐标轴旋转:将关键点邻域的坐标轴旋转到与主方向一致,确保后续描述与图像旋转无关。
- 划分区域与计算梯度:在旋转后的坐标系中,取以关键点为中心的像素窗口。将这个窗口划分为个子区域(每个子区域像素)。
- 生成子区域方向直方图:在每个子区域内,计算每个像素的梯度幅值和方向(此时方向已相对主方向归一化)。将方向(0-360度)划分为8个柱(每柱45度),用梯度幅值进行加权累加,形成一个8维的方向直方图向量。
- 拼接成特征向量:个子区域,每个区域一个8维向量,最终拼接成一个维的特征向量,这就是SIFT描述子。
- 归一化:最后对128维向量进行归一化处理,以进一步减弱光照变化的影响。
import numpy as np
# 模拟SIFT描述子生成中,一个4x4子区域内的梯度方向统计。
# 假设一个4x4子区域,我们已经计算了每个像素相对主方向的梯度方向(0-360度)和幅值。
# 为简化,我们手动指定8个方向bin的累加幅值。
# 假设这是某个子区域统计出的8方向梯度幅值直方图(已加权累加)
subregion_histogram = np.array([1.2, 0.5, 3.1, 0.8, 2.5, 0.1, 1.8, 0.7])
print("一个4x4子区域的8维梯度方向直方图:")
print(subregion_histogram)
print(f"形状: {subregion_histogram.shape}")
# 假设我们有4x4=16个子区域(实际由图像计算得出)
# 这里我们模拟拼接过程:重复上面的直方图16次,形成128维向量(仅为演示,实际每个都不同)
sift_descriptor_demo = np.tile(subregion_histogram, 16)
print(f"\n模拟的完整SIFT描述子向量长度: {len(sift_descriptor_demo)}")
print("前16个元素:", sift_descriptor_demo[:16])一个4x4子区域的8维梯度方向直方图:
[1.2 0.5 3.1 0.8 2.5 0.1 1.8 0.7]
形状: (8,)
模拟的完整SIFT描述子向量长度: 128
前16个元素: [1.2 0.5 3.1 0.8 2.5 0.1 1.8 0.7 1.2 0.5 3.1 0.8 2.5 0.1 1.8 0.7]SIFT的优缺点与总结
优点:
- 强大的不变性:通过尺度空间和方向估计,有效应对缩放、旋转、亮度变化。
- 独特性好:128维的高维向量包含丰富信息,区分能力强,适合精确匹配。
- 抗噪性:高斯平滑使其对噪声不敏感。
缺点:
- 计算复杂度高:构建金字塔、计算DoG、极值检测、方向直方图等步骤都非常耗时,难以满足实时性要求。
- 专利问题:在过去,SIFT曾受专利保护,限制了其商业应用(专利已过期)。
- 对模糊和视角极端变化敏感:虽然对一般仿射变换有一定稳定性,但超出其设计范围时性能下降。
尽管如此,SIFT作为一种里程碑式的特征描述子,其设计思想深刻影响了后续的许多特征提取方法(如SURF、ORB)。在OpenCV等库中都有其实现。
# OpenCV 中 SIFT 的基本使用示例(伪代码,因版本差异可能需调整)
# import cv2
#
# # 创建 SIFT 检测器
# sift = cv2.SIFT_create()
#
# # 读取图像并检测关键点和描述子
# gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
# kp, des = sift.detectAndCompute(gray, None)
#
# # kp 是关键点列表,每个关键点包含 pt, size, angle, response 等属性
# # des 是描述子矩阵,每行是一个128维的SIFT特征向量📝 动手练一练
- 概念辨析:SIFT特征描述子分别依靠什么机制来实现对图像旋转、尺度缩放和亮度变化的不变性?
👉 点击查看参考答案
- 抗旋转:通过计算关键点邻域的梯度方向直方图,将统计出的主方向作为该关键点的参考方向。在生成描述子前,将邻域坐标轴旋转至与主方向对齐,使得后续计算始终基于这个归一化的方向,从而消除了旋转的影响。
- 抗尺度缩放:通过构建高斯金字塔(尺度空间),并在不同尺度的DoG图像中寻找极值点。这使得找到的关键点本身就带有尺度信息(σ)。无论物体在图像中是大是小,总能在某个合适的尺度层上检测到它对应的特征点。
- 抗亮度变化:主要依靠梯度信息而非绝对灰度值。SIFT描述子基于梯度幅值和方向,光照均匀变化(如整体变亮/变暗)对梯度计算影响较小。此外,描述子生成后的向量归一化步骤,进一步削弱了光照对比度变化的影响。
- 流程思考:如果要在每个Octave的3个尺度层上寻找极值点,请问每个Octave至少需要多少层高斯模糊图像?多少层DoG图像?为什么?
👉 点击查看参考答案
- DoG图像层数:需要在
s=3个尺度层上找极值点。为了判断一个点是否是极值,需要与其上一层和下一层的对应邻域进行比较。因此,用于极值检测的DoG层至少需要s + 2 = 3 + 2 = 5层。 - 高斯模糊图像层数:DoG图像由相邻两层高斯模糊图像相减得到。如果有
D层DoG,就需要D+1层高斯模糊图像。因此,至少需要5 + 1 = 6层高斯模糊图像。 - 原因:极值比较需要三维邻域(x, y, scale),缺少上下尺度层就无法确定当前点在尺度维度上是否为极值。
本章小结
本节深入讲解了经典的SIFT局部特征描述子。我们从局部特征的需求出发,理解了SIFT所追求的不变性、独特性和充足性。随后,我们一步步拆解了SIFT的四大核心步骤:在DoG尺度空间找极值点、关键点精细化、方向估计以及最终的128维描述子生成。其中,高斯金字塔与Octave的构建、用DoG近似LoG的巧妙思想、通过梯度直方图确定主方向以实现旋转不变性,都是需要掌握的关键概念。
SIFT特征在深度学习兴起之前,是图像匹配、物体识别等领域不可或缺的工具。虽然其计算速度较慢,但它的设计哲学和稳定性至今仍值得我们学习和借鉴。
📋 行动清单
- 在脑海中复述SIFT特征从图像到128维向量的完整流水线。
- 理解“尺度空间”和“旋转不变性”在SIFT中是如何具体实现的。
- 使用OpenCV(确保版本支持)的
cv2.SIFT_create()尝试对简单图像提取SIFT特征,观察关键点的位置和尺度(圆圈大小)。
—— 小象教研组