学习目标
- 这一节,我们要学习直方图均衡化(histogram equalization),然后提高图像的对比度。
理论
考虑一张图像的像素值仅分布在特定的范围,比如较亮的图像的像素值通常都较大。但是,一张好的图像应该分布均匀。所以我们需要通过直方图均衡化使得图像分布更加均匀,见下图:
可以参考:Histogram Equalization。下面我们使用Numpy
计算图像的直方图,实例如下:
import cv2
import numpy as np
from matplotlib import pyplot as plt
img = cv2.imread('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()
运行结果如下:
从上图可以看出,直方图分布在亮的区域。我们需要均匀的分布。那么我们需要一个变换函数,使得像素分布均匀,这正是直方图均衡化。
现在,我们需要寻找最小的直方图值(除了0),然后应用直方图均衡化,但是,这里我们使用的是Numpy
中的掩码数组(masked array)的概念。对于掩码数组,所有的操作作用于非掩码的元素
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]
然后按照上述的计算直方图的代码重新计算,完整的代码如下:
import cv2
import numpy as np
from matplotlib import pyplot as plt
img = cv2.imread('wiki.jpg', 0)
print(img.shape)
# cv2.imshow('img', img)
# cv2.waitKey(0)
hist, bins = np.histogram(img.flatten(), 256, [0, 256])
cdf = hist.cumsum()
# print(hist)
# print(len(cdf))
# print(cdf)
#
# 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()
cdf_m = np.ma.masked_equal(cdf, 0)
cdf_m = (cdf_m - cdf_m.min())*255/(cdf_m.max()-cdf_m.min())
cdf1 = np.ma.filled(cdf_m, 0).astype('uint8')
img2 = cdf1[img]
cv2.imshow('img2', img2)
cv2.waitKey(0)
hist1, bins1 = np.histogram(img2.flatten(), 256, [0, 256])
cdf2 = hist1.cumsum()
cdf_normalized = cdf2 * hist1.max() / cdf2.max()
plt.plot(cdf_normalized, color='b')
plt.hist(img2.flatten(), 256, [0, 256], color='r')
plt.xlim([0, 256])
plt.legend(('cdf', 'histogram'), loc='upper left')
plt.show()
运行结果如下:
另外一个重要的特性:尽管图像是暗的图像,当进行直方图均衡化后,我们可以得到得到与之前同样的效果的图像。那么,我们可以将该调整后的图像作为参考图像,然后使得源图像调整到参考图像的亮度。比如在人脸识别中,我们可以在训练人脸之前,使用直方图均衡化将所有图像调整到相同的光照条件。
掩码矩阵:
>>> import numpy as np >>> import numpy.ma as ma >>> x = np.array([1, 2, 3, -1, 5]) 如果我们希望-1被标记为无效 则可以: >>> mx = ma.masked_array(x, mask=[0, 0, 0, 1, 0]) >>> 当计算平均值时,不会考虑无效 >>> mx.mean() 2.75
Histograms Equalization in OpenCV
OpenCV中含有相应的函数可以进行直方图均衡化,cv2.equalizeHist()
. 输入时灰度图,输出是进行直方图均衡化的图像。
下面是简单的例子:
img = cv2.imread('wiki.jpg', 0)
equ = cv2.equalizeHist(img)
res = np.hstack((img, equ)) # stacking images side-by-side
# cv2.imwrite('res.png', res)
cv2.imshow('res', res)
cv2.waitKey(0)
当图像的直方图分布于特定的区域内,那么直方图均衡化非常有效。但是,但图像具有较大的强度变化(直方图覆盖区域较大),均衡化就不是那么有效了。
CLAHE (Contrast Limited Adaptive Histogram Equalization)
从上面的直方图均衡化可以看出,它使得全局的对比度得以改善。但是在大多情况下,这并不好。比如,下面的图像以及均衡化处理后的图像:
从图中可以看出,图像的背景对比度得到增强。但是图中的人脸雕像确变得模糊,由于过亮,我们丢失了大部分的信息。造成这种结果的原因是,该图像的直方图覆盖的区域较大,见下面的直方图:
为了解决这个问题,我们使用adaptive histogram equalization
. 这里,图像被分为很多的小块,opencv
中默认的小块大小时8x8
。在每一个小块使用直方图均衡化。那么每一个小块的直方图很可能覆盖很小的区域(除非有噪声)。为了克服噪声,采用对比度限制(contrast limiting)。如果任何一个直方图的bin
超过设定的限制值(默认40),这些像素被截断,并均匀分布到其它的bin
,然后在施加均衡化。最后借助双线性插值移除多余的边界。
下面的代码展示了CLAHE:
img = cv2.imread('tsukuba_l.jpg', 0)
# plt.hist(img.ravel(), 256, [0, 256])
# plt.show()
# create a CLAHE object (Arguments are optional).
clahe = cv2.createCLAHE(clipLimit=2.0, tileGridSize=(8, 8))
cl1 = clahe.apply(img)
# cv2.imwrite('clahe_2.jpg',cl1)
cv2.imshow('cl1', cl1)
cv2.waitKey(0)
运行结果如下,尤其是雕像区域: