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

目标

在本节中,

  • 我们将学习直方图均衡化的概念,并使用它来改善图像的对比度。

理论

考虑一个图像,其像素值仅限于某些特定的值范围内。例如,较亮的图像将所有像素都限制在高值范围内。但是,一个好的图像将具有来自图像所有区域的像素。因此,您需要将此直方图拉伸到两端(如下图所示,来自维基百科),这就是直方图均衡化所做的(简单来说)。通常,这会提高图像的对比度。

image

我建议您阅读维基百科关于直方图均衡化的页面,以获取更多详细信息。它有一个非常好的解释,并附有示例,因此您在阅读后几乎可以理解所有内容。相反,在这里我们将看到它的 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_BGR)
从文件加载图像。

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

现在我们找到最小直方图值(不包括 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 = np.ma.filled(cdf_m,0).astype('uint8')

现在我们有了查找表,它为我们提供了每个输入像素值的输出像素值的信息。所以我们只需应用变换。

img2 = cdf[img]

现在我们像以前一样计算它的直方图和 cdf(您这样做),结果如下

image

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

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)
均衡化灰度图像的直方图。

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

当图像的直方图被限制在特定区域时,直方图均衡化效果很好。在强度变化很大且直方图覆盖较大区域(即同时存在亮像素和暗像素)的地方,它效果不佳。请查看其他资源中的 SOF 链接。

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

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

image

诚然,在直方图均衡化之后,背景对比度得到了改善。但是比较两张图像中雕像的脸。由于过度亮度,我们丢失了大部分信息。这是因为它的直方图不像我们在以前的案例中看到的那样被限制在特定区域(尝试绘制输入图像的直方图,您将获得更多的直觉)。

因此,为了解决这个问题,使用了自适应直方图均衡化。在此方法中,图像被分成称为“tiles”的小块(在 OpenCV 中,tileSize 默认设置为 8x8)。然后像往常一样对每个块进行直方图均衡化。因此,在小区域中,直方图会将自身限制在小区域内(除非存在噪声)。如果存在噪声,它将被放大。为了避免这种情况,应用了对比度限制。如果任何直方图 bin 高于指定的对比度限制(在 OpenCV 中默认为 40),则在应用直方图均衡化之前,这些像素将被剪切并均匀分布到其他 bin。均衡化之后,为了消除 tile 边界中的伪影,应用了双线性插值。

以下代码片段显示了如何在 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 类的智能指针并对其进行初始化。

查看下面的结果并将其与上面的结果进行比较,尤其是雕像区域

image

附加资源

  1. 维基百科关于直方图均衡化的页面
  2. Numpy 中的屏蔽数组

另请查看这些关于对比度调整的 SOF 问题

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