OpenCV  4.10.0
开源计算机视觉
正在加载...
正在搜索...
无匹配项
直方图 - 2:直方图均衡化

目标

在本节中:

  • 我们将学习直方图均衡化的概念并将其用于改进图像的对比度。

理论

考虑仅限于某些特定值的像素值的图像。例如,较亮的图像的所有像素都限制在较高的值。但一张好图像的像素来自图像的所有区域。因此,你需要将此直方图拉伸到两端(如下图所示,来自 Wikipedia),而这就是直方图均衡化所做的(用简单的话说)。这通常可以改善图像的对比度。

图片

我建议你阅读 Wikipedia 上有关 直方图均衡化 的页面,以了解更多有关它的细节。它有一个很好的解释,并有算例,所以你读完之后会理解几乎所有内容。相反,我们在这里将看到它的 Numpy 实现。之后,我们将看到 OpenCV 函数。

import numpy as np
import cv2 as cv
from matplotlib import pyplot as plt
img = cv.imread('wiki.jpg', cv.IMREAD_GRAYSCALE)
assert img is not None, "file could not be read, check with os.path.exists()"
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()
CV_EXPORTS_W Mat imread(const String &filename, int flags=IMREAD_COLOR)
从文件中加载图像。

你可以看到直方图位于更亮的区域。我们需要全光谱。为此,我们需要一个转换函数,将更亮区域的输入像素映射到全区域的输出像素。这就是直方图均衡化所做的。

现在我们找到最小直方图值(不包括 0)并应用如维基页面中所示的直方图均衡化方程。但我在这里使用了 Numpy 的掩码数组概念数组。对于掩码数组,所有操作都对非掩码元素执行。你可以从 Numpy 文档中阅读有关掩码数组的更多信息。

cdf_m = np.ma.masked_equal(cdf,0)
cdf_m = (cdf_m - cdf_m.min())*255/(cdf_m.max()-cdf_m.min())
将 cdf_m 用 0 填充,并转换为 uint8 类型 cdf = np.ma.filled(cdf_m,0).astype('uint8')

现在我们得到了查找表,它提供了有关每个输入像素值的输出像素值的信息。因此,我们只需应用转换即可。

img2 = cdf[img]

现在,我们像以前一样计算其直方图和 cdf(您来执行此操作),结果如下所示

图片

另一个重要特征是,即使图像是一个较暗的图像(而不是我们使用的较亮的图像),在均衡化之后,我们也会获得与我们获得的几乎相同的图像。因此,它被用作“参考工具”,以使得所有图像具有相同的照明条件。这在许多情况下很有用。例如,在面部识别中,在训练面部数据之前,将面部图像进行直方图均衡,以使其全部处于相同的照明条件下。

OpenCV 中的直方图均衡化

OpenCV 有一个函数可用于执行此操作,cv.equalizeHist()。其输入只是灰度图像,输出是我们的直方图均衡化图像。

以下是显示其对用于同一张图像的简单代码段

img = cv.imread('wiki.jpg', cv.IMREAD_GRAYSCALE)
assert img is not None, "file could not be read, check with os.path.exists()"
equ = cv.equalizeHist(img)
res = np.hstack((img,equ)) #并排堆叠图像
cv.imwrite('res.png',res)
CV_EXPORTS_W bool imwrite(const String &filename, InputArray img, const std::vector< int > &params=std::vector< int >())
将图像保存到指定的文件中。
void equalizeHist(InputArray src, OutputArray dst)
Equalizes the histogram of a grayscale image.

因此,现在您可以获取具有不同光照条件的不同图像,对其进行均衡化并检查结果。

当图像的直方图局限于特定区域时,直方图均衡化才有效。在直方图覆盖大区域(即存在明亮和暗像素)的大强度变化区域,它将无效。请查看附加资源中的 SOF 链接。

CLAHE(对比度受限自适应直方图均衡化)

我们刚看到的第一个直方图均衡化,考虑了图像的全局对比度。在很多情况下,这不是一个好主意。例如,下图显示了一张输入图像及其在进行全局直方图均衡化后的结果。

图片

确实,在进行直方图均衡化后,背景对比度得到了改善。但请比较两张图像中雕像的面部。由于过度亮度,我们在那里丢失了大部分信息。这是因为它在直方图中并未局限于特定区域,正如我们之前看到的那样。(尝试绘制输入图像的直方图,您将获得更多直观感受)。

为此,使用了自适应直方图均衡。图像会被分成称为“平铺”的小块(在 OpenCV 中,默认情况下平铺大小为 8x8)。然后对这些块分别执行常规直方图均衡处理。因此,在小区域中,直方图将限制在一个小区域(除非有噪点)。如果有噪点,噪点会被放大。为避免这种情况,请应用对比度限制。如果任何直方图柱状图块高于指定的对比度限制(在 OpenCV 中默认为 40),在应用直方图均衡处理之前,这些像素会被修剪并均匀分配到其他柱状图块中。均衡处理完成后,为消除平铺边框中的伪影,请应用双线性插值。

以下代码段展示了如何在 OpenCV 中应用 CLAHE

import numpy as np
import cv2 as cv
img = cv.imread('tsukuba_l.png', cv.IMREAD_GRAYSCALE)
assert img is not None, "file could not be read, check with os.path.exists()"
# 创建一个 CLAHE 对象(参数是可选的)。
clahe = cv.createCLAHE(clipLimit=2.0, tileGridSize=(8,8))
cl1 = clahe.apply(img)
cv.imwrite('clahe_2.jpg',cl1)
Ptr< CLAHE > createCLAHE(double clipLimit=40.0, Size tileGridSize=Size(8, 8))
创建一个指针,指向 cv::CLAHE 类并对其实现初始化。

查看以下结果并将其与以上结果进行比较,特别是雕像区域

图片

其他资源

  1. 直方图均衡的 Wikipedia 页面
  2. Numpy 中的掩蔽数组

还请查看这些关于对比度调节的 SOF 问题

  1. 如何在 OpenCV 中使用 C 调整对比度?
  2. 如何使用 opencv 均衡图像的对比度和亮度?

练习