翻译及二次校对:cvtutorials.com
目标
在本节中:
理论
考虑一个图像,其像素值只局限于某些特定的数值范围。例如,较亮的图像将有所有的像素限制在高值。但是一个好的图像会有来自图像所有区域的像素。因此,你需要将这个直方图拉伸到两端(如下图所示,来自维基百科),这就是直方图均衡化的作用(简单地说)。这通常会改善图像的对比度。
我建议你阅读关于直方图均衡化的维基百科页面,以了解更多相关细节。它有一个非常好的解释,并有例子,所以在阅读后你会理解几乎所有的东西。相反,在这里我们将看到它的Numpy实现。之后,我们将看到OpenCV函数。
import numpy as np
import cv2 as cv
from matplotlib import pyplot as plt
img = cv.imread('wiki.jpg',0)
hist,bins = np.histogram(img.flatten(),256,[0,256])
cdf = hist.cumsum()
cdf_normalized = cdf * float(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()
你可以看到直方图位于较亮的区域。我们需要完整的光谱。为此,我们需要一个转换函数,将较亮区域的输入像素映射到完整区域的输出像素。这就是直方图均衡化的作用。
现在我们找到直方图的最小值(不包括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]
现在我们像以前一样计算它的直方图和Cdf(你来做),结果看起来像下面。
另一个重要的特点是,即使图像是一个较暗的图像(而不是我们使用的一个较亮的图像),在均衡后,我们将得到与上述图像几乎相同的图像。因此,这被用作一个 "参考工具",使所有图像具有相同的照明条件。这在许多情况下是很有用的。例如,在人脸识别中,在训练人脸数据之前,对人脸图像进行直方图均衡化,使其具有相同的照明条件。
OpenCV中的直方图均衡化
OpenCV有一个函数可以做到这一点,即cv.equalizeHist()。它的输入是灰度图像,输出是我们的直方图均衡化图像。
下面是一个简单的代码片段,显示了它在我们使用的同一图像上的用法。
img = cv.imread('wiki.jpg',0)
equ = cv.equalizeHist(img)
res = np.hstack((img,equ)) #stacking images side-by-side
cv.imwrite('res.png',res)
因此,现在你可以在不同的光线条件下拍摄不同的图像,对其进行均衡并检查结果。
当图像的直方图被限制在一个特定的区域内时,直方图均衡化是好的。在灰度变化较大的地方,即直方图覆盖较大区域的地方,它不会有好的效果,也就是说,明亮和黑暗的像素都存在。请查看附加资源中的SOF链接。
CLAHE(对比度有限的自适应直方图均衡)
我们刚才看到的第一个直方图均衡,考虑了图像的整体对比度。在许多情况下,这并不是一个好主意。例如,下面的图片显示了一张输入图片和全局直方图均衡化后的结果。
诚然,在直方图均衡化之后,背景对比度得到了改善。但比较两张图片中的雕像的脸。由于过亮,我们失去了大部分的信息。这是因为它的直方图并不像我们在以前的案例中看到的那样被限制在一个特定的区域内(试着绘制输入图像的直方图,你会得到更多的直观感受)。
因此,为了解决这个问题,采用了自适应直方图均衡化。在这个过程中,图像被分成小块,称为 "瓦片"(OpenCV中瓦片大小默认为8x8)。然后这些块中的每一个都像往常一样被直方图均衡化。因此,在一个小区域内,直方图将被限制在一个小区域内(除非有噪声)。如果有噪音,它就会被放大。为了避免这种情况,采用了对比度限制。如果任何一个直方图仓超过了指定的对比度限制(在OpenCV中默认为40),在应用直方图均衡化之前,这些像素会被剪掉并均匀地分布到其他仓。在均衡化之后,为了消除瓦片边界的伪影,将应用双线性插值。
下面的代码片段显示了如何在OpenCV中应用CLAHE。
import numpy as np
import cv2 as cv
img = cv.imread('tsukuba_l.png',0)
# create a CLAHE object (Arguments are optional).
clahe = cv.createCLAHE(clipLimit=2.0, tileGridSize=(8,8))
cl1 = clahe.apply(img)
cv.imwrite('clahe_2.jpg',cl1)
请看下面的结果,并与上面的结果进行比较,特别是雕像区域。
其他资源
还可以查看这些关于对比度调整的SOF问题。
[1]
维基百科关于直方图均衡化的页面: https://en.wikipedia.org/wiki/Histogram_equalization
[2]
Numpy中的掩膜数组: https://docs.scipy.org/doc/numpy/reference/maskedarray.html
[3]
我如何在OpenCV中用C语言调整对比度?: https://stackoverflow.com/questions/10549245/how-can-i-adjust-contrast-in-opencv-in-c
[4]
如何用opencv均衡图像的对比度和亮度?: https://stackoverflow.com/questions/10561222/how-do-i-equalize-contrast-brightness-of-images-using-opencv