在本节中,
我们将学习直方图均衡化的概念,并使用它来提高我们图像的对比度。
Theory
考虑一幅图像,它的像素值被限制在一些特定的值范围内。例如,更亮的图像将所有像素限制在高值。但是一个好的图像应该有来自图像所有区域的像素。所以你需要拉伸直方图的两端(如下图所示,来自维基百科),这就是直方图均衡化所做的(简单的说)。这通常会改善图像的对比度。
我建议你去维基百科上的直方图均衡化页面了解更多细节。它有一个很好的解释和算出的例子,所以你会理解几乎所有后,读它。相反,在这里我们将看到它的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),并应用直方图均衡化方程,如维基页面所示。但我在这里使用了,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 (Contrast Limited Adaptive Histogram Equalization)(限制对比度的自适应直方图均衡化)
我们刚才看到的第一个直方图均衡化考虑的是图像的整体对比度。在很多情况下,这不是一个好主意。例如,下面的图片显示了一个输入图像和经过全局直方图均衡化后的结果。
直方图均衡化后的背景对比度确实得到了改善。但是比较一下两幅图像中雕像的脸。由于亮度过高,我们丢失了大部分信息。这是因为它的直方图不像我们在前面的例子中看到的那样局限于一个特定的区域(试着绘制输入图像的直方图,你会得到更多的直觉)。
为了解决这一问题,采用了自适应直方图均衡化。在这个方法中,图像被分割成称为“tiles”的小块(在OpenCV中,平铺大小默认为8x8)。然后像往常一样对每个块进行直方图均衡化。所以在一个小区域内,直方图会限制在一个小区域内(除非有噪音)。如果有噪音,它会被放大。为了避免这种情况,应用了对比度限制。如果任何一个直方图bin超过了指定的对比度限制(OpenCV中默认为40),那么在应用直方图均衡化之前,这些像素会被裁剪并均匀地分布到其他bin。均衡后,为了去除瓷砖边界上的伪影,应用了双线性插值。
下面的代码片段展示了如何在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)
请看下面的结果,并与上面的结果进行比较,特别是雕像区域: