查看全课大纲(第 15 / 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、SURF)的基础,理解它们的本质差异,是掌握传统图像特征提取技术的关键一步。
💡 核心导读
- 关键点的意义:理解为什么在图像大小、方向、光照变化时,我们仍能通过一些“稳定”的点来识别同一物体或场景。
- 哈里斯角点:掌握其核心思想——通过分析图像局部窗口在所有方向移动时的灰度变化,来判断该区域是平面、边缘还是角点。
- FAST角点:学习一种极其快速的角点检测算法,理解其“与众不同即角点”的直观思想及其局限性。
- 斑点特征:从二阶导数的角度,理解斑点(Blob)作为“被边缘包围的区域”的数学定义,及其与角点的根本区别。
关键点:稳定特征的基石
想象一下,我们从不同角度、不同距离、不同光照下观察同一辆玩具车。虽然图像中的车辆大小、形状、明暗都发生了变化,但我们依然能认出它是同一辆车。这是为什么呢?
一个重要的原因是,图像中存在一些稳定的局部特征点。这些点及其周围的小区域,在图像发生各种变化(如平移、旋转、尺度缩放、亮度变化)时,其本身的特性以及它与周围像素的关系,在某种程度上保持着几乎不变的性质。这种性质,我们称之为描述子的可复现性。
在传统的数字图像处理中,一个经典的思路是:先找到这些与众不同的“关键点”,然后再去描述这些关键点周围区域的特征。这个描述结果,就是我们后续会学到的“特征描述子”。今天,我们重点学习如何找到这些关键点,特别是其中的两种重要类型:角点和斑点。
找到稳定的关键点,是许多高级视觉应用的基础:
- 图像配准与拼接:将多张描述同一场景不同部分的图片对齐、拼接成一张全景图。
- 运动跟踪:在视频序列中,跟踪特定物体或特征点的运动轨迹。
- 物体识别:通过匹配不同图像中的关键点,来判断它们是否包含同一物体。
- 三维重建:从多视角图像中恢复场景的三维结构。
那么,什么样的点才算是合格的“关键点”呢?它需要具备可重复性(在不同图像中能被稳定地检测到)、显著性(与周围区域有足够区别)以及对图像变换的抗性。
哈里斯角点:基于灰度变化的检测
哈里斯角点是一种经典且广泛使用的关键点检测方法。它的核心思想非常直观:在一个图像上放置一个小的观察窗口(例如一个3x3或5x5的像素块),然后尝试将这个窗口在各个方向上进行微小的移动。
我们可以根据移动后窗口内像素灰度值的变化情况,将图像区域分为三类:
- 平坦区域:无论朝哪个方向移动窗口,窗口内的像素灰度值都几乎不变。
- 边缘区域:沿着边缘方向移动窗口,灰度值变化很小;但垂直于边缘方向移动时,灰度值会发生剧烈变化。
- 角点区域:朝任何方向移动窗口,窗口内的灰度值都会发生显著的变化。
哈里斯角点检测的目标,就是找出这些“角点区域”。
数学模型与判断
为了量化这种变化,我们建立数学模型。设窗口函数为 w(x, y)(例如矩形窗或高斯窗),窗口中心在 (x, y) 的像素灰度值为 I(x, y)。当窗口在x和y方向分别位移 u 和 v 后,窗口内像素的灰度变化总和 E(u, v) 可以近似表示为:
通过对 I(x+u, y+v) 进行泰勒展开并忽略高阶项,E(u, v) 可以转化为一个二次型:
其中,矩阵 M 是一个2x2的实对称矩阵,由图像在窗口内的导数构成:
这里 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算法的思想极其直观:如果一个像素点与其周围足够多的邻域像素点都“不一样”,那它很可能就是一个角点。
具体步骤如下:
- 以候选点
p为中心,画一个半径为3个像素的离散化的圆,这个圆的周长上有16个像素点(上、下、左、右及对角线方向)。 - 设定一个阈值
t。 - 检查这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
模拟非角点检测结果: FalseFAST角点的特点与局限性:
- 优点:计算速度极快,非常适合实时系统。
- 缺点:
- 不具备尺度不变性:它只在固定的像素半径上检测,无法检测不同尺度下的角点。而哈里斯角点通过高斯窗的尺度参数σ,具有一定的多尺度检测能力。
- 不具备旋转不变性:算法本身没有方向信息。不过,这个缺陷可以在后续生成特征描述子时通过其他方法弥补。
- 对噪声敏感:由于判断基于直接的灰度比较,噪声容易导致误检。
斑点:被边缘包围的区域
现在,我们把目光从“点”转向“块”。斑点,有时也称为斑块,是图像中另一类重要的几何特征。它与角点虽然只有一字之差,但代表的图像结构却完全不同。
我们可以从导数的角度来理解斑点。回顾边缘检测:边缘是图像灰度一阶导数的极值点,对应着二阶导数的过零点。那么,如果我们关注二阶导数的极值点,会对应什么样的图像结构呢?
想象一个深色的圆形斑点放在浅色背景上。这个斑点的边界构成了一个闭合的环,这个环就是边缘(二阶导数为零)。而在这个环的内部中心,灰度变化平缓,一阶导数为零、二阶导数(拉普拉斯响应)取极值。换句话说:边缘出现在一阶导数取极值、二阶导数过零的位置;斑点中心则出现在二阶导数取极值的位置。因此,斑点可以理解为被边缘所包围的、在局部区域内灰度均匀且与周围对比鲜明的区域。
在实际计算中,由于直接求导对噪声非常敏感,我们同样需要先进行平滑。一个标准的方法是使用高斯拉普拉斯滤波器。
- 高斯滤波:用高斯函数
G(x, y, σ)对图像进行卷积,平滑噪声。尺度参数σ决定了平滑的力度,σ越大,图像越模糊,能检测到更大尺度的斑点。 - 拉普拉斯算子:对平滑后的图像应用拉普拉斯算子
∇²(即求二阶导数之和),得到LoG响应图。 - 检测极值:在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(正值)——亮斑与暗斑的响应符号相反,但绝对值都明显高于平坦背景。实际完整的斑点检测算法还需要在多个σ尺度下计算,并执行非极大值抑制来精确定位。
📝 动手练一练
- 概念辨析:角点和斑点都是图像中“与众不同”的特征,但它们背后的几何含义和检测原理有何本质区别?请从定义和检测方法(如一阶/二阶导数)的角度进行阐述。
👉 点击查看参考答案
角点本质上是两条边缘的交汇点,是一维边缘特征的聚合点。从检测原理看,哈里斯角点通过分析局部窗口在所有方向上的一阶灰度变化(梯度)来判断,关注的是变化的方向性差异。FAST角点则通过比较中心点与周围点的灰度绝对值差异来判断。
斑点本质上是被边缘包围的一个小区域,是一个封闭的二维区域特征。从检测原理看,斑点检测通常寻找二阶导数(拉普拉斯响应)的极值点。这个极值点位于斑点的中心:该处一阶梯度为零(灰度平缓),二阶导数取极值;其外围则由二阶导数的过零点(边缘)所包围。
简单来说:角点是“线”的焦点,斑点是小“面”的核心。
- 算法思考: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响应图,理解斑点对应的极值点。 - 思考:在图像拼接应用中,为什么角点比单纯的边缘点更适合作为匹配的特征点?
—— 小象教研组