本节将学习直方图均衡化的概念,并使用它来提高图像的对比度。
理论基础
考虑一个像素值仅限于某个特定值范围的图像。 例如,较亮的图像将所有像素限制在高值。但是一个好的图像将具有来自图像所有区域的像素。 所以需要将这个直方图拉伸到两端(如下图所示,来自维基百科),这就是直方图均衡的作用(简单来说), 通常会提高图像的对比度。
建议阅读维基百科上的直方图均衡页面,了解更多关于它的详细信息。 它有一个很好的解释和计算示例,这样在阅读后几乎可以理解所有内容。 相反,在这里我们将看到Numpy的实现以及OpenCV函数。
%matplotlib inline
import cv2
import numpy as np
from matplotlib import pyplot as plt
img = cv2.imread('/data/cvdata/wiki.jpg',0)
hist,bins = np.histogram(img.flatten(),256,[0,256])
cdf = hist.cumsum()
cdf_normalized = cdf * hist.max()/ cdf.max()
plt.plot(cdf_normalized, color = 'b')
plt.hist(img.flatten(),256,[0,256], color = 'r')
plt.xlim([0,256])
plt.legend(('cdf','histogram'), loc = 'upper left')
plt.show()
/tmp/ipykernel_1765/3828199843.py:15: MatplotlibDeprecationWarning: Passing the range parameter of hist() positionally is deprecated since Matplotlib 3.9; the parameter will become keyword-only in 3.11. plt.hist(img.flatten(),256,[0,256], color = 'r')
我们可以看到直方图位于较亮的区域,因此需要全谱。 同时需要一个变换函数,将较亮区域的输入像素映射到整个区域的输出像素。这就是直方图均衡化的作用。
找到最小直方图值(不包括0),并应用wiki页面中给出的直方图均衡化方程。 但这里使用了Numpy的掩码数组概念数组。 对于掩码数组,所有操作都在非掩码元素上执行。 可以从Numpy文档中了解有关掩码数组的更多信息。
cdf_m = np.ma.masked_equal(cdf,0)
cdf_m = (cdf_m - cdf_m.min())*255/(cdf_m.max()-cdf_m.min())
cdf = np.ma.filled(cdf_m,0).astype('uint8')
查找表提供了每个输入像素值的输出像素值的信息,所以只应用转换。
img2 = cdf[img]
plt.imshow(img)
<matplotlib.image.AxesImage at 0x7f03cb348500>
img = cv2.imread('/data/cvdata/wiki.jpg',0)
equ = cv2.equalizeHist(img)
res = np.hstack((img,equ)) #stacking images side-by-side
cv2.imwrite('res.png',res)
True
所以现在可以在不同的光照条件下拍摄不同的图像,均衡并检查结果。 当图像的直方图被限制在特定区域时,直方图均衡是好的。它在直方图覆盖大区域(即亮像素和暗像素都存在)的强度变化较大的地方效果不佳。 请查看附加资源中的SOF链接。
CLAHE(对比度限制自适应直方图均衡)
我们刚才看到的第一个直方图均衡化考虑了图像的全局对比度。在许多情况下,这不是一个好主意。 例如,下图显示了输入图像及其全局直方图均衡后的结果。
直方图均衡化后背景对比度确实有所提高。但比较一下两幅图像中雕像的脸。 由于亮度过高,丢失了那里的大部分信息。 这是因为它的直方图并不局限于在前面的案例中看到的特定区域(尝试绘制输入图像的直方图,会得到更多的直觉)。
因此,为了解决这个问题,使用了自适应直方图均衡化。 在这种情况下,图像被划分为称为“瓦片”的小块(在OpenCV中,瓦片大小默认为8x8)。 然后,像往常一样对每个块进行直方图均衡。因此,在小区域内,直方图将仅限于一个小区域(除非有噪声)。 如果有噪音,它会被放大。为了避免这种情况,应用了对比度限制。 如果任何直方图区间高于指定的对比度限制(在OpenCV中默认为40),则在应用直方图均衡化之前,这些像素会被剪切并均匀分布到其他区间。 均衡后,为了消除瓷砖边界中的伪影,应用了双线性插值。
下面的代码片段显示了如何在OpenCV中应用CLAHE:
import numpy as np
import cv2
img = cv2.imread('/data/cvdata/tsukuba_l.png',0)
# create a CLAHE object (Arguments are optional).
clahe = cv2.createCLAHE(clipLimit=2.0, tileGridSize=(8,8))
cl1 = clahe.apply(img)
cv2.imwrite('xx_clahe_2.jpg',cl1)
True