查看全课大纲(第 15 / 93 节)
  1. 1.计算机视觉概述
  2. 2.计算机视觉应用场景
  3. 3.困难与挑战
  4. 4.开源库介绍
  5. 5.图像显示与存储
  6. 6.CLAHE
  7. 7.腐蚀膨胀与开闭运算
  8. 8.滤波与边缘填充
  9. 9.各种滤波
  10. 10.图像金字塔
  11. 11.傅里叶变换
  12. 12.短时傅里叶变换与小波变换
  13. 13.颜色特征与颜色直方图
  14. 14.几何特征_边缘
  15. 15.几何特征-角点与斑点
  16. 16.局部特征-SIFT特征描述子
  17. 17.局部特征-SURF特征描述子
  18. 18.局部特征-ORB特征描述子
  19. 19.其他特征描述子
  20. 20.传统图像分割技术
  21. 21.GraphCut
  22. 22.GrabCut
  23. 23.人脸检测的传统方法
  24. 24.行人检测的传统方法HOG+SVM
  25. 25.行人检测DPM
  26. 26.人工神经网络的架构
  27. 27.目标函数与梯度下降
  28. 28.误差反向传播
  29. 29.可手算的神经网络
  30. 30.改进目标函数与克服梯度消散
  31. 31.神经网络之传统神经网络与深度网络的差异
  32. 32.各种梯度下降算法
  33. 33.如何选择合适的梯度下降算法
  34. 34.避免过适应
  35. 35.CNN的架构
  36. 36.CNN的误差反向传播_卷积层
  37. 37.CNN的误差反向传播_池化层
  38. 38.分类所用的数据集
  39. 39.Alexnet详解
  40. 40.VGG详解
  41. 41.GoogLeNet_v1
  42. 42.BatchNormalization
  43. 43.GoogLeNet_v3v4
  44. 44.ResNet
  45. 45.ResNeXt
  46. 46.CNN设计准则
  47. 47.图像检索与推土机距离
  48. 48.基于颜色相似性
  49. 49.基于形状相似性
  50. 50.基于纹理相似性
  51. 51.基于局部特征相似性
  52. 52.DL的二值哈希编码
  53. 53.高维空间中检索KDTree
  54. 54.高维空间中检索LSH
  55. 55.目标识别任务综述
  56. 56.RCNN
  57. 57.SPP-Net
  58. 58.Fast R-CNN
  59. 59.Faster R-CNN
  60. 60.R-CNN系列模型回顾
  61. 61.R-FCN的设计思路
  62. 62.R-FCN的实现
  63. 63.YOLOv1
  64. 64.YOLOv2_9000
  65. 65.YOLOv3
  66. 66.图像分割应用场景与数据集
  67. 67.上采样的实现方式
  68. 68.FCN网络架构的由来
  69. 69.FCN构建细节
  70. 70.DeepLabv1
  71. 71.CRF
  72. 72.DeepLabv2
  73. 73.DeepLabv3
  74. 74.特定的医学影像分割任务
  75. 75.U-Net
  76. 76.3D U-Net
  77. 77.V-Net
  78. 78.DenseNet
  79. 79.病理切片分析任务及数据集
  80. 80.病理切片分析
  81. 81.图说任务及其传统处理方法
  82. 82.深度语言模型与RNN
  83. 83.LSTM及其变种
  84. 84.NIC模型
  85. 85.SAT模型
  86. 86.图说的性能指标
  87. 87.判别模型与生成模型
  88. 88.KL散度与JS散度
  89. 89.VAE
  90. 90.GAN的基本原理
  91. 91.DCGAN与CGAN
  92. 92.Wasserstein GAN
  93. 93.SRGAN

几何特征-角点与斑点

角点与斑点

小象实战讲义 · 基于深度学习的计算机视觉

在上一节中,我们学习了如何提取图像的边缘特征。边缘是图像中像素值发生剧烈变化的区域,蕴含着丰富的几何信息。然而,在计算机视觉的许多实际任务中,例如图像拼接、三维重建或物体跟踪,仅仅找到边缘是不够的。我们需要找到图像中更稳定、更具区分性的“关键点”。本节课,我们将深入探讨两类重要的几何特征:角点与斑点。它们是构建后续复杂特征描述子(如SIFT、SURF)的基础,理解它们的本质差异,是掌握传统图像特征提取技术的关键一步。

💡 核心导读

  • 关键点的意义:理解为什么在图像大小、方向、光照变化时,我们仍能通过一些“稳定”的点来识别同一物体或场景。
  • 哈里斯角点:掌握其核心思想——通过分析图像局部窗口在所有方向移动时的灰度变化,来判断该区域是平面、边缘还是角点。
  • FAST角点:学习一种极其快速的角点检测算法,理解其“与众不同即角点”的直观思想及其局限性。
  • 斑点特征:从二阶导数的角度,理解斑点(Blob)作为“被边缘包围的区域”的数学定义,及其与角点的根本区别。

关键点:稳定特征的基石

想象一下,我们从不同角度、不同距离、不同光照下观察同一辆玩具车。虽然图像中的车辆大小、形状、明暗都发生了变化,但我们依然能认出它是同一辆车。这是为什么呢?

一个重要的原因是,图像中存在一些稳定的局部特征点。这些点及其周围的小区域,在图像发生各种变化(如平移、旋转、尺度缩放、亮度变化)时,其本身的特性以及它与周围像素的关系,在某种程度上保持着几乎不变的性质。这种性质,我们称之为描述子的可复现性。

在传统的数字图像处理中,一个经典的思路是:先找到这些与众不同的“关键点”,然后再去描述这些关键点周围区域的特征。这个描述结果,就是我们后续会学到的“特征描述子”。今天,我们重点学习如何找到这些关键点,特别是其中的两种重要类型:角点和斑点。

找到稳定的关键点,是许多高级视觉应用的基础:

  • 图像配准与拼接:将多张描述同一场景不同部分的图片对齐、拼接成一张全景图。
  • 运动跟踪:在视频序列中,跟踪特定物体或特征点的运动轨迹。
  • 物体识别:通过匹配不同图像中的关键点,来判断它们是否包含同一物体。
  • 三维重建:从多视角图像中恢复场景的三维结构。

那么,什么样的点才算是合格的“关键点”呢?它需要具备可重复性(在不同图像中能被稳定地检测到)、显著性(与周围区域有足够区别)以及对图像变换的抗性。

哈里斯角点:基于灰度变化的检测

哈里斯角点是一种经典且广泛使用的关键点检测方法。它的核心思想非常直观:在一个图像上放置一个小的观察窗口(例如一个3x3或5x5的像素块),然后尝试将这个窗口在各个方向上进行微小的移动。

我们可以根据移动后窗口内像素灰度值的变化情况,将图像区域分为三类:

  1. 平坦区域:无论朝哪个方向移动窗口,窗口内的像素灰度值都几乎不变。
  2. 边缘区域:沿着边缘方向移动窗口,灰度值变化很小;但垂直于边缘方向移动时,灰度值会发生剧烈变化。
  3. 角点区域:朝任何方向移动窗口,窗口内的灰度值都会发生显著的变化。

哈里斯角点检测的目标,就是找出这些“角点区域”。

数学模型与判断

为了量化这种变化,我们建立数学模型。设窗口函数为 w(x, y)(例如矩形窗或高斯窗),窗口中心在 (x, y) 的像素灰度值为 I(x, y)。当窗口在x和y方向分别位移 u 和 v 后,窗口内像素的灰度变化总和 E(u, v) 可以近似表示为:

E(u,v)≈∑x,yw(x,y) [I(x+u,y+v)−I(x,y)]2E(u, v) \approx \sum_{x, y} w(x, y) \, [I(x+u, y+v) - I(x, y)]^2

通过对 I(x+u, y+v) 进行泰勒展开并忽略高阶项,E(u, v) 可以转化为一个二次型:

E(u,v)≈[uv]M[uv]E(u, v) \approx \begin{bmatrix} u & v \end{bmatrix} M \begin{bmatrix} u \\ v \end{bmatrix}

其中,矩阵 M 是一个2x2的实对称矩阵,由图像在窗口内的导数构成:

M=∑x,yw(x,y)[Ix2IxIyIxIyIy2]M = \sum_{x, y} w(x, y) \begin{bmatrix} I_x^2 & I_x I_y \\ I_x I_y & I_y^2 \end{bmatrix}

