计算机视觉入门到实战系列(九) SIFT算法(尺度空间、极值点判断)

SIFT算法(尺度空间、极值点判断)

SIFT(Scale-Invariant Feature Transform)是一种用于检测和描述图像局部特征的经典算法,具有尺度、旋转、光照不变性等优点。

算法原理

一、算法核心思想

SIFT算法由David Lowe在1999年提出,2004年完善。其核心目标是提取对尺度、旋转、光照变化具有不变性的局部特征。SIFT特征在图像匹配、物体识别、三维重建等领域有广泛应用。

二、主要流程(四个关键步骤)

1. 尺度空间极值检测

1.1 构建尺度空间

  • 目的:模拟图像数据的多尺度特征,在不同尺度下寻找稳定的关键点
  • 方法 :使用高斯核函数与图像进行卷积
    • 尺度空间函数:L(x, y, σ) = G(x, y, σ) * I(x, y)
    • 高斯函数:G(x, y, σ) = 1/(2πσ²) * exp(-(x²+y²)/(2σ²))
    • σ为尺度参数,σ越大图像越模糊

1.2 构建高斯差分金字塔(DoG)

  • 原理:DoG近似于尺度归一化的高斯拉普拉斯算子(LoG)
  • 计算D(x, y, σ) = L(x, y, kσ) - L(x, y, σ)
  • 结构
    • 组(Octave):通过下采样得到的图像集合
    • 层(Level):同一组内不同σ的高斯模糊图像
    • 典型的金字塔有4-6个组,每组有3-5层

1.3 局部极值检测

  • 在DoG空间的三维(x, y, σ)空间中搜索极值点
  • 每个像素点与其26个邻居比较(同一尺度8个+相邻尺度各9个)
  • 如果该点是极大值或极小值,则作为候选关键点

2. 关键点精确定位

2.1 去除低对比度点

  • 使用DoG函数的泰勒展开进行精确定位
  • 计算极值点位置:x̂ = - (∂²D⁻¹/∂x²) * (∂D/∂x)
  • 如果|D(x̂)| < 0.03(归一化后),则剔除该点(对比度太低)

2.2 消除边缘响应

  • DoG在边缘处有较强响应,但这些点不稳定

  • 计算Hessian矩阵:

    复制代码
    H = [Dxx Dxy]
        [Dxy Dyy]
  • 计算主曲率比值:Tr(H)²/Det(H)

  • 如果比值大于阈值(通常为10),判定为边缘点并剔除

3. 方向分配

3.1 计算梯度幅值和方向

  • 在关键点所在的高斯模糊图像上计算梯度
    • 幅值:m(x, y) = √[(L(x+1, y) - L(x-1, y))² + (L(x, y+1) - L(x, y-1))²]
    • 方向:θ(x, y) = atan2(L(x, y+1) - L(x, y-1), L(x+1, y) - L(x-1, y))

3.2 构建方向直方图

  • 以关键点为中心,取一定区域(通常半径3×1.5σ)
  • 将360°划分为36个柱(每柱10°)
  • 用高斯加权函数对梯度幅值进行加权(离中心越近权重越大)

3.3 确定主方向

  • 直方图峰值为主方向
  • 如果有其他峰值>主峰值的80%,则创建多个关键点(相同位置,不同方向)

4. 关键点描述符生成

4.1 构建描述符区域

  • 将关键点邻域旋转到主方向(实现旋转不变性)
  • 取16×16的窗口区域,划分为4×4的子区域

4.2 计算子区域梯度直方图

  • 每个子区域大小为4×4像素
  • 计算每个像素的梯度幅值和方向(相对主方向)
  • 将0-360°划分为8个方向(每45°一个方向)
  • 形成4×4×8=128维的描述向量

4.3 描述符归一化

  • 对128维向量进行归一化,减少光照变化影响
  • 进一步处理:
    1. 截断大于0.2的值,防止过大梯度影响
    2. 重新归一化,增强鲁棒性

三、SIFT特征的优势特性

1. 尺度不变性

  • 通过尺度空间理论,在不同尺度下检测特征
  • 使用DoG近似LoG,检测尺度归一化的极值

2. 旋转不变性

  • 为关键点分配主方向
  • 描述符基于相对方向计算

3. 光照不变性

  • 使用梯度信息而非灰度值
  • 描述符归一化处理

4. 视角不变性(一定程度)

  • 局部特征对视角变化较鲁棒
  • 对仿射变换有一定容忍度

四、数学原理基础

1. 尺度空间理论

  • 图像在不同尺度下的表示
  • 唯一可能的尺度空间核是高斯核(根据尺度空间公理)

