新视界-OpenCV教程系列文章
新视界-OpenCV教程(1)-入门介绍
OpenCV 中的图形用户界面特征系列
新视界-OpenCV教程(2)-图片入门
新视界-OpenCV教程(3)-视频入门
新视界-OpenCV教程(4)- 绘图功能
新视界-OpenCV教程(5)- 鼠标的画笔功能
新视界-OpenCV教程(6)- 作为调色板的轨迹栏
核心操作系列
新视界-OpenCV教程(7)- 对图片的基本操作
新视界-OpenCV教程(8)- 图像运算
新视界-OpenCV教程(9)- 性能测量和改进技术
OpenCV 中的图像处理系列
新视界-OpenCV教程(10)- 改变颜色空间
新视界-OpenCV教程(11)- 图像阈值
新视界-OpenCV教程(12)- 图像的几何变换
新视界-OpenCV教程(13)- 平滑图像
新视界-OpenCV教程(14)- 形态变换
新视界-OpenCV教程(15)- 图像梯度
新视界-OpenCV教程(16)- Canny 边缘检测
新视界-OpenCV教程(17)- 图像金字塔
新视界-OpenCV教程(18)- 轮廓:开端
新视界-OpenCV教程(19)- 轮廓:特征
新视界-OpenCV教程(20)- 轮廓:性质
新视界-OpenCV教程(21)- 轮廓:更多功能
新视界-OpenCV教程(22)- 轮廓:层级
新视界-OpenCV教程(23)- 直方图:查找,绘制,分析
本文目标
轮廓系列会分为4篇文章一一详细讲解。这是第二篇。
在本篇文章中,我们将学习直方图均衡化的概念,并用它来改善我们对比图片的过程。
假设有一个图像,其像素值仅局限于某些特定范围的值。举个栗子,其比较明亮的图像区域所有的像素仅局限于高值,但实际上一个好的图像,其像素会来源于图像上的所有区域。所以我们需要拉伸/伸展直方图,方法是结束end (如下图所示,图片来源维基百科),这也是直方图均衡化所做的(用简单的话语来说)。而这样的做法通常也可以优化图像的对比。
原文强烈建议我们阅读维基百科上关于直方图均衡化的更多细节。它有一个很好的例子解释了什么是直方图均衡化,这方便我们明白后面的所要讲的知识点。而在这,我们将看到其用Numpy 的实现。在那之后,我们将看到OpenCV 中的直方图均衡化功能。
维基百科关于直方图均衡化的链接在这里。
代码如下:
# 导入库
import cv2
import numpy as np
from matplotlib import pyplot as plt
# 读取图片
img = cv2.imread('wiki.jpg',0)
# numpy中的histogram() 函数默认使用10个相同大小的区间,然后返回hist 频数, bins(边界)
# 这里的hist 告诉我们有几个item 在第几个bin中
# 这里的bins (边界)告诉我们第几个bin 是在什么边界/范围中
hist,bins = np.histogram(img.flatten(),256,[0,256])
# 这里的cdf 是累积概率函数(cdf),从函数.cumsum()我们可以很直观的知道,
# cdf代表位于其域中的所有概率的累积和
cdf = hist.cumsum()
# 将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()
原图及得到的直方图如下:
从上图,我们可以看到直方图大部分位于较亮的区域,而我们想要的是更平缓的覆盖于全区域。为此,我们需要一个转换函数,它能将亮区域的输入像素映射到整个区域的输出像素中。这就是直方图均衡化的作用。
现在我们知道最小直方图值(不包括0),并应用wiki 页面中给出的直方图均衡化方程。先前的代码里我们用到了Numpy 中的掩码数组概念数组(the masked array concept array)。对于掩码数组,所有的操作都在非掩码元素上执行。感兴趣的话可以从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 值,结果如下:
从上结果我们可以得知,直方图均衡化后的图像的另一个重要的特性是,即使图像较暗,经过均衡后我们得到的图像几乎与我们一开始的图像相同。因此,这可以作为一个“参考工具”,使所有的图像具有相同的照明条件。这在很多情况下都很有用!例如,在人脸识别中,在对人脸数据进行训练之前,我们可以对人脸图像进行直方图均衡化处理,使其都在相同的光照条件下。
在前一节,我们用了numpy 中的函数来做直方图均衡化,但其实OpenCV 也有一个函数可以做到这一点,那就是cv2.equalizeHist()。它的输入需要一个灰度图像,输出则是我们的直方图均衡化图像。
下面是一个基本的代码片段,显示了它对我们使用的相同图像的用法:
img = cv2.imread('wiki.jpg',0)
#opencv 的直方图均衡化函数
equ = cv2.equalizeHist(img)
res = np.hstack((img,equ)) #一行并一行地叠加图片
cv2.imwrite('res.png',res)
原图及代码运行后得到的图像:
所以现在我们可以在不同的光照条件下拍摄不同的图像,然后对其进行直方图均衡并检查结果。
当图像的直方图被限制在一个特定的区域时,直方图均衡化得到的结果是很好的。但是在直方图覆盖较大区域,亮度变化较大的时候,这种方法就行不通了。
我们刚刚看到的第一个直方图均衡化,考虑了的是图像的全局对比度。在很多情况下,这并不是一个好主意。例如,下图显示了一个输入图像及其经过全局直方图均衡后的结果。
直方图均衡化后的背景对比度确实有所提高,但是让我们比较下两幅图中雕像的脸,由于亮度过高,我们丢失了脸部的大部分信息。这是因为它的直方图并不像我们在前面的例子中看到的那样局限于一个特定的区域(可以尝试绘制输入图像的直方图,将会有甘多的感触)。
为了解决这一问题,我们可以采用自适应的直方图均衡化。在这个方法中,图像会被划分为称为“tiles”的小块 (在OpenCV中,tileSize默认为8x8)。然后像先前一样对每个块进行直方图均衡。
那么现在在一个小区域内,直方图会被限制在一个小区域内(排除有噪声的情况);如果有噪音,区域会被放大。为了避免这种情况,我们可以应用对比度限制 contrast limiting。
如果任何直方图中的bin 高于指定的对比度限制 (OpenCV中默认bin 为40),则可以在应用直方图均衡化之前,将这些像素剪切并均匀分布到其他bin 中。在均匀化后,再采用双线性插值bilinear interpolation 的方法来去除tiles 小块边界中的后天的人工痕迹。
下面的代码片段展示了如何在OpenCV 中应用CLAHE:
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教程(25)- 直方图:2D直方图。
如果你觉得我的文章有用,顺手点个赞,关注下我的专栏或则留下你的评论吧!