查看全课大纲(第 51 / 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/SURF等特征描述子,学习如何通过计算两幅图像间特征点的匹配对数来衡量图像相似性。
- 视觉词袋模型:借鉴自然语言处理中的词袋思想,将图像中的局部特征聚类为“视觉单词”,从而将图像表示为一个词频向量。
- 从局部到语义:理解视觉词袋模型如何将底层局部特征聚合,形成对图像内容更宏观、更具语义信息的描述,提升检索的准确性。
局部特征相似性检索
在计算机视觉中,局部特征指的是图像中那些具有显著性的点或区域,例如角点、边缘交点、斑点等。这些特征通常对光照变化、尺度缩放、旋转甚至轻微的视角变化具有较好的不变性。
局部特征提取:SIFT与SURF
我们之前详细介绍过SIFT(尺度不变特征变换)和SURF(加速稳健特征)等算法。它们的核心流程分为两步:
- 特征点检测:在图像的不同尺度空间上,寻找稳定的极值点作为特征点。每个特征点有其位置 和尺度 。
- 特征描述:以特征点为中心,计算其邻域内像素梯度的方向直方图,形成一个高维的描述向量(如SIFT为128维)。
import cv2
import numpy as np
# 示例:使用OpenCV的SIFT检测器(需OpenCV >= 4.4.0)
# 注意:此处仅为演示API,实际检索中会提取大量特征点
np.random.seed(42)
# 构造一个简单的8x8灰度“角点”图案作为示例图像
demo_img = np.zeros((8, 8), dtype=np.uint8)
demo_img[2:6, 2:6] = 100 # 一个亮块
demo_img[4, 4] = 255 # 中心最亮,模拟角点
# 创建SIFT检测器
sift = cv2.SIFT_create()
# 检测关键点并计算描述子
keypoints, descriptors = sift.detectAndCompute(demo_img, None)
print(f"在示例图像中检测到 {len(keypoints)} 个关键点。")
if len(keypoints) > 0:
# 打印第一个关键点的部分信息(位置、尺度)
kp = keypoints[0]
print(f"第一个关键点: 位置 ({kp.pt[0]:.1f}, {kp.pt[1]:.1f}), 尺度 {kp.size:.2f}")
# 打印其描述子的前10个维度(共128维)
if descriptors is not None:
print(f"描述子前10维: {descriptors[0, :10]}")在示例图像中检测到 5 个关键点。
第一个关键点: 位置 (4.1, 4.1), 尺度 2.34
描述子前10维: [0. 0. 0. 0. 0. 0. 0. 1. 0. 0.]除了基础的SIFT,还有许多改进版本,如考虑颜色的Color-SIFT、适应仿射变换的Affine-SIFT、密集采样的Dense-SIFT以及使用主成分分析降维的PCA-SIFT等,它们都是为了在特定条件下获得更好的特征表示。
基于特征匹配的图像相似性
得到两幅图像的特征点集及其描述子后,如何衡量图像的相似性?一个直观的方法是进行特征匹配。
核心思想:对于图像A中的每一个特征点,在图像B的所有特征点中寻找其描述子距离(如欧氏距离)最近的点。如果这个最近距离小于某个阈值,且与次近距离的比值也满足一定条件(如Lowe’s ratio test),则认为这两个点是一对匹配点。最终,两幅图像之间的匹配点对数可以作为它们相似性的一个度量。
双向匹配:为了增加匹配的可靠性,可以进行双向检查。即不仅从A到B寻找匹配,也从B到A寻找匹配,只保留那些互为最近邻的匹配对。
import numpy as np
# 模拟两幅图像的SIFT描述子(为了演示,我们构造简单的数据)
# 假设每幅图像提取了3个128维的特征
np.random.seed(123)
# 图像A的特征
descriptors_A = np.random.randn(3, 128)
# 图像B的特征:构造两个与A相似,一个随机
descriptors_B = np.vstack([
descriptors_A[0] + 0.01 * np.random.randn(128), # 与A[0]相似
descriptors_A[1] + 0.01 * np.random.randn(128), # 与A[1]相似
np.random.randn(128) # 随机特征
])
def match_features(desc1, desc2, ratio_thresh=0.8):
"""简单的最近邻匹配(演示原理,非最优实现)"""
matches = []
for i, d1 in enumerate(desc1):
# 计算d1与desc2中所有特征的距离
distances = np.linalg.norm(desc2 - d1, axis=1)
# 获取最近和次近的距离索引
idx_sorted = np.argsort(distances)
nearest_idx, second_nearest_idx = idx_sorted[0], idx_sorted[1]
nearest_dist, second_nearest_dist = distances[nearest_idx], distances[second_nearest_idx]
# Lowe's ratio test: 最近距离/次近距离 < 阈值
if nearest_dist < ratio_thresh * second_nearest_dist:
matches.append((i, nearest_idx))
return matches
# 进行匹配
matched_pairs = match_features(descriptors_A, descriptors_B)
print(f"匹配到的特征点对数量: {len(matched_pairs)}")
for pair in matched_pairs:
print(f" 图像A的特征点 {pair[0]} <-> 图像B的特征点 {pair[1]}")匹配到的特征点对数量: 2
图像A的特征点 0 <-> 图像B的特征点 0
图像A的特征点 1 <-> 图像B的特征点 1这种方法对于颜色差异大但结构相似的图像(如不同光照下的同一件衬衫、不同角度拍摄的同一建筑物)非常有效。匹配点越多,表明两幅图像在局部结构上越相似。
视觉词袋模型:从局部特征到视觉单词
基于特征点直接匹配的方法虽然有效,但当图像库非常庞大时,逐对比较所有特征点的计算成本很高。此外,这种方法更关注局部几何一致性,缺乏对图像整体语义内容的宏观描述。
视觉词袋模型(Bag of Visual Words, BoVW)正是为了解决这些问题而提出的。它借鉴了自然语言处理中词袋模型的思想。
自然语言处理中的词袋模型
在NLP中,词袋模型忽略文本的语法和语序,仅将文档表示为其包含的单词出现的频率向量。
例如,有两个句子:
- “John likes to watch movies. Mary likes movies too.”
- “John also likes to watch football games.”
首先构建一个词典(所有不重复的单词): [‘John’, ‘likes’, ‘to’, ‘watch’, ‘movies’, ‘Mary’, ‘too’, ‘also’, ‘football’, ‘games’]
然后,每个句子就可以表示为一个10维的词频向量:
- 句子1:
[1, 2, 1, 1, 2, 1, 1, 0, 0, 0] - 句子2:
[1, 1, 1, 1, 0, 0, 0, 1, 1, 1]
这样,复杂的文本就被压缩成了固定长度的向量,便于后续的相似度计算(如计算余弦相似度)。
构建视觉词典
在计算机视觉中,我们将图像类比为文档,将局部特征(如SIFT描述子)类比为单词。但是,图像中的“单词”不是现成的,我们需要从图像集合中“学习”出来。
构建视觉词典的步骤:
- 特征提取:对于一个包含 M 幅图像的数据集,对每幅图像提取大量(比如几百上千个)局部特征描述子(如128维SIFT)。假设总共提取了 N 个特征。
- 特征聚类:使用聚类算法(最常用的是K-means)对这 N 个高维特征向量进行聚类。设定聚类数目为 K(即词典大小)。
- 生成词典:聚类后得到的 K 个聚类中心,就是我们要的 K 个视觉单词。这 K 个中心向量构成了我们的“视觉词典”或“码本”。
import numpy as np
# ========== 步骤1 & 2: 模拟特征提取与聚类(构建视觉词典) ==========
np.random.seed(42)
# 假设我们从3类物体(人脸、自行车、提琴)的图像中提取了共60个128维特征
# 为了演示,我们简化:用3个高斯分布生成3类特征,每类20个
num_features_per_class = 20
dim = 128 # SIFT描述子维度
# 生成3类特征的中心(模拟3类视觉模式)
class_centers = np.array([
[1.0] * dim, # 类1中心
[0.0] * dim, # 类2中心
[-1.0] * dim # 类3中心
])
all_features = []
for center in class_centers:
# 围绕每个中心生成一些特征点,加入噪声
features = center + 0.5 * np.random.randn(num_features_per_class, dim)
all_features.append(features)
all_features = np.vstack(all_features) # 形状 (60, 128)
print(f"总共提取了 {all_features.shape[0]} 个特征,每个特征维度 {all_features.shape[1]}")
# 使用K-means聚类(这里使用简化的模拟,实际应用中使用cv2.kmeans或sklearn)
# 为演示,我们直接指定聚类中心为预设的类中心(实际是迭代求出的)
visual_vocabulary = class_centers # 这就是我们的视觉词典,K=3
print(f"视觉词典(视觉单词)的形状: {visual_vocabulary.shape} (K={visual_vocabulary.shape[0]})")总共提取了 60 个特征,每个特征维度 128
视觉词典(视觉单词)的形状: (3, 128) (K=3)重要理解:视觉单词(聚类中心)并没有一个像“眼睛”、“车轮”这样明确的、人类可解释的物理含义。它只是一个高维空间中的点,代表了某一种频繁出现的局部特征模式。课件中用“眼睛”、“鼻子”等图示仅仅是为了方便解释。
图像编码与检索
构建好视觉词典后,对于任何一幅新的图像(包括数据库中的图像和查询图像),我们都可以将其编码为一个词频向量:
- 提取该图像的所有局部特征。
- 对于每一个特征,计算它与视觉词典中所有 K 个视觉单词的距离。
- 将该特征分配给距离最近的那个视觉单词。
- 统计整幅图像中,每个视觉单词被分配到的次数,形成一个 K 维的词频直方图。这个直方图就是该图像的BoVW表示。
import numpy as np
# 为保持代码块自包含,重新构造与上文相同的视觉词典
np.random.seed(42)
dim = 128
class_centers = np.array([
[1.0] * dim,
[0.0] * dim,
[-1.0] * dim
])
visual_vocabulary = class_centers
# ========== 步骤3: 将图像编码为词频直方图 ==========
def encode_image(features, vocabulary):
"""将一幅图像的特征编码为视觉单词词频直方图。"""
K = len(vocabulary)
histogram = np.zeros(K, dtype=int)
for feat in features:
# 计算特征到每个视觉单词的距离
distances = np.linalg.norm(vocabulary - feat, axis=1)
# 找到最近的视觉单词的索引
nearest_word_idx = np.argmin(distances)
# 对应词频加1
histogram[nearest_word_idx] += 1
return histogram
# 模拟三幅测试图像的特征
# 图像1:主要包含“类1”模式
img1_feats = class_centers[0] + 0.3 * np.random.randn(15, dim)
# 图像2:主要包含“类2”模式
img2_feats = class_centers[1] + 0.3 * np.random.randn(15, dim)
# 图像3:包含“类1”和“类3”模式
img3_feats = np.vstack([
class_centers[0] + 0.3 * np.random.randn(8, dim),
class_centers[2] + 0.3 * np.random.randn(7, dim)
])
# 编码图像
hist1 = encode_image(img1_feats, visual_vocabulary)
hist2 = encode_image(img2_feats, visual_vocabulary)
hist3 = encode_image(img3_feats, visual_vocabulary)
print("图像1的词频直方图:", hist1)
print("图像2的词频直方图:", hist2)
print("图像3的词频直方图:", hist3)图像1的词频直方图: [15 0 0]
图像2的词频直方图: [ 0 15 0]
图像3的词频直方图: [8 0 7]现在,每幅图像都被表示为一个固定的 K 维向量。图像检索就变成了在向量空间中寻找最近邻的问题:
- 将查询图像编码为词频向量。
- 计算该向量与图像库中所有图像向量的距离(如 L1、L2 距离)或相似度(如余弦相似度)。
- 按距离由近到远(用相似度时则按由大到小)排序,返回最相似的图像。
BoVW模型的优势在于:
- 计算高效:将数量不定的局部特征压缩为固定维度的向量,大大减少了相似度比较的计算量。
- 更具语义:词频直方图反映了图像中不同视觉模式的统计分布,比单纯的局部匹配更能捕捉图像的整体和语义内容。
- 对局部噪声更鲁棒:个别错误匹配的特征点对整体词频分布影响较小。
📝 动手练一练
- 概念辨析:基于局部特征点直接匹配的图像检索,与基于视觉词袋模型的检索,核心思想有何不同?各自适用于什么场景?
👉 点击查看参考答案
- 直接匹配:核心思想是计算两幅图像间可匹配的局部特征点对数。它侧重于图像间局部几何结构的一致性,对于存在视角变化、遮挡但局部结构相似的图像非常有效。适用于精确匹配、图像拼接、运动结构恢复等场景。
- 视觉词袋模型:核心思想是将局部特征量化为有限的视觉单词,用图像的词频分布来表示图像。它侧重于图像内容的统计分布,将底层特征提升到了更宏观的“语义”层面。适用于大规模图像库的分类和检索,因为它将图像表示为固定长度的向量,便于索引和快速比较。
- 简单比喻:直接匹配像是比较两篇文章里完全相同的句子有多少;而词袋模型像是比较两篇文章用词风格和主题词分布的相似度。
- 动手计算:假设一个视觉词典只有3个单词。图像A的词频向量为
[10, 2, 0],图像B为[2, 10, 0],图像C为[0, 1, 9]。请计算图像A与B、A与C之间的L2距离(欧氏距离)。根据距离,哪两幅图像更相似?
👉 点击查看参考答案
计算L2距离:
- A与B的距离:
- A与C的距离:
A与B的距离(~11.31)小于A与C的距离(~13.49),因此图像A与图像B更相似。从向量可以看出,A和B的主要成分都集中在第一个和第二个单词上,只是权重不同;而C的分布则完全不同。
本章小结
本节我们深入探讨了基于局部特征的图像检索技术。我们首先回顾了SIFT等局部特征描述子,并学习了如何通过特征点匹配来衡量图像相似性。接着,我们引入了更强大的视觉词袋模型,它通过聚类局部特征构建视觉词典,并将图像编码为词频直方图,从而实现了从局部特征到图像整体语义描述的飞跃。这种方法不仅提高了检索效率,也增强了检索结果在语义层面的相关性。
📋 行动清单
- 使用OpenCV的SIFT接口,对两幅不同的图像提取特征并尝试进行匹配,观察匹配结果。
- 在小型图像数据集上,尝试用K-means聚类SIFT特征构建一个简易的视觉词袋模型,并比较不同图像的词频向量。
- 思考:视觉词袋模型中的“视觉单词”没有明确物理含义,这是它的缺点吗?为什么?
—— 小象教研组