2. LoG与DoG的近似关系

  • 高斯拉普拉斯:∇²G = ∂²G/∂x² + ∂²G/∂y²
  • DoG与LoG的关系:∂G/∂σ ≈ σ∇²G
  • 因此:G(x, y, kσ) - G(x, y, σ) ≈ (k-1)σ²∇²G

3. 高斯平滑的扩散方程解释

  • 高斯平滑等价于热扩散方程
  • 尺度参数σ对应扩散时间

代码实现

尺度空间的生成

python 复制代码
import cv2
import numpy as np
from scipy.ndimage import maximum_filter
import matplotlib.pyplot as plt
import random


# 生成指定数目的高斯核,并对图像做高斯滤波
def generateGimage(image, sigma, num_layers = 8, k_stride = 2):
    sigma_res = np.sqrt(np.max([(sigma ** 2) - ((1) ** 2), 0.01]))
    # (0,0)表示卷积核的大小根据sigma来决定
    image = cv2.GaussianBlur(image, (0, 0), sigmaX=sigma_res, 
                             sigmaY=sigma_res)
    
    # 生成高斯核
    k = 2 ** (1 / k_stride)
    gaussian_kernels = np.zeros(num_layers)
    # 第一层高斯就是1.6
    gaussian_kernels[0] = sigma
    
    for i in range(1, num_layers):
        # 根据高斯的性质,可以将大的高斯核拆分,减少计算量
        gaussian_old = k**(i-1) * sigma
        gaussian_new = k * gaussian_old
        gaussian_kernels[i] = np.sqrt(gaussian_new**2 - gaussian_old**2)
    
    # 至此,我们已经得到一系列高斯核
    
    # 进行高斯模糊
    gaussian_images = [image]
    for kernel in gaussian_kernels:
        tmp_image = cv2.GaussianBlur(image, (0, 0), sigmaX=kernel, 
                                     sigmaY=kernel)
        gaussian_images.append(tmp_image)
    
    # 返回不同高斯核对图像滤波的结果
    return np.array(gaussian_images)
        
    
# 生成DoG空间
def generateDoGSpace(gaussian_images):
    dog_images = []
    for img1, img2 in zip(gaussian_images, gaussian_images[1:]):
        dog_images.append(img2 - img1)
    return dog_images

这段代码是SIFT算法的第一步:构建尺度空间和高斯差分(DoG)空间,用于后续的关键点检测。

一、整体功能

代码包含两个函数:

  1. generateGimage():生成多尺度高斯模糊图像(构建高斯金字塔)
  2. generateDoGSpace():通过相邻尺度高斯图像相减生成DoG空间

二、详细解释
1. generateGimage() 函数

函数参数

python 复制代码
def generateGimage(image, sigma, num_layers=8, k_stride=2):
  • image:输入图像
  • sigma:初始尺度参数(SIFT中通常为1.6)
  • num_layers:每个octave(组)中的层数(默认8)
  • k_stride:尺度倍增因子中的参数(默认2,表示k=2^(1/2))

步骤1:初始高斯模糊

python 复制代码
sigma_res = np.sqrt(np.max([(sigma ** 2) - ((1) ** 2), 0.01]))
image = cv2.GaussianBlur(image, (0, 0), sigmaX=sigma_res, sigmaY=sigma_res)
  • 目的:补偿原始图像的模糊(假设相机模糊σ≈1)
  • 计算σ_实际 = √(σ² - 1²),如果σ²<1则取0.01
  • 物理意义:如果我们要得到σ=1.6的模糊效果,原始图像已有σ=1的模糊,只需额外添加σ=√(1.6²-1²)=1.25的模糊
  • 关于上面代码的具体解释可见高斯卷积的可加性定理

步骤2:生成高斯核序列

python 复制代码
k = 2 ** (1 / k_stride)  # k = √2 ≈ 1.414
gaussian_kernels = np.zeros(num_layers)
gaussian_kernels[0] = sigma  # 第一层:σ = 1.6

for i in range(1, num_layers):
    gaussian_old = k**(i-1) * sigma  # 上一层的σ
    gaussian_new = k * gaussian_old  # 当前层的σ
    gaussian_kernels[i] = np.sqrt(gaussian_new**2 - gaussian_old**2)

关键理解

  1. 尺度序列:σ, kσ, k²σ, k³σ, ...(几何增长)
  2. 增量计算原理 (高斯卷积的可分离性):
    • 要对已经具有σ₁模糊的图像得到σ₂的模糊效果
    • 只需添加σ_add = √(σ₂² - σ₁²)的额外模糊
    • 因为:G(σ₂) = G(σ₁) * G(σ_add)

示例计算(σ=1.6,k=√2):