这里 I_x 和 I_y 分别是图像在x和y方向的一阶导数(梯度)。

矩阵 M 的特征值 λ1 和 λ2 揭示了该区域的结构信息:

  • 如果两个特征值都很小,说明在任何方向上移动灰度变化都小,对应平坦区域。
  • 如果一个特征值大,另一个小,说明灰度变化主要在一个方向上,对应边缘区域。
  • 如果两个特征值都很大且近似相等,说明在任何方向上移动灰度变化都很大,对应角点区域。

基于这个原理,哈里斯定义了角点响应函数 R: R = det(M) - k * (trace(M))^2,其中 det(M) 是矩阵行列式(等于特征值乘积),trace(M) 是矩阵的迹(等于特征值之和),k 是一个经验常数(通常取0.04~0.06)。R 值很大的点就被认为是角点。

窗口函数 w(x, y) 的选择也有讲究:

  • 矩形窗:权重非0即1,相当于硬边界。它对角点的定位更精确,但对噪声更敏感。
  • 高斯窗:权重从中心向四周平滑衰减,相当于软边界。它具有一定的平滑和抗噪能力,并且隐含着多尺度的思想——通过调整高斯函数的尺度参数σ,我们可以在不同模糊程度上检测角点,从而发现更大尺度上的“角状”结构。

让我们通过一个简单的代码示例,直观感受哈里斯角点的检测过程。我们将使用OpenCV的 cv2.cornerHarris 函数。

import numpy as np
import cv2

# 1. 构造一个简单的测试图像:一个白色背景上的黑色“L”形角
image = np.ones((100, 100), dtype=np.uint8) * 255  # 白色背景
image[30:70, 30:40] = 0  # 垂直黑条
image[30:40, 30:70] = 0  # 水平黑条
# 这样就在(30,30)到(40,40)区域形成了一个直角

# 2. 转换为浮点型用于计算
image_float = np.float32(image)

# 3. 应用哈里斯角点检测
# 参数:图像,邻域窗口大小 blockSize,Sobel 导数孔径 ksize,k 值
block_size = 2
ksize = 3
k = 0.04
harris_response = cv2.cornerHarris(image_float, block_size, ksize, k)

# 4. 对角点响应进行阈值化,标记角点
# 通常取响应最大值的某个比例作为阈值
threshold = 0.01 * harris_response.max()
corner_image = np.copy(image)
# 将角点位置标记为灰色 128(单通道图像没有红色通道)
corner_image[harris_response > threshold] = 128  # 用灰色标记

print("图像中心区域 (30:45, 30:45) 的哈里斯响应值示例:")
print(harris_response[30:45, 30:45].astype(int)) # 查看角点附近的响应值
print("\n标记角点后的图像,角点处灰度值为128。")
图像中心区域 (30:45, 30:45) 的哈里斯响应值示例:
[[  82583024  218845008 -169130016 -169130016 -169130016 -169130016
  -169130016 -169130016 -169130016 -169130016 -169130016 -169130016
  -169130016 -169130016 -169130016]
 [ 218845008  458335776  -42282504  -42282504  -42282504  -42282504
   -42282504  -42282504  -42282504  -42282504  -42282504  -42282504
   -42282504  -42282504  -42282504]
 [-169130016  -42282504          0          0          0          0
           0          0          0          0          0          0
           0          0          0]
 [-169130016  -42282504          0          0          0          0
           0          0          0          0          0          0
           0          0          0]
 [-169130016  -42282504          0          0          0          0
           0          0          0          0          0          0
           0          0          0]
 [-169130016  -42282504          0          0          0          0
           0          0          0          0          0          0
           0          0          0]
 [-169130016  -42282504          0          0          0          0
           0          0          0          0          0          0
           0          0          0]
 [-169130016  -42282504          0          0          0          0
           0          0          0          0          0          0
           0          0          0]
 [-169130016  -42282504          0          0          0          0
           0          0          0          0          0          0
           0          0          0]
 [-169130016  -42282504          0          0          0          0
           0          0          0    -165166   -1816825  -11396456
   -42282504  -42282504  -42282504]
 [-169130016  -42282504          0          0          0          0
           0          0          0   -1816825   82583024  218845008
  -169130016 -169130016 -169130016]
 [-169130016  -42282504          0          0          0          0
           0          0          0  -11396456  218845008  458335776
   -42282504  -42282504  -42282504]
 [-169130016  -42282504          0          0          0          0
           0          0          0  -42282504 -169130016  -42282504
           0          0          0]
 [-169130016  -42282504          0          0          0          0
           0          0          0  -42282504 -169130016  -42282504
           0          0          0]
 [-169130016  -42282504          0          0          0          0
           0          0          0  -42282504 -169130016  -42282504
           0          0          0]]

