1. 图像直方图(Image histogram)
图像直方图,又叫影像直方图,是一种用来表现数位影像中像素分布的直方图,根据统计影像中不同亮度的像素总数,我们可以画出一张代表这张影像的影像直方图,透过这张直方图我们可以一眼就看出这张图中像素的分布情形。
图像直方图由于其计算代价较小,且具有图像平移、旋转、缩放不变性等众多优点,广泛地应用于图像处理的各个领域,特别是灰度图像的阈值分割、基于颜色的图像检索以及图像分类。
2 图像直方图的计算
直方图计算,即计算图像中每个像素值的出现次数,然后根据像素值范围,将像素值分成一定数量的区间,然后计算每个区间内的像素值出现次数,最后将这些出现次数绘制在一张直方图上。
2.1 彩色图像的直方图
使用 OpenCV-Python 的 calcHist
以及 matplotlib 库,我们可以这样计算并绘制一个彩色图像的直方图。
import cv2
import matplotlib.pyplot as plt
# 读取图像
img = cv2.imread('../data/park.jpg')
# 分离BGR颜色通道
b, g, r = cv2.split(img)
# 设置直方图大小
hist_size = 256
# 设置直方图范围,左闭右开区间,最大值为255
hist_range = [0, 256]
# 计算直方图
b_hist = cv2.calcHist([b], [0], None, [hist_size], hist_range)
g_hist = cv2.calcHist([g], [0], None, [hist_size], hist_range)
r_hist = cv2.calcHist([r], [0], None, [hist_size], hist_range)
# 绘制直方图
fig, ax = plt.subplots(1, 1) # 创建一个子图及布局,BGR三个通道都绘制到一个图上
ax.plot(b_hist, color='b', label='B')
ax.plot(g_hist, color='g', label='G')
ax.plot(r_hist, color='r', label='R')
ax.set_xlim(hist_range) # 设置x轴范围
ax.legend() # 显示图例
plt.show() # 显示图像
以条形图方式表达直方图:
import cv2
import matplotlib.pyplot as plt
# 读取图像
image_path = '../data/park.jpg'
image = cv2.imread(image_path) # 读取彩色图像
# 计算直方图
b_hist = cv2.calcHist([image], [0], None, [256], [0, 256])
g_hist = cv2.calcHist([image], [1], None, [256], [0, 256])
r_hist = cv2.calcHist([image], [2], None, [256], [0, 256])
# 绘制直方图
plt.figure(figsize=(10, 6))
# 使用bar函数绘制直方图
plt.bar(range(256), b_hist.ravel(), color='blue', alpha=0.7)
plt.bar(range(256), g_hist.ravel(), color='green', alpha=0.7)
plt.bar(range(256), r_hist.ravel(), color='red', alpha=0.7)
# 设置标题和轴标签
plt.title('Histogram of the Image')
plt.xlabel('Intensity Value')
plt.ylabel('Frequency')
# 显示网格
plt.grid(axis='y', linestyle='--', alpha=0.7)
# 显示图表
plt.show()
2.2 灰度图像直方图
灰度图像的直方图计算与彩色图像的直方图计算类似,可以读入灰度图进行计算。但是对于一个彩色图像,我们既可以通过 cv2.imread(image_path, cv2.IMREAD_GRAYSCALE)
直接读入灰度图,也可以通过 cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
将彩色图转化为灰度图。通过比较,这两个方法得到的灰度图可能非常相似(本例相似度为0.9996933300209153),但事实上并不一致。
import cv2
import numpy as np
import matplotlib.pyplot as plt
# 读取图像
image_path = '../data/park.jpg'
image = cv2.imread(image_path)
image_gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
read_gray = cv2.imread(image_path, cv2.IMREAD_GRAYSCALE)
# 计算直方图
b_hist = cv2.calcHist([image], [0], None, [256], [0, 256])
g_hist = cv2.calcHist([image], [1], None, [256], [0, 256])
r_hist = cv2.calcHist([image], [2], None, [256], [0, 256])
cvt_gray_hist = cv2.calcHist([image_gray], [0], None, [256], [0, 256])
read_gray_hist = cv2.calcHist([read_gray], [0], None, [256], [0, 256])
# 绘制直方图
fig, axes = plt.subplots(3, 2, figsize=(15, 15))
# 显示图像
axes[0, 0].imshow(cv2.cvtColor(image, cv2.COLOR_BGR2RGB))
axes[1, 0].imshow(image_gray, cmap='gray')
axes[2, 0].imshow(read_gray, cmap='gray')
# 使用bar函数绘制直方图
axes[0, 1].bar(range(256), b_hist.ravel(), color='blue', alpha=0.7)
axes[0, 1].bar(range(256), g_hist.ravel(), color='green', alpha=0.7)
axes[0, 1].bar(range(256), r_hist.ravel(), color='red', alpha=0.7)
axes[1, 1].bar(range(256), cvt_gray_hist.ravel(), color='gray')
axes[2, 1].bar(range(256), read_gray_hist.ravel(), color='gray')
# 显示网格
axes[0, 1].grid(axis='y', linestyle='--', alpha=0.7)
axes[1, 1].grid(axis='y', linestyle='--', alpha=0.7)
axes[2, 1].grid(axis='y', linestyle='--', alpha=0.7)
# 设置图表标题和坐标轴标签
axes[0, 0].set_title('Original Image')
axes[1, 0].set_title('Gray Image')
axes[2, 0].set_title('Read Gray Image')
axes[0, 1].set_title('BGR Histogram')
axes[1, 1].set_title('Converted Gray Histogram')
axes[2, 1].set_title('Read Gray Histogram')
# 显示图表
plt.show()
similarity = cv2.compareHist(cvt_gray_hist, read_gray_hist, cv2.HISTCMP_CORREL)
print(f"The similarity between the two images is:{similarity} ")
diff = cv2.absdiff(image_gray, read_gray)
plt.plot(diff)
plt.show()
total_nonzero = np.count_nonzero(diff)
print("Total non-zero elements:", total_nonzero)
The similarity between the two images is:0.9996933300209153
Total non-zero elements: 16351
因此,直接通过 cv2.imread(image_path, cv2.IMREAD_GRAYSCALE)
读入的灰度图和 cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
转换的灰度图,是不一样的,在开发时需要注意这个问题。