复制代码
层0: σ0 = 1.6
层1: σ1 = k*σ0 = 1.414*1.6 = 2.262
    σ_add = √(2.262² - 1.6²) = √(5.12 - 2.56) = √2.56 = 1.6
层2: σ2 = k*σ1 = 1.414*2.262 = 3.2
    σ_add = √(3.2² - 2.262²) = √(10.24 - 5.12) = √5.12 = 2.262

步骤3:生成高斯图像序列

python 复制代码
gaussian_images = [image]  # 第一张:初始模糊后的图像
for kernel in gaussian_kernels:
    tmp_image = cv2.GaussianBlur(image, (0, 0), sigmaX=kernel, sigmaY=kernel)
    gaussian_images.append(tmp_image)
  • 注意 :这里有个潜在问题:所有模糊都基于同一张初始图像
  • 标准的SIFT做法:应该逐层递进模糊(Gₖ = G_{k-1} * G(σ_add))
  • 这里的方法:Gₖ = G₀ * G(σ_k),计算量大但结果相同

2. generateDoGSpace() 函数

python 复制代码
def generateDoGSpace(gaussian_images):
    dog_images = []
    for img1, img2 in zip(gaussian_images, gaussian_images[1:]):
        dog_images.append(img2 - img1)
    return dog_images

功能:计算相邻尺度高斯图像的差分

  • 输入:[G(σ0), G(σ1), G(σ2), ..., G(σn)]
  • 输出:[G(σ1)-G(σ0), G(σ2)-G(σ1), ..., G(σn)-G(σ_{n-1})]
  • 结果:n个DoG图像,对应n+1个高斯图像

三、关键点说明

1. 尺度参数k的计算

python 复制代码
k = 2 ** (1 / k_stride)  # 当k_stride=2时,k=2^(1/2)=√2≈1.414
  • k_stride表示每个octave中的层数
  • 在标准SIFT中,通常有3个DoG层用于极值检测,因此需要4个高斯层
  • 这里使用8层可能为了更精细的尺度采样

2. 高斯卷积的可分离性应用

python 复制代码
gaussian_kernels[i] = np.sqrt(gaussian_new**2 - gaussian_old**2)
  • 数学原理 :两个高斯卷积的合并
    • G(σ₁) * G(σ₂) = G(√(σ₁² + σ₂²))
  • 实际意义:为了得到σ_new的模糊,如果已有σ_old的模糊,只需添加σ_add = √(σ_new² - σ_old²)的模糊

四、在完整SIFT算法中的位置

这是SIFT算法的第一阶段

复制代码
原始图像 → 初始高斯模糊 → 多尺度高斯模糊 → DoG空间
      ↓           ↓              ↓          ↓
     I(x,y)   G(σ_res)*I   {G(σ_k)*I}   {D(x,y,σ)}

五、输出结果

  1. 高斯图像序列num_layers+1张不同尺度的模糊图像
  2. DoG图像序列num_layers张差分图像,用于后续的极值检测

这个DoG空间将在SIFT的下一步中用于检测尺度空间极值点(与26个邻居比较找极值)。

判断每个位置其是否为局部极值点

python 复制代码
# 1.判别当前像素点是否为局部最大值点
def isLocalExtremum(l1, l2, l3, threshold):
    # l1,l2,l3是DoG尺度空间中相邻的3层,大小均已被切片成 3*3
    # 即[l1,l2,l3]是一个cube
    # 需要确定l2层的中心位置,即(1,1)位置是否为极值点
    # threshold是一个设定的阈值,l2[1,1]必须大于阈值才可进行极值点的判定
    if l2[1,1] > threshold:
        if l2[1,1] > 0:
            return np.all(l2[1,1]>=l1) and np.all(l2[1,1]>=l3) \
                    and np.sum(l2[1,1]<l2)==0
        
        elif l2[1,1] < 0:
            return np.all(l2[1,1]<=l1) and np.all(l2[1,1]<=l3) \
                    and np.sum(l2[1,1]>l2)==0
    return False

np.all NumPy 库中的一个函数,用于测试数组中的所有元素是否都为 True。它在数据验证、条件检查等场景中非常有用。‌

这个函数判断在DoG尺度空间中,一个点是否为3D局部极值(在x、y、σ三个维度上)。

python 复制代码
def isLocalExtremum(l1, l2, l3, threshold):
  • l1, l2, l3:DoG空间中相邻的三层,每层都是3×3切片
  • threshold:响应阈值,过滤弱响应点
  • 返回值:True表示是局部极值,False表示不是

3×3×3立方体

复制代码
        层l1(σ较小)         层l2(当前层)         层l3(σ较大)
      ⎡ a1 b1 c1 ⎤        ⎡ a2 b2 c2 ⎤        ⎡ a3 b3 c3 ⎤
      ⎢ d1 e1 f1 ⎥        ⎢ d2 e2 f2 ⎥        ⎢ d3 e3 f3 ⎥
      ⎣ g1 h1 i1 ⎦        ⎣ g2 h2 i2 ⎦        ⎣ g3 h3 i3 ⎦
         (上层)              (当前层)             (下层)

中心点l2[1, 1]e2

第一步:阈值过滤

python 复制代码
if l2[1,1] > threshold:
  • 只处理响应强度大于阈值的点
  • 过滤掉弱响应的点(可能是噪声)
  • threshold通常取经验值(如0.03×max_response)

第二步:判断极值类型

情况1:正响应(极大值)

python 复制代码
if l2[1,1] > 0:
    return np.all(l2[1,1] >= l1) and np.all(l2[1,1] >= l3) \
           and np.sum(l2[1,1] < l2) == 0

条件解释

  1. np.all(l2[1,1] >= l1):中心点 ≥ l1层的所有9个点
  2. np.all(l2[1,1] >= l3):中心点 ≥ l3层的所有9个点
  3. np.sum(l2[1,1] < l2) == 0:中心点 ≥ l2层的其他8个点

图示(正极大值):

复制代码
l1层:所有点 ≤ e2
      [≤e2 ≤e2 ≤e2]
      [≤e2 ≤e2 ≤e2] 
      [≤e2 ≤e2 ≤e2]

l2层:中心e2最大,其他8个点 ≤ e2
      [≤e2 ≤e2 ≤e2]
      [≤e2  e2  ≤e2] ← e2是最大值
      [≤e2 ≤e2 ≤e2]

l3层:所有点 ≤ e2
      [≤e2 ≤e2 ≤e2]
      [≤e2 ≤e2 ≤e2]
      [≤e2 ≤e2 ≤e2]

情况2:负响应(极小值)

python 复制代码
elif l2[1,1] < 0:
    return np.all(l2[1,1] <= l1) and np.all(l2[1,1] <= l3) \
           and np.sum(l2[1,1] > l2) == 0

条件解释

  1. np.all(l2[1,1] <= l1):中心点 ≤ l1层的所有9个点
  2. np.all(l2[1,1] <= l3):中心点 ≤ l3层的所有9个点
  3. np.sum(l2[1,1] > l2) == 0:中心点 ≤ l2层的其他8个点

图示(负极小值):

复制代码
l1层:所有点 ≥ e2
      [≥e2 ≥e2 ≥e2]
      [≥e2 ≥e2 ≥e2]
      [≥e2 ≥e2 ≥e2]

l2层:中心e2最小,其他8个点 ≥ e2
      [≥e2 ≥e2 ≥e2]
      [≥e2  e2  ≥e2] ← e2是最小值
      [≥e2 ≥e2 ≥e2]

l3层:所有点 ≥ e2
      [≥e2 ≥e2 ≥e2]
      [≥e2 ≥e2 ≥e2]
      [≥e2 ≥e2 ≥e2]

第三步:返回结果

python 复制代码
return False
  • 如果不满足阈值条件,或响应为0,返回False
  • 注意:这里没有处理l2[1,1] == 0的情况(直接返回False)

检测26邻域极值

函数实际上检查了26个邻居点

  • 上层(l1):9个点
  • 同层(l2):8个点(排除中心点)
  • 下层(l3):9个点

总计:9 + 8 + 9 = 26个比较点

物理意义

在DoG空间中,极值点对应图像中的显著特征

  • 正极大值:暗背景上的亮斑点
  • 负极小值:亮背景上的暗斑点
相关推荐
Jerry3 小时前
LeetCode 28. 找出字符串中第一个匹配项的下标
算法
Jerry5 小时前
LeetCode 459. 重复的子字符串
算法
海石8 小时前
1500分的题目,确实有实力,不过还是我略胜一筹
算法·leetcode
海石8 小时前
【记忆化搜索】条条大路通AC,走好适合你的那一条,走到后再考虑走得快
算法·leetcode
Jerry10 小时前
LeetCode 151. 反转字符串中的单词
算法
a11177612 小时前
LM 算法迭代过程动画演示(SLAM)
算法
头茬韭菜12 小时前
Context 的生死抉择:四层压缩、截断算法与 Session Memory
算法·ai
Jerry12 小时前
LeetCode 541. 反转字符串 II
算法
Jerry13 小时前
LeetCode 344. 反转字符串
算法
搞科研的小刘选手13 小时前
【香港大学主办&IEEE出版】第六届计算机视觉、应用与算法国际学术会议(CVAA 2026)
算法·计算机视觉·应用·学术会议