我们之前对直方图有一个大概的了解,它可以将图像的不同颜色的像素值在坐标上表示出来,现在我们来讨论直方图的均衡化,在基本概念的基础上来进行实战化。
假设有一个图像,其像素值仅局限于某些特定范围的值。举个例子,其比较明亮的图像区域所有的像素仅局限于高值,但实际上一个好的图像,其像素会来源于图像上的所有区域。所以我们需要拉伸/伸展直方图,这也是直方图均衡化所做的。而这样的做法通常也可以优化图像的对比度:
这种方法通常用来增加许多图像的全局对比度,尤其是当图像的有用数据的对比度相当接近的时候。通过这种方法,亮度可以更好地在直方图上分布。这样就可以用于增强局部的对比度而不影响整体的对比度,直方图均衡化通过有效地扩展常用的亮度来实现这种功能。
这种方法对于背景和前景都太亮或者太暗的图像非常有用,这种方法尤其是可以带来X光图像中更好的骨骼结构显示以及曝光过度或者曝光不足照片中更好的细节。这种方法的一个主要优势是它是一个相当直观的技术并且是可逆操作,如果已知均衡化函数,那么就可以恢复原始的直方图,并且计算量也不大。
这种方法的一个缺点是它对处理的数据不加选择,它可能会增加背景噪声的对比度并且降低有用信号的对比度。
原理
现在我们拿到一个256图像,我们知道什么?
1、像素值是0-255,即能表达256种颜色.
2、我们能统计出各个像素值的像素个数.即我们知道原图的概率分布
3、我们希望生成的新图的像素值的概率分布是平均分布的.
我们要求的是一个函数T,可以使得像素分布从下面左图变成右图.其中L是像素值的最大值+1:
我们根据一道数学题来进行推导:
很简单,已知x的概率分布,及x,y的转换关系,可以求得y的概率分布。F_x(x) 就相当于已知的第二点,即原始图片的像素的概率分布,F_Y(y)就相当于已知的第三点,即转换后的图片的像素要均匀分布,现在要求的是y = T(x)的这个T是什么样的,这样就可以把原图的像素x,转换成均衡化后的图片的像素y。根据上面的数学题,我们可以继续推导,得到s和r的关系.即我们所要求的转换函数T:
以上,就是做直方图均衡化的数学原理。
OpenCV直方图均衡化
直方图均衡化的三种情况,分别是:
- 灰度图像直方图均衡化
- 彩色图像直方图均衡化
- YUV 直方图均衡化
先来看灰度直方图均衡化,我们输入一个灰度图像就可以,函数原型:
dst=cv.equalizeHist(src[, dst])
参数为输入的图像。
代码:
def grayequal(img): gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) dst = cv2.equalizeHist(gray) cv2.imshow("res", gray) cv2.imshow("dst", dst) cv2.waitKey(0) cv2.destroyWindow()
我们再来看看彩色图像的均值化,彩色图像的直方图均衡化和灰度图像略有不同,需要将彩色图像先用split()方法,将三个通道拆分,然后分别进行均衡化.最后使用merge()方法将均衡化之后的三个通道进行合并.操作如下:
def colorequal(img): (b, g, r) = cv2.split(img) bH = cv2.equalizeHist(b) gH = cv2.equalizeHist(g) rH = cv2.equalizeHist(r) # 合并每一个通道 result = cv2.merge((bH, gH, rH)) cv2.imshow("img",img) cv2.imshow("dst", result) cv2.waitKey(0)
继续看YUV图像直方图均值化:
def yuvequal(img): imgYUV = cv2.cvtColor(img, cv2.COLOR_BGR2YCrCb) cv2.imshow("src", img) channelsYUV = cv2.split(imgYUV) channelsYUV[0] = cv2.equalizeHist(channelsYUV[0]) channels = cv2.merge(channelsYUV) result = cv2.cvtColor(channels, cv2.COLOR_YCrCb2BGR) cv2.imshow("dst", result) cv2.waitKey(0)
自适应直方图均值化
我们刚刚看到的第一个直方图均衡化,考虑了的是图像的全局对比度。在很多情况下,这并不是一个好的处理方法。例如,下图显示了一个输入图像及其经过全局直方图均衡后的结果:
直方图均衡化后的背景对比度确实有所提高,但是让我们比较两幅图中雕像的脸,由于亮度过高,我们丢失了脸部的大部分信息。这是因为它的直方图并不像我们在前面的例子中看到的那样局限于一个特定的区域。
为了解决这一问题,我们可以采用自适应的直方图均衡化。在这个方法中,图像会被划分为称为“tiles”的小块 (在OpenCV中,tileSize默认为8x8)。然后像先前一样对每个块进行直方图均衡。
那么现在在一个小区域内,直方图会被限制在一个小区域内(排除有噪声的情况);如果有噪音,区域会被放大。为了避免这种情况,我们可以应用对比度限制 contrast limiting。
如果任何直方图中的bin 高于指定的对比度限制 (OpenCV中默认bin 为40),则可以在应用直方图均衡化之前,将这些像素剪切并均匀分布到其他bin 中。在均匀化后,再采用双线性插值bilinear interpolation 的方法来去除tiles 小块边界中的后天的人工痕迹。
我们来看代码:
import numpy as np import cv2 img = cv2.imread('tsukuba_l.png',0) # 创建一个 CLAHE 对象 (Arguments 可写可不写) # clipLimit颜色对比度的阈值 # titleGridSize进行像素均衡化的网格大小,即在多少网格下进行直方图的均衡化操作 clahe = cv2.createCLAHE(clipLimit=2.0, tileGridSize=(8,8)) cl1 = clahe.apply(img) cv2.imwrite('clahe_2.jpg',cl1)
可以看到,图像更加的清晰,事实上,我们称之为——图像锐化,它是由OpenCV中的直方图操作完成的。