1. 介绍
直方图算法(Image Histogram Algorithm) 通过统计图像中各个颜色值的分布情况来提供关于图像颜色特征的信息,它可以用来衡量两张图片在颜色分布上的相似度,进而可以用来进行图像相似度的比较,因此,直方图算法是一种常用的图片相似度算法,通常是一个一维的数组(取决于使用通道的数量),其中每个元素表示特定颜色或强度值的像素数量。
关于直方图算法的一些概念:
直方图的定义
直方图是一个二维数组,通常是一维的(取决于使用通道的数量),其中每个元素表示特定颜色或强度值的像素数量。对于彩色图像,通常有多个通道,比如蓝、绿、红(BGR),因此可能有多个直方图。
直方图的统计
- 直方图的统计是通过扫描图像的每个像素来收集颜色信息的过程。
- 对于灰度图像(属于单通道),每个像素只有一个强度值,因此只有一个通道的直方图。对于彩色图像,每个像素有多个通道的值,所以需要分别统计每个通道的直方图。
- 关于单通道更详细的详细实验可见下文:讨论1#关于单通道 [0]
直方图的用途
- 直方图可以用于多种图像处理任务,包括图像增强、颜色校正、图像分割、物体检测和图像相似性比较。
- 通过分析直方图,可以获得有关图像的颜色分布、对比度、亮度等信息。
直方图均衡化
- 直方图均衡化是一种用于增强图像对比度的技术,它通过重新分配像素的强度值来拉伸或压缩直方图,以使其分布更均匀。这可以使图像中的细节更加清晰。
直方图相似度
- 直方图相似度用于比较两幅图像的相似程度。通过计算两个图像的直方图之间的差异,可以量化它们的相似性。
- 常见的直方图相似度度量包括交叉相关性(cv2.HISTCMP_INTERSECT)、卡方距离(cv2.HISTCMP_CHISQR)、相关性(cv2.HISTCMP_CORREL)、巴氏距离(cv2.HISTCMP_BHATTACHARYYA)等。
- 对于直方图比较方法的详细实验可见下文:讨论2#直方图相似度函数
颜色直方图
- 对于彩色图像,颜色直方图通常分别计算每个通道的直方图。这可以提供关于不同颜色通道的颜色分布信息,有助于颜色特征的分析。
- 对于彩色图像直方图的详细实验可见下文:讨论3#彩色图像直方图
2. 原理
直方图算法通过统计图像中不同颜色的像素数量,并以直方图的形式呈现,进而进行图像相似度的比较。
关于 bin
在一个灰度图像中,像素值的范围通常从黑色(0)到白色(255)之间变换。如果将图像的像素值范围分成 256 个 bin 格子,那么每个 bin 格子则代表一个灰度级别,其灰度值从 0 到 255。直方图中每个 bin 格子记录了对应灰度级别的像素数量,通过统计每个 bin 格子中的像素数量,就可以了解图像中不同灰度级别的分布情况。
即可看作有你 256 张灰色系色度卡,每一个灰度卡上都统计了灰度图像中所有该色度的像素数量,这样就可以直观看出灰度图像的像素在不同色度卡上的分布,然后和其它图像加以对比分析。
3. 魔法
直方图计算图片相似度的步骤:
- 图像预处理: 将目标图像转换为灰度图像或彩色图像,并根据需要进行尺寸调整。
- 计算直方图: 对于灰度图像,直方图表示不同灰度级别的像素数量。对于彩色图像,可以分别计算各个通道(如红、绿、蓝)的直方图对图像中的每个像素进行像素值的统计,以确定每个像素值的频率或数量。这通常包括遍历图像的每个像素,并将其像素值归类到相应的像素值区间(通常是0到255)。最终,得到一个表示像素值频率的直方图。
- 直方图比较: 对于两张图片的直方图,可以使用不同的距离或相似度度量方法来进行比较。常见的度量方法包括相关性、卡方距离、巴氏距离等。
- 相似度评估: 根据直方图比较的结果,计算出两张图片之间的相似度得分。
4. 实验
4.1 魔法
第一步:图像预处理
将目标图像转换为灰度图像或彩色图像,并根据需要进行尺寸调整。
1)读取原图: 首先读取我们要分析的图像。
2)图像灰度化: 如果需要计算灰度直方图,将彩色图像转换为灰度图像。这里我们使用 [0],只考虑图像灰度级别(亮度)信息,结果是一维数组。
第二步:计算直方图
对于灰度图像,直方图表示不同灰度级别的像素数量。
"""
以图搜图:图像直方图(Image Histogram)查找相似图像的原理与实现
测试环境:win10 | python 3.9.13 | OpenCV 4.4.0 | numpy 1.21.1 | matplotlib 3.7.1
实验时间:2023-10-27
实例名称:imgHistogram_v1.0.py
"""import cv2
import matplotlib.pyplot as plt
from matplotlib.font_manager import FontProperties# 目标图像素材库文件夹路径
database_dir = '../../P0_Doc/'
# 文件路径
img_path = database_dir + 'img_data/car-101.jpg'
font_path = database_dir + 'fonts/chinese_cht.ttf'# 读取图像
img = cv2.imread(img_path)
# 计算直方图
img_hist = cv2.calcHist([img], [0], None, [256], [0, 256])
print(img_hist)# 设置中文字体
font = FontProperties(fname=font_path, size=14)
# 绘制直方图
plt.plot(img_hist)
plt.title('Histogram(直方图)', fontproperties=font)
plt.xlabel('Pixel Value(像素值)', fontproperties=font)
plt.ylabel('Frequency(频率)', fontproperties=font)
# 像素分布可视化
plt.show()
输出打印:255个像素亮度
[[ 25.][ 13.][ 13.][ 25.][ 39.]......[ 798.][ 779.][ 2034.]]
像素分布可视化效果(直方图):
对于彩色图像,可以分别计算各个通道(如红、绿、蓝)的直方图。对图像中的每个像素进行像素值的统计,以确定每个像素值的频率或数量。这通常包括遍历图像的每个像素,并将其像素值归类到相应的像素值区间(通常是0到255)。最终,得到一个表示像素值频率的直方图。
img_hist = cv2.calcHist([img], [0, 1, 2], None, [8, 8, 8], [0, 256, 0, 256, 0, 256])
注:彩色直方图我们下后面详说。
cv2.calcHist
函数用于计算图像的直方图。以下是它的主要参数和说明:
img
:要计算直方图的图像。它以方括号的形式传递,允许计算多个图像的直方图。例如,[img] 表示计算单个图像的直方图,[img1, img2] 表示计算两个图像的直方图。channels
:指定要考虑的通道。这是一个通道索引列表,用于选择要计算直方图的通道。在 OpenCV 中,通常情况下,通道 0 对应于蓝色(B),通道 1 对应于绿色(G),通道2 对应于红色(R)。如果要考虑所有通道,可以使用 [0, 1, 2],而对于单通道,只会考虑图像的灰度信息,而不考虑颜色信息。使用 [0] 即可表示灰色通道,也可以使用 [1] 或 [2] 表示灰色单通道。B、G、R 单通道的对比效果见下文 讨论1#关于单通道 [0]mask
:可选参数,用于指定一个掩码图像,以便只计算掩码中非零元素对应的像素值。如果不需要掩码,可以将其设置为 None。
histSize:指定直方图的 bin 数量,即要计算的直方图的维度。它通常以方括号形式传递,例如 [256] 表示每个通道有 256 个 bin(256个色卡)。ranges
:指定像素值的范围。通常以方括号形式传递,例如 [0, 256] 表示单通道像素值的范围从 0 到 255。对于彩色图像,通常设置为 [0, 256, 0, 256, 0, 256],表示三个通道的范围。
讨论1:关于单通道 [0]
- 因为通道 0 对应的是蓝色(B),所以 [0] ,即使用了蓝色(B)单通道的灰度图像,因为灰度图像中只有一个通道(单通道)。
- 所以,[1]、[2] 都可以表示单通道的灰度图像。
- 同理,[0]、[1]、[2] 虽然都可以表示单通道的灰度图像,但由于使用的通道分别是 0:蓝色(Blue)、1:绿色(Green)、2:红色(Red),所以,灰度图像的亮度略有区别(会影响灰度像素亮度分布)。
效果如下(分别是BGR、RGB、B、G、R):
示图代码:
"""
以图搜图:图像直方图(Image Histogram)查找相似图像的原理与实现
测试环境:win10 | python 3.9.13 | OpenCV 4.4.0 | numpy 1.21.1 | matplotlib 3.7.1
实验时间:2023-10-27
实例名称:imgHistogram_v1.2_rgb_split.py
"""import cv2
import matplotlib.pyplot as plt# 目标图像素材库文件夹路径
database_dir = '../../P0_Doc/'
# 文件路径
img_path = database_dir + 'img_data/car-101.jpg'# 读取图像:默认使用BGR加载图像
img = cv2.imread(img_path)
# 分离通道:将彩色图像分离成各个通道(R、G、B),然后分别绘制它们的直方图
img_b, img_g, img_r = cv2.split(img)# 绘制子图
plt.figure(figsize=(15, 5))
# 151:表示子图位于一个 1x5 的网格中的第一个位置。如比第2张图的位置152,即一行五列第2张图
# 显示各通道的图像
plt.subplot(151)
plt.imshow(img)
plt.title('BGR (Default)')plt.subplot(152)
plt.imshow(cv2.cvtColor(img, cv2.COLOR_BGR2RGB))
plt.title('BGR TO RGB')plt.subplot(153)
plt.imshow(cv2.cvtColor(img_b, cv2.COLOR_BGR2RGB))
plt.title('Blue Channel')plt.subplot(154)
plt.imshow(cv2.cvtColor(img_g, cv2.COLOR_BGR2RGB))
plt.title('Green Channel')plt.subplot(155)
plt.imshow(cv2.cvtColor(img_r, cv2.COLOR_BGR2RGB))
plt.title('Red Channel')plt.show()
代码对目标图像进行 R、G、B 通道分离,读取像素,然后分别绘制它们的直方图。
下图中,读取目标图像的方式分别是:BGR、RGB、B、G、R,绘画出来的直方图灰度像素分布效果如下:
可以看到,BGR 与 Blue(第1张与第3张)、RGB 与 Red(第2张与第5张)的灰度像素分布趋势一致。
示图代码:
"""
以图搜图:图像直方图(Image Histogram)查找相似图像的原理与实现
测试环境:win10 | python 3.9.13 | OpenCV 4.4.0 | numpy 1.21.1 | matplotlib 3.7.1
实验时间:2023-10-27
实例名称:imgHistogram_v1.3_rgb_split.py
"""import cv2
import matplotlib.pyplot as plt# 目标图像素材库文件夹路径
database_dir = '../../P0_Doc/'
# 文件路径
img_path = database_dir + 'img_data/car-101.jpg'# 读取图像:默认使用BGR加载图像
img = cv2.imread(img_path)
img_rgb = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)# 分离通道:将彩色图像分离成各个通道(R、G、B),然后分别绘制它们的直方图
img_b, img_g, img_r = cv2.split(img)# 计算各通道的直方图
hist_bgr = cv2.calcHist([img], [0], None, [256], [0, 256])
hist_rgb = cv2.calcHist([img_rgb], [0], None, [256], [0, 256])
hist_b = cv2.calcHist([img_b], [0], None, [256], [0, 256])
hist_g = cv2.calcHist([img_g], [0], None, [256], [0, 256])
hist_r = cv2.calcHist([img_r], [0], None, [256], [0, 256])# 绘制线图子图展示各通道的直方图灰度趋势
plt.subplot(151)
plt.plot(hist_bgr, color='orange')
plt.title('BGR Histogram')plt.subplot(152)
plt.plot(hist_rgb, color='purple')
plt.title('RGB Histogram')plt.subplot(153)
plt.plot(hist_b, color='b')
plt.title('Blue Histogram')plt.subplot(154)
plt.plot(hist_g, color='g')
plt.title('Green Histogram')plt.subplot(155)
plt.plot(hist_r, color='r')
plt.title('Red Histogram')plt.show()
如果不好分辨,我们再加上 [0]、[1]、[2] 单通道合成一张直方图:
可以清楚看到,合成后的直方图,只剩下 B、G、R 三通道的灰度像素分布趋势。
示图代码:
"""
以图搜图:图像直方图(Image Histogram)查找相似图像的原理与实现
测试环境:win10 | python 3.9.13 | OpenCV 4.4.0 | numpy 1.21.1 | matplotlib 3.7.1
实验时间:2023-10-27
实例名称:imgHistogram_v1.4_rgb_one.py
"""import cv2
import matplotlib.pyplot as plt
from matplotlib.font_manager import FontProperties# 目标图像素材库文件夹路径
database_dir = '../../P0_Doc/'
# 文件路径
img_path = database_dir + 'img_data/car-101.jpg'
# 字体路径
font_path = database_dir + 'fonts/chinese_cht.ttf'# 读取图像
img = cv2.imread(img_path)
img_rgb = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)# 分离通道:将彩色图像分离成各个通道(R、G、B),然后分别绘制它们的直方图
img_b, img_g, img_r = cv2.split(img)# 计算各通道的直方图(依次为 BGR、RGB、0:蓝色通道,1:绿色通道,2:红色通道)
hist_bgr = cv2.calcHist([img], [0], None, [256], [0, 256])
hist_rgb = cv2.calcHist([img_rgb], [0], None, [256], [0, 256])hist_b = cv2.calcHist([img_b], [0], None, [256], [0, 256])
hist_g = cv2.calcHist([img_g], [0], None, [256], [0, 256])
hist_r = cv2.calcHist([img_r], [0], None, [256], [0, 256])hist_0 = cv2.calcHist([img], [0], None, [256], [0, 256])
hist_1 = cv2.calcHist([img], [1], None, [256], [0, 256])
hist_2 = cv2.calcHist([img], [2], None, [256], [0, 256])# 绘制多线线图,展示各通道的直方图灰度趋势
plt.plot(hist_bgr, color='orange', label='BGR')
plt.plot(hist_rgb, color='purple', label='RGB')plt.plot(hist_b, color='blue', label='Channel Blue')
plt.plot(hist_0, color='blue', label='Channel 0')plt.plot(hist_g, color='green', label='Channel Green')
plt.plot(hist_1, color='green', label='Channel 1')plt.plot(hist_r, color='red', label='Channel Red')
plt.plot(hist_2, color='red', label='Channel 2')# 设置中文字体
font = FontProperties(fname=font_path, size=14)
plt.title('Histogram(直方图)', fontproperties=font)
plt.xlabel('Pixel Value(像素值)', fontproperties=font)
plt.ylabel('Frequency(频率)', fontproperties=font)# 添加图例
plt.legend()
# 显示图像
plt.show()
计算各通道的直方图(依次为 BGR,RGB,0:蓝色通道,1:绿色通道,2:红色通道):
- [0]: 使用
cv2.imread(img_path)
的 img、cv2.split(img) 中的 img_b、[0] 获得到的灰度像素分布趋势等同 - [1]: 使用
cv2.split(img)
中的 img_g、[1] 获得到的灰度像素分布趋势等同 - [2]: 使用
cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
的 img_rgb、cv2.split(img) 中的 img_r、[2] 获得到的灰度像素分布趋势等同
所以,[0]、[1]、[2] 都可以表示单通道的灰度图像,但由于使用的通道分别是 蓝色(Blue)、绿色(Green)、红色(Red),所以,灰度图像的亮度略有区别(会影响灰度像素亮度分布),体现在直方图上,则表现为灰度像素的分布趋势略有差异。
第三步:直方图比较
对于两张图像的直方图比较,可以使用不同的距离或相似度度量方法来进行比较。常见的度量方法包括相关性、卡方距离、巴氏距离等。
"""
以图搜图:图像直方图(Image Histogram)查找相似图像的原理与实现
测试环境:win10 | python 3.9.13 | OpenCV 4.4.0
实验时间:2023-10-27
实例名称:imgHistogram_v2.1_graySimilarity.py
"""import cv2def get_calcHist(img1_path, img2_path):# 读取图像img1 = cv2.imread(img1_path)img2 = cv2.imread(img2_path)# 计算图像灰度单通道直方图img1_hist = cv2.calcHist([img1], [0], None, [256], [0, 256])img2_hist = cv2.calcHist([img2], [0], None, [256], [0, 256])# 计算直方图相似度# cv2.HISTCMP_CORREL: 相关性比较,值越接近 1 表示颜色分布越相似similarity = cv2.compareHist(img1_hist, img2_hist, cv2.HISTCMP_CORREL)print("图像2与图像1的相似度(HISTCMP_CORREL/相关性):", similarity)# 或者# 计算直方图的重合度degree = 0for i in range(len(img1_hist)):if img1_hist[i] != img2_hist[i]:degree = degree + (1 - abs(img1_hist[i] - img2_hist[i]) / max(img1_hist[i], img2_hist[i]))else:degree = degree + 1degree = degree / len(img1_hist)print("图像2与图像1的重合度:", degree)return similarity# 目标图像素材库文件夹路径
database_dir = '../../P0_Doc/img_data/'
# 文件路径
img1_path = database_dir + 'car-101.jpg'
img2_path = database_dir + 'car-102.jpg'print("图像1路径:", img1_path)
print("图像2路径:", img2_path)get_calcHist(img1_path, img2_path)
输出打印:
图像1路径: ../../P0_Doc/img_data/car-101.jpg
图像2路径: ../../P0_Doc/img_data/car-102.jpg
图像2与图像1的相似度(HISTCMP_CORREL/相关性): 1.0
图像2与图像1的重合度: 1.0
说明:图像2是图像1的180度倒置图。
cv2.compareHist
是 OpenCV
中用于比较直方图相似度的函数。用于计算两个直方图之间的相关性(correlation)。img1_hist 和 img2_hist 是两个直方图,它们分别代表两幅图像的颜色分布。
直方图的比较方法:
- cv2.HISTCMP_CORREL(相关性): 计算两个直方图之间的相关性。相关性的值越接近1,表示两幅图像的颜色分布越相似,值越接近-1表示颜色分布越不相似,值接近0表示中等相似度。但不太适用于颜色直方图比较。
- cv2.HISTCMP_CHISQR(卡方距离): 计算卡方距离,用于比较两个直方图之间的差异。值越接近 0 表示颜色分布越相似。
- cv2.HISTCMP_INTERSECT(交集性): 计算两个直方图的交集,用于度量它们的相似度。该值越大表示相似度越高。
- cv2.HISTCMP_BHATTACHARYYA(巴氏距离): 计算两个直方图之间的巴氏距离。值越接近 0 表示颜色分布越相似。
讨论2:直方图相似度函数
使用 cv2.compareHist()
,不同的直方图比较方法,对比结果略有差异。这里,我们使用 [0] 作为灰度通道进行测试实验。
"""
以图搜图:图像直方图(Image Histogram)查找相似图像的原理与实现
测试环境:win10 | python 3.9.13 | OpenCV 4.4.0
实验时间:2023-10-27
实例名称:imgHistogram_v2.3_compareHist.py
"""import cv2
import osdef get_degreeHist(img1_hist, img2_hist):# 计算直方图的重合度degree = 0for i in range(len(img1_hist)):if img1_hist[i] != img2_hist[i]:degree = degree + (1 - abs(img1_hist[i] - img2_hist[i]) / max(img1_hist[i], img2_hist[i]))else:degree = degree + 1degree = degree / len(img1_hist)return degreedef all_compareHist(img1_path, img2_path):img1 = cv2.imread(img1_path)img2 = cv2.imread(img2_path)img1_hist = cv2.calcHist([img1], [0], None, [256], [0, 256])img2_hist = cv2.calcHist([img2], [0], None, [256], [0, 256])similarity = get_degreeHist(img1_hist, img2_hist)print(f"图像 {os.path.basename(img2_path)} 与目标图像 {os.path.basename(img1_path)} 的相似度:", similarity)# 计算直方图相似度# cv2.HISTCMP_BHATTACHARYYA: 巴氏距离比较,值越接近 0 表示颜色分布越相似similarity = cv2.compareHist(img1_hist, img2_hist, cv2.HISTCMP_BHATTACHARYYA)print(f"图像 {os.path.basename(img2_path)} 与目标图像 {os.path.basename(img1_path)} 的相似度(HISTCMP_BHATTACHARYYA/巴氏距离):", similarity)# cv2.HISTCMP_CHISQR: 卡方比较,值越接近 0 表示颜色分布越相似similarity = cv2.compareHist(img1_hist, img2_hist, cv2.HISTCMP_CHISQR)print(f"图像 {os.path.basename(img2_path)} 与目标图像 {os.path.basename(img1_path)} 的相似度(HISTCMP_CHISQR/卡方比较):", similarity)# cv2.HISTCMP_CORREL: 相关性比较,值越接近 1 表示颜色分布越相似similarity = cv2.compareHist(img1_hist, img2_hist, cv2.HISTCMP_CORREL)print(f"图像 {os.path.basename(img2_path)} 与目标图像 {os.path.basename(img1_path)} 的相似度(HISTCMP_CORREL/相关性):", similarity)# cv2.HISTCMP_INTERSECT: 直方图交集比较,值越大表示颜色分布越相似similarity = cv2.compareHist(img1_hist, img2_hist, cv2.HISTCMP_INTERSECT)print(f"图像 {os.path.basename(img2_path)} 与目标图像 {os.path.basename(img1_path)} 的相似度(HISTCMP_INTERSECT/交集比较):", similarity)return similarity# 目标图像素材库文件夹路径
database_dir = '../../P0_Doc/img_data/'
# 文件路径
img1_path = database_dir + 'car-101.jpg'
img2_path = database_dir + 'car-102.jpg'
img2_path = database_dir + 'car-103.jpg'all_compareHist(img1_path, img2_path)
输出打印:
图像 car-102.jpg 与目标图像 car-101.jpg 的相似度: 1.0
图像 car-102.jpg 与目标图像 car-101.jpg 的相似度(HISTCMP_BHATTACHARYYA/巴氏距离): 0.0
图像 car-102.jpg 与目标图像 car-101.jpg 的相似度(HISTCMP_CHISQR/卡方比较): 0.0
图像 car-102.jpg 与目标图像 car-101.jpg 的相似度(HISTCMP_CORREL/相关性): 1.0
图像 car-102.jpg 与目标图像 car-101.jpg 的相似度(HISTCMP_INTERSECT/交集比较): 1232154.0图像 car-103.jpg 与目标图像 car-101.jpg 的相似度: [0.6923658]
图像 car-103.jpg 与目标图像 car-101.jpg 的相似度(HISTCMP_BHATTACHARYYA/巴氏距离): 0.2135487778250319
图像 car-103.jpg 与目标图像 car-101.jpg 的相似度(HISTCMP_CHISQR/卡方比较): 11867327.715396598
图像 car-103.jpg 与目标图像 car-101.jpg 的相似度(HISTCMP_CORREL/相关性): 0.4266303485505497
图像 car-103.jpg 与目标图像 car-101.jpg 的相似度(HISTCMP_INTERSECT/交集比较): 972986.0
讨论3:彩色图像直方图
"""
以图搜图:图像直方图(Image Histogram)查找相似图像的原理与实现
测试环境:win10 | python 3.9.13 | OpenCV 4.4.0
实验时间:2023-10-27
实例名称:imgHistogram_v2.4_rgb_compareHist.py
"""import cv2
import osdef bgr_compareHist(img1_path, img2_path):img1 = cv2.imread(img1_path)img2 = cv2.imread(img2_path)img1_hist = cv2.calcHist([img1], [0, 1, 2], None, [8, 8, 8], [0, 256, 0, 256, 0, 256])img2_hist = cv2.calcHist([img2], [0, 1, 2], None, [8, 8, 8], [0, 256, 0, 256, 0, 256])# 计算直方图相似度# cv2.HISTCMP_BHATTACHARYYA: 巴氏距离比较,值越接近 0 表示颜色分布越相似similarity = cv2.compareHist(img1_hist, img2_hist, cv2.HISTCMP_BHATTACHARYYA)print(f"图像 {os.path.basename(img2_path)} 与目标图像 {os.path.basename(img1_path)} 的相似度(HISTCMP_BHATTACHARYYA/巴氏距离):", similarity)# cv2.HISTCMP_CHISQR: 卡方比较,值越接近 0 表示颜色分布越相似similarity = cv2.compareHist(img1_hist, img2_hist, cv2.HISTCMP_CHISQR)print(f"图像 {os.path.basename(img2_path)} 与目标图像 {os.path.basename(img1_path)} 的相似度(HISTCMP_CHISQR/卡方比较):", similarity)# cv2.HISTCMP_CORREL: 相关性比较,值越接近 1 表示颜色分布越相似similarity = cv2.compareHist(img1_hist, img2_hist, cv2.HISTCMP_CORREL)print(f"图像 {os.path.basename(img2_path)} 与目标图像 {os.path.basename(img1_path)} 的相似度(HISTCMP_CORREL/相关性):", similarity)# cv2.HISTCMP_INTERSECT: 直方图交集比较,值越大表示颜色分布越相似similarity = cv2.compareHist(img1_hist, img2_hist, cv2.HISTCMP_INTERSECT)print(f"图像 {os.path.basename(img2_path)} 与目标图像 {os.path.basename(img1_path)} 的相似度(HISTCMP_INTERSECT/交集比较):", similarity)return similarity# 目标图像素材库文件夹路径
database_dir = '../../P0_Doc/img_data/'
# 文件路径
img1_path = database_dir + 'car-101.jpg'
img2_path = database_dir + 'car-102.jpg'
img2_path = database_dir + 'car-103.jpg'bgr_compareHist(img1_path, img2_path)
输出打印:
图像 car-102.jpg 与目标图像 car-101.jpg 的相似度(HISTCMP_BHATTACHARYYA/巴氏距离): 0.0
图像 car-102.jpg 与目标图像 car-101.jpg 的相似度(HISTCMP_CHISQR/卡方比较): 0.0
图像 car-102.jpg 与目标图像 car-101.jpg 的相似度(HISTCMP_CORREL/相关性): 1.0
图像 car-102.jpg 与目标图像 car-101.jpg 的相似度(HISTCMP_INTERSECT/交集比较): 1232154.0图像 car-103.jpg 与目标图像 car-101.jpg 的相似度(HISTCMP_BHATTACHARYYA/巴氏距离): 0.25780152883475765
图像 car-103.jpg 与目标图像 car-101.jpg 的相似度(HISTCMP_CHISQR/卡方比较): 981578.8189641015
图像 car-103.jpg 与目标图像 car-101.jpg 的相似度(HISTCMP_CORREL/相关性): 0.8933747646918704
图像 car-103.jpg 与目标图像 car-101.jpg 的相似度(HISTCMP_INTERSECT/交集比较): 911172.0
对比 讨论2:直方图相似度函数 的输出结果,可以看到,相同测试图像,彩色直方图的相似度 与 灰度直方图的相似度 有一点差异,原因就是,通过单通道的灰度直方图,可以捕捉到图像的整体亮度和对比度信息,而不受颜色的影响。在相似图片查找中,通常更关注图像的结构和纹理,因此灰度信息更具代表性。
第四步:相似度评估
根据直方图比较的结果(似度得分),进行目标图像相似度评估。
案例场景:图像测试素材库中,查找所有图像,找出与目标图像相似值小于等于0.5的图像。
......for similarity in similarities:if (similarity[1] <= 0.5):print(f"图像名称:{similarity[0]},与目标图像 {os.path.basename(img_org_path)} 的近似值:{similarity[1]}")
4.2 测试
实验场景
通过 opencv,使用直方图算法查找目标图像素材库中所有符合期望值的相似图像。
实验素材
这里,我准备了45张素材图像,其中14张图像为水果,其余为不同类型的汽车,但形态不一。
实验代码
"""
以图搜图:图像直方图(Image Histogram)查找相似图像的原理与实现
测试环境:win10 | python 3.9.13 | OpenCV 4.4.0 | numpy 1.21.1 | matplotlib 3.7.1
实验场景:图像测试素材库中,查找所有图像,找出与目标图像相似值小于等于0.7的图像
实验时间:2023-10-27
实例名称:imgHistogram_v3.2_gray_show.py
"""import os
import time
import cv2
import matplotlib.pyplot as pltdef get_calcHist(org_img_hist, img_path):# 读取图像:通过OpenCV的imread加载RGB图像img = cv2.imread(img_path)# img = cv2.cvtColor(cv2.imread(img_path), cv2.COLOR_BGR2GRAY)img_hist = cv2.calcHist([img], [0], None, [256], [0, 256])# 计算直方图相似度# cv2.HISTCMP_CORREL: 相关性比较,值越接近 1 表示颜色分布越相似# cv2.HISTCMP_CHISQR: 卡方比较,值越接近 0 表示颜色分布越相似# cv2.HISTCMP_BHATTACHARYYA: 巴氏距离比较,值越接近 0 表示颜色分布越相似# cv2.HISTCMP_INTERSECT: 直方图交集比较,值越大表示颜色分布越相似similarity = cv2.compareHist(org_img_hist, img_hist, cv2.HISTCMP_BHATTACHARYYA)return similaritydef show_similar_images(similar_images, images_per_column=3):# 计算总共的图片数量num_images = len(similar_images)# 计算所需的行数num_rows = (num_images + images_per_column - 1) // images_per_column# 创建一个子图,每行显示 images_per_column 张图片fig, axes = plt.subplots(num_rows, images_per_column, figsize=(12, 15), squeeze=False)# 遍历每一行for row in range(num_rows):# 遍历每一列for col in range(images_per_column):# 计算当前图片在列表中的索引index = row * images_per_column + col# 检查索引是否越界if index < num_images:# 获取当前相似图片的路径和相似度image_path = similar_images[index][0]similarity = similar_images[index][1]# 读取图片并转换颜色通道image = cv2.cvtColor(cv2.imread(image_path), cv2.COLOR_BGR2RGB)# 在子图中显示图片axes[row, col].imshow(image)# 设置子图标题,包括图片路径和相似度axes[row, col].set_title(f"Similar Image: {os.path.basename(image_path)} \n Similar Score: {similarity:.4f}")# 关闭坐标轴axes[row, col].axis('off')# 显示整个图plt.show()# ------------------------------------------------ 测试 ------------------------------------------------
if __name__ == '__main__':time_start = time.time()# 指定测试图像库目录img_dir = '../../P0_Doc/img_data/'# 指定测试图像文件扩展名img_suffix = ['.jpg', '.jpeg', '.png', '.bmp', '.gif']# 获取当前执行脚本所在目录script_dir = os.path.dirname(__file__)# 获取目标测试图像的全路径img_org_path = os.path.join(script_dir, img_dir, 'apple-101.jpg')# 加载要查询的图像query_image = cv2.imread(img_org_path)# query_image = cv2.cvtColor(cv2.imread(img_org_path), cv2.COLOR_BGR2GRAY)# 计算查询图像的直方图:灰度直方图算法img_org_hist = cv2.calcHist([query_image], [0], None, [256], [0, 256])print(f"目标图像:{os.path.relpath(img_org_path)}")# 获取测试图像库中所有文件all_files = os.listdir(os.path.join(script_dir, img_dir))# 筛选出指定后缀的图像文件img_files = [file for file in all_files if any(file.endswith(suffix) for suffix in img_suffix)]# 存储相似度值和对应的图像路径img_search_results = []# 遍历测试图像库中的每张图像for img_file in img_files:# 获取相似图像文件路径img_path = os.path.join(script_dir, img_dir, img_file)# 获取相似图像可识别哈希值(图像指纹)similarity = get_calcHist(img_org_hist, img_path)# print(f"图像名称:{img_path},与目标图像 {os.path.basename(img_org_path)} 的近似值:{similarity}")if (similarity <= 0.7):# 存储相似度值和对应的图像路径img_search_results.append((os.path.relpath(img_path), similarity))# 按相似度升序排序img_search_results.sort(key=lambda item: item[1])for img_similarity in img_search_results:print(f"图像名称:{img_similarity[0]},与目标图像 {os.path.basename(img_org_path)} 的相似值:{img_similarity[1]}")time_end = time.time()print(f"耗时:{time_end - time_start}")# 显示目标相似图像show_similar_images(img_search_results)
多图相似查找效果显示,实验代码使用的是巴氏距离比较,值越接近 0 表示颜色分布越相似:
输出打印:
目标图像:..\..\P0_Doc\img_data\apple-101.jpg
图像名称:..\..\P0_Doc\img_data\apple-101.jpg,与目标图像 apple-101.jpg 的相似值:0.0
图像名称:..\..\P0_Doc\img_data\apple-104.jpg,与目标图像 apple-101.jpg 的相似值:0.0
图像名称:..\..\P0_Doc\img_data\car-109.jpg,与目标图像 apple-101.jpg 的相似值:0.6158102157213413
图像名称:..\..\P0_Doc\img_data\car-103.jpg,与目标图像 apple-101.jpg 的相似值:0.662176197294615
图像名称:..\..\P0_Doc\img_data\car-101.jpg,与目标图像 apple-101.jpg 的相似值:0.6813075243521007
图像名称:..\..\P0_Doc\img_data\car-102.jpg,与目标图像 apple-101.jpg 的相似值:0.6813075243521007
图像名称:..\..\P0_Doc\img_data\Q3-09.jpg,与目标图像 apple-101.jpg 的相似值:0.6844184531149912
图像名称:..\..\P0_Doc\img_data\car-108.jpg,与目标图像 apple-101.jpg 的相似值:0.6861940450661771
图像名称:..\..\P0_Doc\img_data\X3-09.jpg,与目标图像 apple-101.jpg 的相似值:0.692340714053627
图像名称:..\..\P0_Doc\img_data\apple-114.jpg,与目标图像 apple-101.jpg 的相似值:0.6932278615425139
图像名称:..\..\P0_Doc\img_data\apple-112.jpg,与目标图像 apple-101.jpg 的相似值:0.6959175186621991
图像名称:..\..\P0_Doc\img_data\pear-201.jpg,与目标图像 apple-101.jpg 的相似值:0.6997766670329476
耗时:1.0362038612365723
5. 总结
总体来说,直方图算法属于一种传统外观相似算法,适用于一些简单的图像相似性比较问题,但直方图反应的是图像灰度值得概率分布,并没有图像的空间位置信息在里面,因此,可能会出现误判。比如纹理结构相同,但明暗不同的图像,应该相似度很高,但实际结果是相似度很低,而纹理结构不同,但明暗相近的图像,相似度却很高。
优点
- 简单直观: 直方图是一种简单直观的图像表达方式,易于理解和实现。
- 快速计算: 直方图的计算相对快速,特别是对于小尺寸的图像。
- 颜色不变性: 直方图在某种程度上对颜色的变化具有不变性,因此可以在一定程度上应对图像的轻微变形。
- 对光照变化有一定的鲁棒性: 直方图可以在一定程度上处理图像的光照变化。
缺点
- 不考虑空间信息: 直方图方法忽略了图像的空间信息,对于图像的排列、结构等方面的变化较为敏感。
- 受噪声影响: 如果图像受到噪声的影响,直方图会受到一定程度的干扰。
- 灰度信息有限: 灰度直方图只考虑了像素的灰度信息,对于颜色信息较为有限。
- 无法处理形状变化: 直方图方法难以处理图像中物体的形状变化。
6. 系列书签
OpenCV书签 #均值哈希算法的原理与相似图片搜索实验
OpenCV书签 #感知哈希算法的原理与相似图片搜索实验
OpenCV书签 #差值哈希算法的原理与相似图片搜索实验
OpenCV书签 #直方图算法的原理与相似图片搜索实验