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

基于颜色相似性

基于颜色相似性的图像检索

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

在图像检索任务中,除了关注物体的形状和纹理,颜色本身就是一个非常直观且强大的检索线索。想象一下,你想在电商平台找一件“酒红色”的毛衣,或者在相册里找出所有“夕阳”主题的照片,颜色相似性检索就能派上用场。本节我们将学习如何利用颜色直方图来量化图像的颜色特征,并引入符合人眼视觉感知的色差度量方法,最终通过“推土机距离”来比较图像之间的颜色相似度,从而实现精准的检索。

💡 核心导读

  • 颜色特征提取:学习如何将一张图像的丰富色彩信息,通过颜色直方图或聚类直方图,压缩成一个高维特征向量。
  • Lab 颜色空间:理解为何 RGB 空间不适合直接度量颜色差异,以及 Lab 颜色空间如何更好地模拟人眼视觉感知。
  • 色差度量 CIEDE2000:了解用于度量 Lab 空间中两个颜色视觉相似度的标准方法,它考虑了人眼对不同颜色的敏感度差异。
  • 推土机距离 (EMD):掌握如何将两个颜色直方图之间的相似度比较,转化为一个经典的“运输问题”,并求解最小成本。
  • 检索流程实战:从特征提取到相似度计算与排序,完整走一遍基于颜色相似性的图像检索流程。

颜色特征提取:从像素到直方图

图像检索的第一步是特征提取。对于颜色特征,最经典的方法就是颜色直方图。

颜色直方图统计的是一幅图像中各种颜色出现的频率。如果我们把 RGB 图像中每一个可能的 (R, G, B) 组合都看作一种颜色,那么直方图的横坐标就是所有可能的颜色,纵坐标就是该颜色在图像中出现的像素数量。这样,一张图像就被表示成了一个高维度的特征向量。

import numpy as np
import cv2

# 示例:构造一个简单的3x3 RGB图像,并计算其颜色直方图(简化版)
# 假设颜色空间被量化为8个等级(0-31, 32-63, ..., 224-255),那么共有8^3=512种颜色
# 这里我们手动统计一个极简的例子

# 构造一个3x3的RGB图像,每个像素值在[0, 255]之间
# 为了演示,我们使用整数并假设已经量化到8个等级(实际计算时会先量化)
image_data = np.array([
    [[200, 50, 50], [50, 200, 50], [50, 50, 200]],
    [[200, 200, 50], [50, 200, 200], [200, 50, 200]],
    [[100, 100, 100], [150, 150, 150], [200, 200, 200]]
], dtype=np.uint8)

print("原始图像像素值(3x3x3):")
print(image_data)
print()

# 量化:将0-255范围压缩到0-7(8个等级)
# 实际应用中,量化等级可以是16、32等,这里用8级简化演示
quantized = image_data // 32  # 256/8=32
print("量化后的像素值(等级0-7):")
print(quantized)
print()

# 将三维颜色索引转换为一维直方图索引
# 公式:index = R * (bins^2) + G * bins + B,其中bins=8
bins = 8
hist_index = (quantized.astype(np.int32)[:, :, 0] * (bins**2)
              + quantized.astype(np.int32)[:, :, 1] * bins
              + quantized.astype(np.int32)[:, :, 2])

print("每个像素对应的一维直方图索引:")
print(hist_index)
print()

# 计算直方图(统计每个索引出现的次数)
hist, _ = np.histogram(hist_index.flatten(), bins=range(bins**3 + 1))
print(f"颜色直方图向量(长度为{bins**3}={bins**3}):")
# 只打印非零的部分以节省空间
non_zero_indices = np.where(hist > 0)[0]
for idx in non_zero_indices:
    # 将一维索引解码回RGB等级
    r = idx // (bins**2)
    g = (idx % (bins**2)) // bins
    b = idx % bins
    print(f"  颜色等级(R={r}, G={g}, B={b}) 出现次数: {hist[idx]}")
原始图像像素值(3x3x3):
[[[200  50  50]
  [ 50 200  50]
  [ 50  50 200]]

 [[200 200  50]
  [ 50 200 200]
  [200  50 200]]

 [[100 100 100]
  [150 150 150]
  [200 200 200]]]

量化后的像素值(等级0-7):
[[[6 1 1]
  [1 6 1]
  [1 1 6]]

 [[6 6 1]
  [1 6 6]
  [6 1 6]]

 [[3 3 3]
  [4 4 4]
  [6 6 6]]]

每个像素对应的一维直方图索引:
[[393 113  78]
 [433 118 398]
 [219 292 438]]

颜色直方图向量(长度为512=512):
  颜色等级(R=1, G=1, B=6) 出现次数: 1
  颜色等级(R=1, G=6, B=1) 出现次数: 1
  颜色等级(R=1, G=6, B=6) 出现次数: 1
  颜色等级(R=3, G=3, B=3) 出现次数: 1
  颜色等级(R=4, G=4, B=4) 出现次数: 1
  颜色等级(R=6, G=1, B=1) 出现次数: 1
  颜色等级(R=6, G=1, B=6) 出现次数: 1
  颜色等级(R=6, G=6, B=1) 出现次数: 1
  颜色等级(R=6, G=6, B=6) 出现次数: 1

从输出可以看到,我们的小图像包含了9种不同的颜色(对应9个非零的直方图条目)。然而,直接使用所有可能的颜色(例如256^3约1678万种)会导致直方图维度爆炸,且许多颜色可能根本不出现。为了解决这个问题,我们通常采用两种策略:

  1. 颜色量化:将连续的RGB颜色空间划分为有限个区间(如8x8x8=512个),将落在同一区间的颜色视为同一种。
  2. 颜色聚类:使用聚类算法(如K-means++)对图像中的所有像素颜色进行聚类,用聚类中心代表主要的颜色成分,形成颜色聚类直方图。这尤其适用于Lab这类颜色数量不可枚举的颜色空间。

Lab颜色空间与视觉感知均匀性

为什么我们更倾向于在 Lab 颜色空间 中进行颜色相似度的计算,而不是直接使用 RGB 空间?

RGB 颜色模型是基于硬件(如显示器发光)的,其三个分量(红、绿、蓝)的欧氏距离并不能很好地反映人眼感知到的颜色差异。例如,人眼对绿色色调的细微变化不敏感,但对蓝色色调的微小变化却非常敏感。

Lab 颜色空间 是由国际照明委员会(CIE)制定的,旨在数字化地描述人眼的视觉感应,它是一个感知上更均匀的颜色空间。在Lab空间中:

  • L 分量代表明度(Lightness),范围从0(纯黑)到100(纯白)。
  • a 分量代表从绿色(负值)到红色(正值)的色轴。
  • b 分量代表从蓝色(负值)到黄色(正值)的色轴。

Lab 空间能描述正常人眼可见的所有颜色,其范围比 RGB 更广。更重要的是,在 Lab 空间中,两个颜色点之间的欧氏距离,比在 RGB 空间中更能反映人眼感知到的色差。

然而,即使是 Lab 空间,其均匀性仍不完美。因此,CIE 制定了更复杂的色差公式,如 CIEDE2000,它进一步修正了不同颜色区域人眼敏感度的差异,是目前工业界度量小色差的标准。

import numpy as np
import cv2

# 演示RGB到Lab空间的转换
# 注意:OpenCV 的 8 位 Lab 编码为 L: [0, 255](对应 L* 0~100),a/b: [0, 255](偏移 128)

# 颜色1/2:绿色通道 +30;颜色3/4:蓝色通道 +30
color1_rgb = np.uint8([[[0, 100, 0]]])
color2_rgb = np.uint8([[[0, 130, 0]]])
color3_rgb = np.uint8([[[0, 0, 100]]])
color4_rgb = np.uint8([[[0, 0, 130]]])

# 转换到Lab空间
color1_lab = cv2.cvtColor(color1_rgb, cv2.COLOR_RGB2Lab).squeeze()
color2_lab = cv2.cvtColor(color2_rgb, cv2.COLOR_RGB2Lab).squeeze()
color3_lab = cv2.cvtColor(color3_rgb, cv2.COLOR_RGB2Lab).squeeze()
color4_lab = cv2.cvtColor(color4_rgb, cv2.COLOR_RGB2Lab).squeeze()

print("颜色值对比(OpenCV 8位 Lab 编码):")
print(f"颜色1 (深绿) - RGB: {color1_rgb.squeeze()}, Lab: {color1_lab}")
print(f"颜色2 (浅绿) - RGB: {color2_rgb.squeeze()}, Lab: {color2_lab}")
print(f"颜色3 (深蓝) - RGB: {color3_rgb.squeeze()}, Lab: {color3_lab}")
print(f"颜色4 (浅蓝) - RGB: {color4_rgb.squeeze()}, Lab: {color4_lab}")
print()
print("可以看到:RGB 中同样是 +30 的变化,在 Lab 的 L、a、b 三个分量上并不等价;")
print("因此不能直接用 RGB 数值差来代表视觉色差,需要在 Lab 上再用 CIEDE2000 做加权。")
颜色值对比(OpenCV 8位 Lab 编码):
颜色1 (深绿) - RGB: [  0 100   0], Lab: [ 92  85 170]
颜色2 (浅绿) - RGB: [  0 130   0], Lab: [120  76 178]
颜色3 (深蓝) - RGB: [  0   0 100], Lab: [ 21 167  74]
颜色4 (浅蓝) - RGB: [  0   0 130], Lab: [ 34 176  63]

可以看到:RGB 中同样是 +30 的变化,在 Lab 的 L、a、b 三个分量上并不等价;
因此不能直接用 RGB 数值差来代表视觉色差,需要在 Lab 上再用 CIEDE2000 做加权。

注意:OpenCV 的 8 位 Lab 是编码值(L 按 255/100 缩放,a/b 偏移 128),并非直接的 Lab* 物理量;本示例只演示 RGB→Lab 的转换,不直接比较欧氏距离。真正的 CIEDE2000 会在 Lab 上引入对明度(L)、彩度(C)和色相(h)的加权函数,以及针对中性色、蓝色区域等的修正项,使其与大规模人眼视觉实验高度吻合。其公式较复杂,我们只需知道它是当前度量小色差的工业标准即可。

推土机距离:比较颜色直方图

当我们有了两幅图像的颜色直方图(或聚类直方图)后,如何度量它们的相似度?直接计算直方图的欧氏距离或余弦相似度可能不合适,因为它没有考虑颜色本身之间的相似性。例如,一个以深红色为主的直方图和一个以浅红色为主的直方图,在颜色内容上应该是相似的,但如果把红色和蓝色当作完全无关的维度,它们的距离就会很大。

推土机距离(Earth Mover’s Distance, EMD),又称 Wasserstein 距离,完美地解决了这个问题。它将两个分布之间的差异度量,转化为一个经典的运输问题:

想象有两个土堆分布,代表第一幅图像的颜色直方图;另有一些土坑,代表第二幅图像的颜色直方图。每个土堆的土量是某种颜色的频率,每个土坑的容量是另一种颜色的频率。将土从土堆运到土坑需要成本,成本就是两种颜色之间的色差(例如用CIEDE2000计算)。EMD 要解决的问题是:找到一种运输方案,以最小的总成本将所有的土从土堆填入土坑。这个最小总成本就是 EMD 距离。

EMD 的优势在于它同时考虑了分布“形状”的差异和基础元素(颜色)之间的相似性。即使两个直方图的峰值没有严格对齐,只要它们的颜色在视觉上是相近的,EMD 距离也会很小。

import numpy as np

# 简化版EMD计算演示(仅用于理解概念,实际使用需调用优化库如`pyemd`)
# 假设我们有两幅图像的简化颜色直方图,每种颜色用一个聚类中心代表(这里用一维索引简化)
# 以及一个预定义的颜色距离矩阵。

