官网https://docs.opencv.org/3.4.1/d5/daf/tutorial_py_histogram_equalization.html
我们来想象一下,一副图像的像素值都集中在某些特定范围之内。例如,明亮的图像,像素值都会很高。
但是一副好图像,像素值应该发布在图像所有区域。所以你需要把直方图横向拉升到2端(如下图),这就是直方图均衡化。通常情况,这种操作会增加图像的对比度。
直方图均衡化详见 en.wikipedia.org/wiki/Histogram_equalization
现在我们先来看看如何用numpy实现直方图均衡化。
例1,观察以下图像的直方图
代码如下
import cv2
import numpy as np
from matplotlib import pyplot as plt
img = cv2.imread('1.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中的直方图均衡化公式。
1.numpy掩膜数组实现直方图均衡化
这里我们使用了numpy的掩膜数组。对于掩膜数组,所有操作只有对非掩膜(non-masked)元素有效。
# 构建Numpy 掩模数组。cdf 为原数组,当数组元素为0 时掩盖(计算时被忽略)。
cdf_m = np.ma.masked_equal(cdf,0)
cdf_m = (cdf_m - cdf_m.min())*255/(cdf_m.max()-cdf_m.min())
# 对被掩盖的元素赋值为0
cdf = np.ma.filled(cdf_m,0).astype('uint8')
然后我们就获得了一个表,这个表中给我们提供了每一个输入像素值对应的输出像素值。
最后我们把它应用到图像上。
img2 = cdf[img]
例1,利用numpy掩膜数组实现直方图均衡化
import cv2
import numpy as np
from matplotlib import pyplot as plt
img = cv2.imread('1.jpg',0)
hist,bins = np.histogram(img.flatten(),256,[0,256])
cdf = hist.cumsum()
# 构建Numpy 掩模数组。cdf 为原数组,当数组元素为0 时掩盖(计算时被忽略)。
cdf_m = np.ma.masked_equal(cdf,0)
cdf_m = (cdf_m - cdf_m.min())*255/(cdf_m.max()-cdf_m.min())
# 对被掩盖的元素赋值为0
cdf = np.ma.filled(cdf_m,0).astype('uint8')
img2 = cdf[img]
cv2.imshow('result', img2)
cv2.waitKey(0)
cv2.destroyAllWindows()
显示结果如上,对比度明显增加。
例2,显示numpy掩膜数组均衡化后直方图
import cv2
import numpy as np
from matplotlib import pyplot as plt
img = cv2.imread('1.jpg',0)
hist,bins = np.histogram(img.flatten(),256,[0,256])
cdf = hist.cumsum()
# 构建Numpy 掩模数组。cdf 为原数组,当数组元素为0 时掩盖(计算时被忽略)。
cdf_m = np.ma.masked_equal(cdf,0)
cdf_m = (cdf_m - cdf_m.min())*255/(cdf_m.max()-cdf_m.min())
# 对被掩盖的元素赋值为0
cdf = np.ma.filled(cdf_m,0).astype('uint8')
img2 = cdf[img]
hist,bins = np.histogram(img2.flatten(),256,[0,256])
cdf = hist.cumsum()
cdf_normalized = cdf * float(hist.max()) / cdf.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()
直方图(红色)和累积分布图(蓝色)如上。
关于直方图均衡化另外一个重要特点是,如果输入的图像是一个比较暗的图像(不像上面例子中使用的是比较亮的图像),在均衡化后也会得到和上面一样的结果。
所以直方图均衡化被作为一个参考工具,用于使所有图像获得相同的亮度条件。这在很多案例中都有用。
例如,人脸识别场景,在对人脸数据图像进行训练之前,先进行直方图均衡化,使所有图像都有相同的亮度条件。
2.opencv中的直方图均衡化
opencv提供cv.equalizeHist()函数实现均衡化。输入是灰度图,输入的是均衡化后的直方图。
例,opencv中实现直方图均衡化
import cv2
import numpy as np
from matplotlib import pyplot as plt
img = cv2.imread('1.jpg',0)
equ = cv2.equalizeHist(img)
res = np.hstack((img,equ)) #stacking images side-by-side
cv2.imwrite('res.png',res)
同样使用的是numpy掩膜数组均衡化例子中图像,显示对比结果如上。
当图像中的直方图数据集中在某个区域时候,均衡化就非常有用了。但是如果灰度变化非常强烈,同时分布范围非常大时候,均衡化的效果并不好。例如:图像中既有亮的像素点又有暗的像素点。
3.CLAHE (Contrast Limited Adaptive Histogram Equalization) -对比度有限自适应直方图均衡化
上面例子中的均衡化,可以发现都是进行全图像对比度变化。但是很多情况下,这样并不好。例如,观察下图,特别是进行全图均衡化后的效果。
可以发现,均衡化后,背景的对比度有了提高。但是对比上下2幅图雕像的脸部,由于过亮丢失了很多信息。这是因为直方图均衡化时候并没有考虑特别的区域。
为了解决这个问题,需要使用自适应直方图均衡化。此时,图像会被分成许多个小块,每个小块被称为“tiles”(opencv中默认tiles大小是8*8)。然后每个小块分别进行均衡化。所以每一个小块中,直方图被限制在小区域中(除非有噪声)。如果有噪声,它会被放大,要避免这个问题,就要使用对比度限制。如果有任何一个直方图的bin值超过对比度限制值(opencv中默认40),在直方图均衡化之前,把那些像素均匀分散到其他bin中。均衡化后,为了消除tiles之间的边界,使用双线性差值。
例,对比度限制自适应直方图均衡化
import cv2
import numpy as np
from matplotlib import pyplot as plt
img = cv2.imread('a.jpg',0)
# 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)
结果如上,雕塑的脸部是不是好很多了?