标记角点后的图像,角点处灰度值为128。

注意:在这个极简的示例中,由于图像非常简单且边界锐利,角点响应可能集中在非常小的像素上。在实际复杂图像中,响应区域会更明显。

FAST角点:追求极致的速度

哈里斯角点虽然效果稳定,但需要逐像素计算梯度、构建二阶矩矩阵并求响应函数,计算量较大,在需要实时处理的应用中(如SLAM、视频处理)可能成为瓶颈。FAST角点算法应运而生,其全称是“Features from Accelerated Segment Test”,正如其名,它的目标是快速。

FAST算法的思想极其直观:如果一个像素点与其周围足够多的邻域像素点都“不一样”,那它很可能就是一个角点。

具体步骤如下:

  1. 以候选点 p 为中心,画一个半径为3个像素的离散化的圆,这个圆的周长上有16个像素点(上、下、左、右及对角线方向)。
  2. 设定一个阈值 t。
  3. 检查这16个像素点。如果存在连续的 n 个点(通常 n=12),它们的灰度值要么全部大于 I_p + t,要么全部小于 I_p - t(其中 I_p 是中心点 p 的灰度值),那么点 p 就被认为是角点。

为什么是连续12个点(16的3/4)?这模拟了一个直角或更尖锐的角的情况。想象一个黑色的直角放在白色背景上,围绕直角顶点画圆,大约3/4圆周上的点都是白色(与黑色顶点明显不同)。FAST算法就是捕捉这种模式。

import numpy as np

# 模拟FAST角点判断的简化逻辑
def fast_corner_check(patch, threshold):
    """
    简化版的FAST判断逻辑。
    patch: 一个17个元素的列表,patch[0]是中心点Ip, patch[1:17]是周围16个点的灰度值。
    threshold: 阈值t。
    返回: 如果是角点返回True,否则返回False。
    """
    Ip = patch[0]
    circle_pixels = patch[1:]
    
    # 判断是否连续12个点都大于 Ip+t 或都小于 Ip-t
    brighter = [p > Ip + threshold for p in circle_pixels]
    darker = [p < Ip - threshold for p in circle_pixels]
    
    # 将16个点首尾相连成环进行检查
    circle_brighter = brighter + brighter[:12] # 复制一段用于环状检查
    circle_darker = darker + darker[:12]
    
    for start in range(16):
        # 检查连续12个是否都更亮
        if all(circle_brighter[start:start+12]):
            return True
        # 检查连续12个是否都更暗
        if all(circle_darker[start:start+12]):
            return True
    return False

# 构造一个模拟的角点情况:中心是暗点,周围16个点中有连续12个是亮点
# 例如:中心值=50, 阈值t=30, 亮点=100, 其他点=60
center_val = 50
threshold = 30
# 假设索引0-11是亮点,12-15是普通点
simulated_patch = [center_val] + [100]*12 + [60]*4

is_corner = fast_corner_check(simulated_patch, threshold)
print(f"模拟角点检测结果: {is_corner}")

# 构造一个非角点情况:中心点与周围差异不大
non_corner_patch = [80] + [85]*16
is_corner2 = fast_corner_check(non_corner_patch, 10)
print(f"模拟非角点检测结果: {is_corner2}")
模拟角点检测结果: True
模拟非角点检测结果: False

FAST角点的特点与局限性:

  • 优点:计算速度极快,非常适合实时系统。
  • 缺点:
    1. 不具备尺度不变性:它只在固定的像素半径上检测,无法检测不同尺度下的角点。而哈里斯角点通过高斯窗的尺度参数σ,具有一定的多尺度检测能力。
    2. 不具备旋转不变性:算法本身没有方向信息。不过,这个缺陷可以在后续生成特征描述子时通过其他方法弥补。
    3. 对噪声敏感:由于判断基于直接的灰度比较,噪声容易导致误检。

斑点:被边缘包围的区域

现在,我们把目光从“点”转向“块”。斑点,有时也称为斑块,是图像中另一类重要的几何特征。它与角点虽然只有一字之差,但代表的图像结构却完全不同。

我们可以从导数的角度来理解斑点。回顾边缘检测:边缘是图像灰度一阶导数的极值点,对应着二阶导数的过零点。那么,如果我们关注二阶导数的极值点,会对应什么样的图像结构呢?

想象一个深色的圆形斑点放在浅色背景上。这个斑点的边界构成了一个闭合的环,这个环就是边缘(二阶导数为零)。而在这个环的内部中心,灰度变化平缓,一阶导数为零、二阶导数(拉普拉斯响应)取极值。换句话说:边缘出现在一阶导数取极值、二阶导数过零的位置;斑点中心则出现在二阶导数取极值的位置。因此,斑点可以理解为被边缘所包围的、在局部区域内灰度均匀且与周围对比鲜明的区域。

在实际计算中,由于直接求导对噪声非常敏感,我们同样需要先进行平滑。一个标准的方法是使用高斯拉普拉斯滤波器。

  1. 高斯滤波:用高斯函数 G(x, y, σ) 对图像进行卷积,平滑噪声。尺度参数 σ 决定了平滑的力度,σ 越大,图像越模糊,能检测到更大尺度的斑点。
  2. 拉普拉斯算子:对平滑后的图像应用拉普拉斯算子 ∇²(即求二阶导数之和),得到LoG响应图。
  3. 检测极值:在LoG响应图中寻找极值点(极大值或极小值),这些点就对应着图像中的斑点。

通过改变高斯滤波的 σ 值,我们可以在不同尺度下检测斑点,这构成了尺度空间理论的基础,也是后续SIFT等强大特征描述子的核心思想之一。

import numpy as np
import cv2

# 1. 构造一个简单的测试图像:包含一个亮斑和一个暗斑
image = np.ones((100, 100), dtype=np.uint8) * 128  # 灰色背景
# 创建一个亮斑 (高斯分布)
y, x = np.ogrid[-10:11, -10:11]
bright_blob = 200 * np.exp(-(x**2 + y**2) / (2*3.0**2))
image[40:61, 40:61] = np.clip(image[40:61, 40:61] + bright_blob, 0, 255).astype(np.uint8)
# 创建一个暗斑
dark_blob = -100 * np.exp(-(x**2 + y**2) / (2*5.0**2))
image[70:91, 70:91] = np.clip(image[70:91, 70:91] + dark_blob, 0, 255).astype(np.uint8)

# 2. 应用高斯拉普拉斯滤波 (LoG)
# OpenCV的Laplacian函数本身不包含高斯平滑,所以我们分步进行。
sigma = 3.0
# 首先进行高斯平滑
image_blurred = cv2.GaussianBlur(image.astype(np.float32), (0, 0), sigmaX=sigma)
# 然后应用拉普拉斯算子
# cv2.CV_32F 指定输出为浮点型,以保留负值
laplacian = cv2.Laplacian(image_blurred, cv2.CV_32F, ksize=3) # ksize必须是正奇数

# 3. 寻找LoG响应图中的极值点 (简化版:寻找绝对值较大的点)
# 在实际的斑点检测中,需要在尺度空间和二维空间同时寻找极值。
abs_log = np.abs(laplacian)
threshold = 0.5 * abs_log.max()
# 获取极值点坐标 (这里简单阈值化)
blob_coords = np.column_stack(np.where(abs_log > threshold))

print(f"检测到 {len(blob_coords)} 个候选斑点位置。")
print("前5个候选点坐标 (y, x):")
print(blob_coords[:5])
print("\nLoG响应图在亮斑中心(50,50)附近的值(示例):")
print(laplacian[48:53, 48:53].astype(int))
检测到 46 个候选斑点位置。
前5个候选点坐标 (y, x):
[[47 48]
 [47 49]
 [47 50]
 [47 51]
 [47 52]]

