查看全课大纲(第 50 / 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

基于纹理相似性

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

在前面两节我们学习了基于颜色和形状相似性的图像检索,但颜色和轮廓只是图像的一个方面。当图像颜色差异巨大,但表面图案(如格子衬衫、木纹、织物)相似时,颜色检索就会失效。本节我们将学习如何利用纹理(texture) 这一关键视觉特征,实现“不看颜色看图案”的相似图像检索,掌握从纹理特征提取到相似度计算的完整流程。

💡 核心导读

  • 纹理是什么:理解纹理作为“小图元按规律重复排列”的视觉模式,及其反映物体表面同质性与结构信息的特点。
  • Gabor滤波器组:掌握使用多方向、多尺度的Gabor滤波器组来提取纹理特征,这是模拟生物视觉系统感知纹理的基础工具。
  • 特征降维与聚类:学习如何将高维的像素级Gabor特征,通过K-means聚类压缩成代表整图纹理分布的直方图。
  • 相似度计算与加速:了解使用推土机距离(EMD)或欧氏距离计算纹理直方图相似度,并认识KD-tree等索引技术对海量高维数据检索的加速作用。

纹理:图像的“表面密码”

纹理(Texture) 是计算机视觉中描述图像局部区域表面结构的重要特征。它指的是一种小图元或基元,按照一定的规律规则重复排列所形成的视觉模式。

这种重复性在自然界和人工制品中随处可见:

  • 自然界:树木的年轮、蛇的鳞片、沙丘的波纹。
  • 人工制品:纺织品的编织图案、墙纸的装饰花纹、程序员最爱的格子衬衫。

纹理特征具有以下几个关键属性:

  1. 反映同质现象:纹理区域内部表现出视觉上的一致性(不断重复),而区域之间则存在变化。
  2. 表征表面内在特性:纹理包含了物体表面材质、粗糙度、朝向等共有的内在信息。
  3. 蕴含结构组织信息:纹理的排列规律揭示了物体表面的微观或宏观组织结构。

基于纹理的检索,其目标就是忽略颜色、形状等干扰,找到那些在人类纹理感知上相似的图像。

Gabor滤波器:纹理特征的“提取器”

如何量化描述纹理呢?一个经典且有效的方法是使用Gabor滤波器组。Gabor滤波器因其在空域和频域同时具有良好的局部化特性,被认为能很好地模拟哺乳动物视觉皮层中简单细胞的视觉响应,非常适合用于纹理分析。

一个二维Gabor滤波器在空域可以看作是一个复正弦平面波受一个高斯函数调制的结果。其一般形式如下:

复数形式: g(x,y;λ,θ,ψ,σ,γ)=exp⁡(−x′2+γ2y′22σ2)exp⁡(i(2πx′λ+ψ))g(x, y; \lambda, \theta, \psi, \sigma, \gamma) = \exp\left(-\frac{x'^2 + \gamma^2 y'^2}{2\sigma^2}\right) \exp\left(i\left(2\pi\frac{x'}{\lambda} + \psi\right)\right)

实部(通常用于特征提取): greal(x,y;λ,θ,ψ,σ,γ)=exp⁡(−x′2+γ2y′22σ2)cos⁡(2πx′λ+ψ)g_{\text{real}}(x, y; \lambda, \theta, \psi, \sigma, \gamma) = \exp\left(-\frac{x'^2 + \gamma^2 y'^2}{2\sigma^2}\right) \cos\left(2\pi\frac{x'}{\lambda} + \psi\right)

其中:

  • (x,y)(x, y) 是空域坐标。
  • x′=xcos⁡θ+ysin⁡θx' = x \cos\theta + y \sin\theta, y′=−xsin⁡θ+ycos⁡θy' = -x \sin\theta + y \cos\theta 是旋转后的坐标。
  • λ\lambda 是正弦函数的波长,决定了滤波器的尺度(频率)。尺度大对应粗纹理,尺度小对应细纹理。
  • θ\theta 是滤波器的方向(如0°, 45°, 90°等),用于捕捉不同朝向的纹理。
  • ψ\psi 是相位偏移。
  • σ\sigma 是高斯包络的标准差。
  • γ\gamma 是空间纵横比,决定了滤波器的椭圆度。

为了全面捕捉纹理信息,我们不会只使用一个Gabor滤波器,而是使用一个滤波器组,覆盖多个尺度和多个方向。例如,一个常见的配置是:

  • 6个尺度(频率):对应不同的纹理粗细程度。
  • 8个方向:覆盖0°到157.5°,间隔22.5°。

这样,我们就得到了 6×8=486 \times 8 = 48 个不同的Gabor滤波器。

import numpy as np
import cv2

# 演示:生成一个特定尺度和方向的Gabor滤波器核(实部)
def demo_gabor_kernel():
    # 滤波器参数
    ksize = 35  # 核大小
    sigma = 5.0  # 高斯函数的标准差
    theta = np.pi / 4  # 方向,45度
    lambd = 10.0  # 正弦函数的波长
    gamma = 0.5  # 空间纵横比
    psi = 0  # 相位偏移

    # 使用OpenCV创建Gabor核
    kernel = cv2.getGaborKernel((ksize, ksize), sigma, theta, lambd, gamma, psi, ktype=cv2.CV_32F)

    # 为了演示,我们只打印核中心的一小部分值
    center = ksize // 2
    print("Gabor滤波器核中心 7x7 区域的值 (theta=45°, lambda=10):")
    # 取核中心附近7x7的区域进行展示
    display_region = kernel[center-3:center+4, center-3:center+4]
    np.set_printoptions(precision=4, suppress=True)
    print(display_region)
    return kernel

_ = demo_gabor_kernel()
Gabor滤波器核中心 7x7 区域的值 (theta=45°, lambda=10):
[[-0.6202 -0.4705 -0.1729  0.2106  0.582   0.8398  0.9139]
 [-0.4705 -0.1746  0.2149  0.5998  0.874   0.9608  0.8398]
 [-0.1729  0.2149  0.6058  0.8917  0.99    0.874   0.582 ]
 [ 0.2106  0.5998  0.8917  1.      0.8917  0.5998  0.2106]
 [ 0.582   0.874   0.99    0.8917  0.6058  0.2149 -0.1729]
 [ 0.8398  0.9608  0.874   0.5998  0.2149 -0.1746 -0.4705]
 [ 0.9139  0.8398  0.582   0.2106 -0.1729 -0.4705 -0.6202]]

纹理特征提取流程

基于Gabor滤波器组的纹理特征提取,遵循一个清晰的管道(pipeline),其核心思想是将图像中每个像素点映射为一个高维特征向量,再通过聚类统计得到整图的纹理分布。

第一步:图像预处理与Gabor滤波

  1. 灰度化:对于彩色图像,首先将其转换为灰度图。在纹理分析中,颜色信息通常被忽略,重点在于亮度模式。
  2. 多尺度多方向滤波:用准备好的48个Gabor滤波器分别与灰度图像进行卷积操作。每个滤波器会产生一个与原图同尺寸的响应图,图中每个像素值代表了原图对应位置对该特定尺度、方向滤波器的响应强度。
  3. 构建像素级特征向量:对于原图中的每一个像素位置(i,j)(i, j),我们收集它在48个响应图上的值,形成一个48维的向量 vij\mathbf{v}_{ij}。这个向量描述了该像素点所在局部区域的纹理属性。

第二步:特征降维与直方图统计

直接使用所有像素的48维向量进行图像间比较,数据量巨大且冗余。我们需要一种紧凑的表示方法。

  1. K-means聚类:我们对图像中所有像素的48维Gabor特征向量进行K-means聚类。聚类的目的是将视觉上相似的纹理模式归为同一类。聚类中心(质心)可以看作是数据集中代表性的“纹理基元”或“纹理单词”。聚类数 KK 是一个超参数,例如可以设为10,它决定了我们将纹理划分为多少种基本类型。
  2. 生成纹理直方图:聚类完成后,图像中的每个像素点都会被赋予一个类别标签(即它属于哪个聚类)。统计整幅图像中各个类别出现的频率,就得到了一个 KK 维的纹理直方图。这幅直方图就是整张图像的纹理特征表示,它描述了图像中各种纹理基元的分布情况。
import numpy as np

# 演示:模拟小图像上的纹理特征提取与聚类思想
def demo_texture_clustering_idea():
    np.random.seed(42)  # 固定随机种子,确保结果可复现
    # 1. 模拟生成一张简单的10x10“纹理图像”
    # 上半部分:粗棋盘格纹理 (类似大格子)
    # 下半部分:细棋盘格+噪声纹理 (类似细格子或杂乱纹理)
    img = np.zeros((10, 10), dtype=np.uint8)
    # 上半部分 (0-4行):2x2 像素一格的粗棋盘格(i//2、j//2 保证块之间互不重叠)
    for i in range(5):
        for j in range(10):
            img[i, j] = 200 if ((i // 2 + j // 2) % 2) == 0 else 50
    # 下半部分 (5-9行):细棋盘格+随机噪声,模拟另一种纹理
    for i in range(5, 10):
        for j in range(0, 10, 2):
            base_val = 50 if ((i//1 + j//2) % 2) == 0 else 200 # 更细的格子
            noise = np.random.randint(-20, 21)
            img[i, j] = np.clip(base_val + noise, 0, 255)
            img[i, j+1] = np.clip(base_val + noise, 0, 255)

    print("合成纹理图像 (上半部分粗棋盘格,下半部分细棋盘格+噪声):")
    print(img)
    print()

    # 2. 模拟Gabor滤波后得到“特征向量”(此处简化为像素值及其邻域)
    # 假设我们用一个非常简单的“特征”:像素本身及其上下左右四个邻域的平均值(模拟局部纹理)
    height, width = img.shape
    # 为简单起见,我们只用每个像素及其右侧和下侧像素构成一个3维“特征”
    features = []
    positions = []
    for i in range(height - 1):
        for j in range(width - 1):
            # 一个简化的3维特征:[当前像素,右侧像素,下方像素]
            feat = [img[i, j], img[i, j+1], img[i+1, j]]
            features.append(feat)
            positions.append((i, j))
    features = np.array(features, dtype=np.float32)

    # 3. 模拟K-means聚类 (K=3)
    # 定义K-means的简单迭代步骤(仅演示原理,非优化实现)
    K = 3
    # 随机初始化3个聚类中心
    np.random.seed(42)
    centroids = features[np.random.choice(len(features), K, replace=False)]
    labels = np.zeros(len(features), dtype=int)

    for _ in range(5):  # 迭代5次
        # 分配步骤:将每个点分配给最近的质心
        for idx, feat in enumerate(features):
            distances = np.linalg.norm(feat - centroids, axis=1)
            labels[idx] = np.argmin(distances)
        # 更新步骤:重新计算质心
        for k in range(K):
            if np.sum(labels == k) > 0:
                centroids[k] = features[labels == k].mean(axis=0)

    # 4. 生成纹理标签图(与原图对应位置)
    label_map = np.full((height, width), -1, dtype=int)  # -1表示边缘未处理
    for (pos, label) in zip(positions, labels):
        i, j = pos
        label_map[i, j] = label

    print("纹理聚类标签图 (-1表示图像边缘):")
    print(label_map)
    print()

    # 5. 计算纹理直方图
    hist, _ = np.histogram(labels, bins=K, range=(0, K))
    print(f"纹理特征直方图 (K={K}):", hist)
    return img, label_map, hist

img_sim, label_map_sim, hist_sim = demo_texture_clustering_idea()
合成纹理图像 (上半部分粗棋盘格,下半部分细棋盘格+噪声):
[[200 200  50  50 200 200  50  50 200 200]
 [200 200  50  50 200 200  50  50 200 200]
 [ 50  50 200 200  50  50 200 200  50  50]
 [ 50  50 200 200  50  50 200 200  50  50]
 [200 200  50  50 200 200  50  50 200 200]
 [218 218  58  58 194 194  37  37 200 200]
 [ 68  68 198 198  52  52 190 190  40  40]
 [203 203  65  65 219 219  53  53 182 182]
 [ 51  51 181 181  53  53 209 209  67  67]
 [181 181  50  50 212 212  41  41 201 201]]

纹理聚类标签图 (-1表示图像边缘):
[[ 1  0  2  2  1  0  2  2  1 -1]
 [ 0  0  2  1  0  0  2  1  0 -1]
 [ 2  2  1  0  2  2  1  0  2 -1]
 [ 2  1  0  0  2  1  0  0  2 -1]
 [ 1  0  2  2  1  0  2  2  1 -1]
 [ 0  0  2  1  0  0  2  1  0 -1]
 [ 2  1  0  0  2  1  0  0  2 -1]
 [ 0  0  2  1  0  0  2  1  0 -1]
 [ 2  1  0  0  2  1  0  0  2 -1]
 [-1 -1 -1 -1 -1 -1 -1 -1 -1 -1]]

纹理特征直方图 (K=3): [33 20 28]

从模拟结果可以看到,三种标签在上下两部分是交错分布的,并不按“上半/下半”分块:本例为了能在一屏内跑通,只用「当前像素、右侧像素、下方像素」三个灰度值当特征,它只编码局部明暗组合、不携带尺度信息,所以粗棋盘格和细棋盘格里相同的明暗组合会被归到同一类。最终的直方图 [33, 20, 28] 只说明 81 个采样位置在三个聚类中心上的分布,并不能直接解读为“某个类别代表某种纹理”;只有换成真正的 48 维 Gabor 特征,才能区分纹理的粗细与方向。

第三步:相似度计算与检索加速

得到两幅图像的纹理直方图 H1H_1 和 H2H_2 后,我们需要一个度量来计算它们之间的相似度。

  1. 相似度度量:

    • 推土机距离(Earth Mover‘s Distance, EMD):如前文颜色检索所述,EMD是一种衡量两个分布之间差异的有效方法。它将一个直方图“搬运”成另一个直方图所需的最小工作量(成本)作为距离。成本通常使用纹理聚类中心在特征空间中的欧氏距离(L2距离)来定义。EMD能更好地符合人类视觉感知。
    • 欧氏距离(L2距离):直接计算两个直方图向量之间的欧氏距离。计算更简单,但有时感知一致性不如EMD。
  2. 检索加速: 当图像库非常庞大时,对查询图像的纹理直方图与库中所有图像的直方图逐一计算相似度是低效的。此时需要索引技术来加速最近邻搜索。

    • KD-Tree:一种用于多维空间数据检索的二叉平衡树结构。它将数据点递归地按照某一维度的中值进行划分,形成树状结构,可以快速排除大量不可能成为最近邻的数据区域,从而加速搜索。本课后续章节会详细介绍KD-Tree。
    • 局部敏感哈希(LSH):另一种近似最近邻搜索技术,其核心思想是让相似的数据点以高概率映射到相同的哈希桶中。这样只需要在少数几个桶内进行精确比较即可。

工程优化:加速Gabor卷积

Gabor卷积,尤其是大尺寸的滤波器核,计算量很大。在实际工程中,通常采用两种策略进行加速:

  1. 频域计算:利用卷积定理,将空域的卷积运算转换为频域的乘法运算。通过快速傅里叶变换(FFT) 将图像和滤波器核变换到频域,相乘后再进行逆傅里叶变换(IFFT) 回空域。对于大核卷积,FFT方法通常更快。
  2. 多尺度策略优化:传统方法中,为了获取不同尺度的纹理,会使用尺寸递增的Gabor核。一个巧妙的优化策略是固定使用较小的Gabor核尺寸,转而对输入图像进行下采样(缩小)。在缩小后的图像上用固定小核进行卷积,其效果近似于在原图上用大核卷积,但计算量显著降低。例如,使用35x35的核处理一系列下采样图像,来代替直接使用137x137的大核处理原图。
# 伪代码:展示利用图像金字塔与固定小核模拟多尺度Gabor滤波的思路
# 原方法:不同尺度 = 不同大小的核
# for scale in scales:
#     kernel_size = get_kernel_size(scale) # 可能很大,如137
#     kernel = create_gabor_kernel(kernel_size, sigma, theta, lambda)
#     response = convolve_large_kernel(original_image, kernel) # 计算量大

# 优化方法:不同尺度 = 不同下采样率的图像 + 固定小核
# fixed_kernel_size = 35
# fixed_kernel = create_gabor_kernel(fixed_kernel_size, sigma, theta, lambda)
# for scale in scales:
#     scaled_img = resize(original_image, fx=1/scale_factor, fy=1/scale_factor)
#     response_small = convolve_small_kernel(scaled_img, fixed_kernel) # 计算量小
#     response = resize(response_small, size=original_image.shape) # 上采样回原尺寸

纹理检索效果与应用

基于上述流程,纹理相似性检索能够有效地找到表面图案相似的图像,即使它们在颜色、物体类别或形状上差异很大。

典型应用场景:

  • 服装面料检索:寻找具有相似格子、条纹、波点或编织纹理的服装。
  • 材质识别:区分木材、石材、织物、金属等不同材质。
  • 医学图像分析:识别组织切片中具有特定纹理模式的区域(如纤维化、癌变组织)。
  • 遥感图像分析:区分农田、森林、城市区域等具有不同纹理特征的地物。

其强大之处在于抓住了人类视觉中“模式相似性”的本质。例如,输入一张红色格子衬衫的图片,系统能够检索出蓝色、绿色甚至黑白的不同颜色的格子衬衫,因为它们共享了“格子”这一纹理模式。

📝 动手练一练

  1. 概念辨析:基于颜色的检索和基于纹理的检索,各自在什么情况下会失效?请各举一个例子说明。能否设想一种需要同时结合颜色和纹理进行检索的场景?
👉 点击查看参考答案
  • 颜色检索失效场景:当两幅图像颜色分布完全不同,但表面图案一致时。例如,一件红色格子衬衫和一件蓝色格子衬衫,颜色直方图差异很大,但纹理(格子模式)高度相似。此时仅靠颜色无法将它们关联。
  • 纹理检索失效场景:当两幅图像具有相似的颜色氛围但纹理结构迥异时。例如,一幅是夕阳下的平静湖面(平滑渐变纹理),另一幅是夕阳下的金色麦田(具有方向性的细密纹理)。两者主色调可能都是橙黄色系,颜色直方图相似,但纹理特征完全不同。
  • 结合颜色与纹理的场景:艺术品或特定商品检索。例如,寻找“梵高风格的星空画作”,既需要画面具有蓝色和黄色的主色调(颜色),也需要具有漩涡状、短笔触的独特画风(纹理)。单独使用任一特征都可能检索出不符合要求的作品。
  1. 流程思考:在纹理特征提取的K-means聚类步骤中,如果将聚类数 K 设置得非常大(例如1000),会对最终的检索效果和系统性能产生什么潜在影响?
👉 点击查看参考答案
  • 对检索效果的影响:
    • 正面:更精细的纹理划分。K值越大,聚类中心越多,能区分的纹理基元类型就越细致,可能提升对细微纹理差异的辨别能力。
    • 负面:容易过拟合,泛化能力下降。过多的类别可能导致直方图过于稀疏,两张纹理本质相似但略有差异的图像,其直方图可能因为一些微小变化落在不同聚类而显得差异很大。同时,噪声的影响也会被放大。
  • 对系统性能的影响:
    • 计算开销增大:K-means聚类过程本身迭代会更慢。特征直方图维度变高(1000维),后续计算相似度(如EMD)的时间复杂度也会增加。
    • 数据稀疏性:高维直方图面临“维数灾难”,需要更多的数据来可靠地估计分布,否则检索稳定性差。

因此,K值需要根据实际数据集的纹理复杂度和数据量,通过实验来权衡选择,通常不会设置得极端大。

本章小结

本节我们深入探讨了基于纹理相似性的图像检索技术。纹理作为描述图像局部区域重复性结构的关键特征,为我们打开了超越颜色感知的图像相似性度量之门。通过模拟生物视觉的Gabor滤波器组,我们能够提取出刻画纹理方向与尺度的多维特征。进而,通过聚类将这些高维特征压缩为紧凑的纹理直方图,并利用EMD等距离度量进行相似性比较。最后,我们还了解了KD-Tree等索引技术对于在大规模图像库中实现快速检索的必要性。

📋 行动清单

  • 理解纹理本质:能够清晰阐述纹理的定义、特征及其在图像检索中的价值。
  • 掌握Gabor滤波器原理:了解Gabor滤波器如何通过方向与尺度参数捕捉纹理信息,并知道多尺度多方向滤波器组的构成。
  • 复现核心流程:在脑海中或通过简单代码模拟纹理特征提取、聚类生成直方图、计算相似度的完整流程。
  • 思考优化策略:了解频域计算和图像下采样如何加速Gabor卷积运算。

—— 小象教研组