# 图像A的颜色直方图 (土堆)
hist_a = np.array([0.5, 0.3, 0.2])  # 三种颜色,总质量为1
# 图像B的颜色直方图 (土坑)
hist_b = np.array([0.4, 0.4, 0.2])  # 三种颜色,总容量为1

# 颜色距离矩阵 (假设是CIEDE2000距离,这里用简化的数字示意)
# D[i][j] 表示将颜色i的土运到颜色j的坑的成本
distance_matrix = np.array([
    [0.0, 2.0, 5.0],  # 颜色0到颜色0,1,2的成本
    [2.0, 0.0, 3.0],  # 颜色1到颜色0,1,2的成本
    [5.0, 3.0, 0.0],  # 颜色2到颜色0,1,2的成本
])

print("图像A直方图(土堆):", hist_a)
print("图像B直方图(土坑):", hist_b)
print("颜色距离矩阵:")
print(distance_matrix)
print()

# 手动给出一个运输方案(后文可验证:它恰好就是最优方案)
# 方案:尽量将土运到同色坑,缺口再按最低单价补
# 从颜色0运0.4到颜色0(成本0),运0.1到颜色1(成本2)
# 从颜色1运0.3到颜色1(成本0)
# 从颜色2运0.2到颜色2(成本0)
# 注意:颜色1的坑还差0.1,由颜色0运来的0.1填补。

flow = np.array([
    [0.4, 0.1, 0.0],
    [0.0, 0.3, 0.0],
    [0.0, 0.0, 0.2]
])

print("假设的土方运输流量矩阵(flow[i][j]):")
print(flow)
print("检查: 每行之和应等于hist_a")
print("  行和:", flow.sum(axis=1))
print("检查: 每列之和应等于hist_b")
print("  列和:", flow.sum(axis=0))
print()

# 计算该方案的总成本
total_cost = np.sum(flow * distance_matrix)
print(f"该运输方案的总成本: {total_cost}")
print("可以验证:颜色1的坑需要0.4,而同色土堆只有0.3,缺口0.1只能从颜色0(单位成本2)")
print("或颜色2(单位成本3)补足,所以任何可行方案的成本都不低于 0.1*2=0.2。")
print("因此上面这个flow恰好就是最优运输方案,0.2 就是这两幅图像颜色直方图的真实EMD。")
图像A直方图(土堆): [0.5 0.3 0.2]
图像B直方图(土坑): [0.4 0.4 0.2]
颜色距离矩阵:
[[0. 2. 5.]
 [2. 0. 3.]
 [5. 3. 0.]]

假设的土方运输流量矩阵(flow[i][j]):
[[0.4 0.1 0. ]
 [0.  0.3 0. ]
 [0.  0.  0.2]]
检查: 每行之和应等于hist_a
  行和: [0.5 0.3 0.2]
检查: 每列之和应等于hist_b
  列和: [0.4 0.4 0.2]

该运输方案的总成本: 0.2
可以验证:颜色1的坑需要0.4,而同色土堆只有0.3,缺口0.1只能从颜色0(单位成本2)
或颜色2(单位成本3)补足,所以任何可行方案的成本都不低于 0.1*2=0.2。
因此上面这个flow恰好就是最优运输方案,0.2 就是这两幅图像颜色直方图的真实EMD。

完整检索流程与实战总结

结合以上知识点,基于颜色相似性的图像检索流程可以归纳为以下四步:

  1. 特征提取:对于查询图像和图像库中的所有图像,提取其颜色特征。通常做法是:

    • 将图像转换到 Lab 颜色空间。
    • 对像素的 Lab 值进行聚类(如 K-means++),得到 K 个主要颜色簇。
    • 统计每个簇中的像素数量,归一化后得到 K 维的颜色聚类直方图。
  2. 特征存储:将图像库中所有图像的颜色直方图特征预先计算并存储起来。

  3. 相似度计算:

    • 当用户提交查询图像时,同样提取其颜色直方图。
    • 计算查询图像直方图与库中每个图像直方图之间的 EMD 距离。
    • EMD 计算中需要的基础颜色距离,使用 CIEDE2000 公式计算聚类中心之间的色差。
  4. 结果排序:根据 EMD 距离由小到大对库中图像进行排序。EMD 距离越小,表示两幅图像的颜色组成在视觉上越相似,排名就越靠前。

这个过程的核心思想是:用符合人眼视觉的色差度量(CIEDE2000)作为“运费单价”,用推土机距离(EMD)作为“总运费”来计算两幅图像颜色分布的差异,从而实现以人类感知为基础的相似性排序。

📝 动手练一练

  1. 概念辨析:在基于颜色直方图的检索中,为什么使用 EMD 距离比使用欧氏距离更合理?请从直方图比较的本质和颜色相似性两个角度阐述。
👉 点击查看参考答案

从直方图比较的本质看:直方图本质上是概率分布。欧氏距离计算的是两个向量在每个维度上的数值差异,它假设所有维度是独立且正交的。对于直方图,这意味着它把“红色增多”和“蓝色减少”视为完全独立的事件,忽略了分布的整体形态和对应关系。而 EMD 距离将比较视为一个分布匹配问题,寻找一个最小成本的“流动”方案将一个分布变成另一个,更能反映两个分布整体的相似性。

从颜色相似性看:欧氏距离在颜色直方图的应用中,隐含假设了“颜色索引i和颜色索引j之间的距离是固定的”(例如,索引1代表红色,索引2代表蓝色,它们的距离就是1)。但这不符合视觉事实,深红和浅红是相似的,深红和深蓝则差异巨大。EMD 距离允许我们定义一个更合理的颜色距离矩阵(如使用 CIEDE2000),在这个基础上计算分布匹配的最小成本,从而将颜色本身的视觉相似性融入到了相似度度量中。

  1. 流程理解:假设我们有一个包含3种主色(聚类中心)的图像库,查询图像Q的直方图为 [0.6, 0.3, 0.1],库中图像A的直方图为 [0.5, 0.4, 0.1],图像B的直方图为 [0.1, 0.3, 0.6]。已知颜色距离矩阵中,颜色0和颜色1很接近(距离小),颜色0和颜色2差异很大(距离大)。在不进行精确计算的情况下,请判断查询图像Q更可能与图像A还是图像B更相似?为什么?
👉 点击查看参考答案

查询图像Q更可能与图像A更相似。

推理过程:

  • 比较Q和A:两者的直方图形状相似,主要质量都集中在颜色0和颜色1上,且颜色0和1本身很接近。因此,将Q的“土”运到A的“坑”时,大部分土(颜色0和1)可以以很低的成本运到相同或相近颜色的坑里,总成本(EMD)会很小。
  • 比较Q和B:两者的直方图形状几乎相反。Q的质量主要在颜色0,而B的质量主要在颜色2,且颜色0和2差异巨大。为了将Q的分布“变成”B的分布,需要将大量“颜色0的土”以极高的成本运到“颜色2的坑”里,总成本(EMD)会非常大。
  • 因此,EMD距离 D(Q, A) < D(Q, B),Q与A更相似。

本章小结

本节我们深入探讨了基于颜色相似性的图像检索技术。我们了解到,简单的颜色统计(直方图)是强大的特征,但为了使其有效,必须考虑人眼的视觉特性。这引导我们使用 Lab 颜色空间和 CIEDE2000 色差公式来度量颜色间的感知差异。最后,通过推土机距离(EMD),我们能够以一种同时考虑分布形态和元素相似性的方式,来比较两幅图像的整体颜色构成,从而实现符合人类直觉的图像检索。

📋 行动清单

  • 理解颜色空间转换的意义:动手写几行代码,将 RGB 图像转换为 Lab 空间,观察三个通道的含义。
  • 查阅 CIEDE2000 公式:虽然无需记忆,但可以浏览其 Wikipedia 页面,了解它为了解决人眼感知不均匀性做了哪些修正(如亮度、彩度、色相加权,蓝色区域修正等)。
  • 思考 EMD 的应用:除了颜色直方图,EMD 还可以用于比较哪些类型的特征分布?(例如:纹理基元分布、词袋模型中的视觉单词分布等)。

—— 小象教研组