LoG响应图在亮斑中心(50,50)附近的值(示例):
[[-22 -26 -27 -26 -22]
 [-26 -30 -32 -30 -26]
 [-27 -32 -33 -32 -27]
 [-26 -30 -32 -30 -26]
 [-22 -26 -27 -26 -22]]

注意:亮斑中心(50,50)附近的 LoG 响应约为 -33(负值),暗斑中心(80,80)附近约为 +17(正值)——亮斑与暗斑的响应符号相反,但绝对值都明显高于平坦背景。实际完整的斑点检测算法还需要在多个σ尺度下计算,并执行非极大值抑制来精确定位。

📝 动手练一练

  1. 概念辨析:角点和斑点都是图像中“与众不同”的特征,但它们背后的几何含义和检测原理有何本质区别?请从定义和检测方法(如一阶/二阶导数)的角度进行阐述。
👉 点击查看参考答案

角点本质上是两条边缘的交汇点,是一维边缘特征的聚合点。从检测原理看,哈里斯角点通过分析局部窗口在所有方向上的一阶灰度变化(梯度)来判断,关注的是变化的方向性差异。FAST角点则通过比较中心点与周围点的灰度绝对值差异来判断。

斑点本质上是被边缘包围的一个小区域,是一个封闭的二维区域特征。从检测原理看,斑点检测通常寻找二阶导数(拉普拉斯响应)的极值点。这个极值点位于斑点的中心:该处一阶梯度为零(灰度平缓),二阶导数取极值;其外围则由二阶导数的过零点(边缘)所包围。

简单来说:角点是“线”的焦点,斑点是小“面”的核心。

  1. 算法思考:FAST角点检测算法中,通常要求连续12个像素点满足条件。如果我想让算法对更“钝”的角(比如120度的角)也敏感一些,应该调大还是调小这个连续点数 n?为什么?同时,n 的值能否小于等于8?为什么?
👉 点击查看参考答案

应该调小连续点数 n。对于一个更钝的角,围绕其顶点画圆时,与顶点颜色明显不同的像素点所占的圆弧比例会小于3/4(即小于12个)。降低 n 值可以使得这种包含更少连续“不同点”的模式也能被检测为角点。

n 的值不能小于等于8。因为当 n=8 时,意味着只要一半的圆周像素点满足条件即可。但这正好对应了边缘的情况——一条直线边缘穿过圆心时,恰好会有一半的圆周点在一侧,另一半在另一侧。如果 n<=8,会导致大量的边缘点被误检为角点,破坏了角点作为“两条边交汇”的定义。因此,n 必须大于8,以保证检测到的是真正的角状结构。

本章小结

本节我们深入探讨了深度学习时代之前,传统计算机视觉中两种核心的底层几何特征:角点与斑点。

  • 角点是图像中灰度在多个方向发生剧烈变化的点,是边缘的交汇处。哈里斯角点通过分析局部梯度矩阵的特征值来稳健地检测角点,并可通过高斯窗实现多尺度检测。FAST角点则以惊人的速度,通过直接比较像素灰度来定位角点,但其在尺度与旋转不变性上有所牺牲。
  • 斑点是与周围区域对比鲜明的封闭区域,其中心对应二阶导数(拉普拉斯响应)的极值点。通过高斯拉普拉斯滤波器在不同尺度下检测极值,我们可以找到图像中各种大小的斑点结构。

理解这两种特征的提取原理,是掌握SIFT、SURF等经典局部特征描述子的必经之路。它们代表了在深度学习统治之前,研究者们如何从图像中提取稳定、鲁棒的特征来理解和匹配视觉世界。

📋 行动清单

  • 使用OpenCV的 cv2.cornerHarris 和 cv2.FastFeatureDetector_create() 函数,分别对同一张图片进行角点检测,直观对比两者的检测结果和速度差异。
  • 尝试用 cv2.GaussianBlur 配合 cv2.Laplacian,对一张有明显纹理或斑点的图片(如棋盘格、斑点狗图片)进行处理,观察LoG响应图,理解斑点对应的极值点。
  • 思考:在图像拼接应用中,为什么角点比单纯的边缘点更适合作为匹配的特征点?

—— 小象教研组