内容来自OpenCV-Python Tutorials 自己翻译整理
目标:
直方图均衡化
调整图片对比度
原理:
考虑一副图片的的像素限制在一些特定区域的像素值。例如,亮度更高的图片像素值会集中在值更高的地方。但是一个好的图片会将像素值分布到图片的各个区域。你需要拉伸直方图,这就是直方图均衡化的目的。
维基百科对于直方图均衡和的解释很好,有很多细节。详细见此处
查看直方图
import cv2
import numpy as np
from matplotlib import pyplot as plt
img = cv2.imread('13.jpg',0)
hist,bins = np.histogram(img.flatten(),256,[0,256])#img.flatten将数组变成一维数组
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当中的蒙板数组,对于蒙板数组,所有操作都对非蒙板的元素执行。
#蒙板数组,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]
完整代码
import cv2
import numpy as np
from matplotlib import pyplot as plt
img = cv2.imread('13.jpg',0)
hist,bins = np.histogram(img.flatten(),256,[0,256])#img.flatten将数组变成一维数组
cdf = hist.cumsum()#计算直方图
cdf_normalized = cdf * hist.max()/ cdf.max()
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('p',img2)
cv2.waitKey(0)
直方图(histogram )和累积分布图(cdf)如下
另一个重要的特性是如我我们输入的图片比较暗,在均衡化以后会得到相同的结果。因此,这种方法被用来当做相同偏了亮度条件的参考工具。该方法在很多条件下很有用,例如面部识别,分类器训练等等。
opencv中的直方图均衡化
使用cv2.equalizeHist()函数
输入灰度图,输出均衡化的图片
import cv2
import numpy as np
from matplotlib import pyplot as plt
img = cv2.imread('13.jpg',0)
equ = cv2.equalizeHist(img)
res = np.hstack((img,equ))#两个图片的像素分布连接在一起,拍成一维数组
cv2.imshow('p',equ)
cv2.waitKey(0)
CLAHE对比限制适应性直方图均衡化
之前的直方图均衡化会改版图像对比度,这不是好办法。例如下面的图片。
对比度改变以后丢失很多图像信息(人脸的阴影),造成这样的原因是因为该图像的直方图没有集中在同一个区域。
这里使用自适应直方图均衡化。此方法将图像分成小区域,称之为tiles,在opencv中默认为8×8,之后对这些tiles直方图均衡化。所以每一个去榆中,直方图会集中在一个小的区域。但是如果有噪声,噪声会被放大。为了避免此情况发生,要使用对比度限制。对于每个tiles,如果直方图中的bin超过对比度上限,就将其中的像素均分到其他bins中,然后直方图均衡化。最后使用双线性插值对每一个tiles的边界进行拼接。
import cv2
import numpy as np
from matplotlib import pyplot as plt
img = cv2.imread('13.jpg',0)
# create a CLAHE object (Arguments are optional).
clahe = cv2.createCLAHE(clipLimit=2.0, tileGridSize=(8,8))
cl1 = clahe.apply(img)
cv2.imshow('p',cl1)
cv2.waitKey(0)
结果如下: