OpenCV-Python实战(8)——直方图均衡化(含大量示例,建议收藏)

x33g5p2x  于2021-09-26 转载在 Python  
字(11.6k)|赞(0)|评价(0)|浏览(1716)

0. 前言

图像处理技术是计算机视觉项目的核心,通常是计算机视觉项目中的关键工具,可以使用它们来完成各种计算机视觉任务。在本文中,将介绍如何使用 OpenCV 函数 cv2.equalizeHist() 执行直方图均衡,并将其应用于灰度和彩色图像,cv2.equalizeHist() 函数将亮度归一化并提高图像的对比度。

1. 灰度直方图均衡化

使用 cv2.equalizeHist() 函数来均衡给定灰度图像的对比度:

  1. # 加载图像并转换为灰度图像
  2. image = cv2.imread('example.png')
  3. gray_image = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
  4. hist = cv2.calcHist([gray_image], [0], None, [256], [0, 256])
  5. # 直方图均衡化
  6. gray_image_eq = cv2.equalizeHist(gray_image)
  7. # 直方图均衡化后的图像直方图
  8. hist_eq = cv2.calcHist([gray_image_eq], [0], None, [256], [0, 256])

为了深入了解直方图均衡,我们对原始灰度图像进行修改,为图像的每个像素添加/减去 30,并计算直方图均衡前后的直方图:

  1. M = np.ones(gray_image.shape, dtype='uint8') * 30
  2. # 为图像的每个像素添加 30
  3. added_image = cv2.add(gray_image, M)
  4. hist_added_image = cv2.calcHist([added_image], [0], None, [256], [0, 256])
  5. # 直方图均衡化
  6. added_image_eq = cv2.equalizeHist(gray_image_eq)
  7. hist_eq_added_image = cv2.calcHist([added_image_eq], [0], None, [256], [0, 256])
  8. # 为图像的每个像素减去 30
  9. subtracted_image = cv2.subtract(gray_image, M)
  10. hist_subtracted_image = cv2.calcHist([subtracted_image], [0], None, [256], [0, 256])
  11. # 直方图均衡化
  12. subtracted_image_eq = cv2.equalizeHist(subtracted_image)
  13. hist_eq_subtracted_image = cv2.calcHist([subtracted_image_eq], [0], None, [256], [0, 256])

最后,绘制所有这些图像:

  1. def show_img_with_matplotlib(color_img, title, pos):
  2. img_RGB = color_img[:, :, ::-1]
  3. ax = plt.subplot(3, 4, pos)
  4. plt.imshow(img_RGB)
  5. plt.title(title, fontsize=8)
  6. plt.axis('off')
  7. def show_hist_with_matplotlib_gray(hist, title, pos, color):
  8. ax = plt.subplot(3, 4, pos)
  9. plt.xlabel("bins")
  10. plt.ylabel("number of pixels")
  11. plt.xlim([0, 256])
  12. plt.plot(hist, color=color)
  13. # 可视化
  14. show_img_with_matplotlib(cv2.cvtColor(gray_image, cv2.COLOR_GRAY2BGR), "gray", 1)
  15. show_hist_with_matplotlib_gray(hist, "grayscale histogram", 2, 'm')
  16. show_img_with_matplotlib(cv2.cvtColor(added_image, cv2.COLOR_GRAY2BGR), "gray lighter", 5)
  17. show_hist_with_matplotlib_gray(hist_added_image, "grayscale histogram", 6, 'm')
  18. show_img_with_matplotlib(cv2.cvtColor(subtracted_image, cv2.COLOR_GRAY2BGR), "gray darker", 9)
  19. show_hist_with_matplotlib_gray(hist_subtracted_image, "grayscale histogram", 10, 'm')
  20. # 其他图像的可视化方法类似,不再赘述
  21. # ...

程序运行的输出如下图所示:

在上图中,我们可以看到三个均衡化后的图像非常相似,这也反映在均衡化后的直方图中,这是因为直方图均衡化倾向于标准化图像的亮度,同时增加对比度。

2. 颜色直方图均衡化

使用相同的方法,我们可以在彩色图像中执行直方图均衡,将直方图均衡应用于 BGR 图像的每个通道(虽然这不是彩色图像直方图均衡的最佳方法),创建 equalize_hist_color() 函数,使用 cv2.split() 分割 BGR 图像并将 cv2.equalizeHist() 函数应用于每个通道,最后,使用 cv2.merge() 合并结果通道:

  1. def equalize_hist_color(img):
  2. # 使用 cv2.split() 分割 BGR 图像
  3. channels = cv2.split(img)
  4. eq_channels = []
  5. # 将 cv2.equalizeHist() 函数应用于每个通道
  6. for ch in channels:
  7. eq_channels.append(cv2.equalizeHist(ch))
  8. # 使用 cv2.merge() 合并所有结果通道
  9. eq_image = cv2.merge(eq_channels)
  10. return eq_image

接下来,将此函数应用于三个不同的图像:原始 BGR 图像、将原始图像的每个像素值添加 10、将原始图像的每个像素值减去 10,并计算直方图均衡前后的直方图:

  1. # 加载图像
  2. image = cv2.imread('example.png')
  3. # 计算直方图均衡前后的直方图
  4. hist_color = hist_color_img(image)
  5. image_eq = equalize_hist_color(image)
  6. hist_image_eq = hist_color_img(image_eq)
  7. M = np.ones(image.shape, dtype="uint8") * 10
  8. # 为图像的每个像素添加 10
  9. added_image = cv2.add(image, M)
  10. # 直方图均衡前后的直方图
  11. hist_color_added_image = hist_color_img(added_image)
  12. added_image_eq = equalize_hist_color(added_image)
  13. hist_added_image_eq = hist_color_img(added_image_eq)
  14. # 为图像的每个像素减去 10
  15. subtracted_image = cv2.subtract(image, M)
  16. # 直方图均衡前后的直方图
  17. hist_color_subtracted_image = hist_color_img(subtracted_image)
  18. subtracted_image_eq = equalize_hist_color(subtracted_image)
  19. hist_subtracted_image_eq = hist_color_img(subtracted_image_eq)

最后,绘制所有这些图像:

  1. def show_img_with_matplotlib(color_img, title, pos):
  2. img_RGB = color_img[:, :, ::-1]
  3. ax = plt.subplot(3, 4, pos)
  4. plt.imshow(img_RGB)
  5. plt.title(title, fontsize=8)
  6. plt.axis('off')
  7. def show_hist_with_matplotlib_rgb(hist, title, pos, color):
  8. ax = plt.subplot(3, 4, pos)
  9. plt.xlabel("bins")
  10. plt.ylabel("number of pixels")
  11. plt.xlim([0, 256])
  12. for (h, c) in zip(hist, color):
  13. plt.plot(h, color=c)
  14. # 可视化
  15. show_img_with_matplotlib(image, "image", 1)
  16. show_hist_with_matplotlib_rgb(hist_color, "color histogram", 2, ['b', 'g', 'r'])
  17. show_img_with_matplotlib(added_image, "image lighter", 5)
  18. show_hist_with_matplotlib_rgb(hist_color_added_image, "color histogram", 6, ['b', 'g', 'r'])
  19. show_img_with_matplotlib(subtracted_image, "image darker", 9)
  20. show_hist_with_matplotlib_rgb(hist_color_subtracted_image, "color histogram", 10, ['b', 'g', 'r'])
  21. # 其他图像的可视化方法类似,不再赘述
  22. # ...

将直方图均衡化应用于 BGR 图像的每个通道并不是颜色直方图均衡化的好方法,这是由于 BGR 色彩空间的加性特性导致彩色图像的颜色变化很大。由于我们独立地改变三个通道中的亮度和对比度,因此在合并均衡通道时,这可能会导致图像中出现新的色调,正如上图所看到的那样。
一种颜色直方图均衡化更好的方法是将 BGR 图像转换为包含亮度/强度通道的色彩空间( YuvLabHSVHSL )。然后,只在亮度通道上应用直方图均衡,最后合并通道并将它们转换回 BGR 颜色空间,以 HSV 空间为例,创建 equalize_hist_color_hsv() 函数实现上述颜色直方图归一化方法:

  1. def equalize_hist_color_hsv(img):
  2. H, S, V = cv2.split(cv2.cvtColor(img, cv2.COLOR_BGR2HSV))
  3. eq_V = cv2.equalizeHist(V)
  4. eq_image = cv2.cvtColor(cv2.merge([H, S, eq_V]), cv2.COLOR_HSV2BGR)
  5. return eq_image

接下来,将此函数应用于三个不同的图像:原始 BGR 图像、将原始图像的每个像素值添加 10、将原始图像的每个像素值减去 10,并计算直方图均衡前后的直方图:

  1. hist_color = hist_color_img(image)
  2. # 计算直方图均衡前后的直方图
  3. image_eq = equalize_hist_color_hsv(image)
  4. hist_image_eq = hist_color_img(image_eq)
  5. M = np.ones(image.shape, dtype="uint8") * 10
  6. # 为图像的每个像素添加 10
  7. added_image = cv2.add(image, M)
  8. hist_color_added_image = hist_color_img(added_image)
  9. # 直方图均衡前后的直方图
  10. added_image_eq = equalize_hist_color_hsv(added_image)
  11. hist_added_image_eq = hist_color_img(added_image_eq)
  12. # 为图像的每个像素减去 10
  13. subtracted_image = cv2.subtract(image, M)
  14. hist_color_subtracted_image = hist_color_img(subtracted_image)
  15. # 直方图均衡前后的直方图
  16. subtracted_image_eq = equalize_hist_color_hsv(subtracted_image)
  17. hist_subtracted_image_eq = hist_color_img(subtracted_image_eq)

最后,绘制所有这些图像:

  1. # show_img_with_matplotlib() 和 show_hist_with_matplotlib_rgb() 函数与上一示例相同
  2. show_img_with_matplotlib(image, "image", 1)
  3. show_hist_with_matplotlib_rgb(hist_color, "color histogram", 2, ['b', 'g', 'r'])
  4. show_img_with_matplotlib(added_image, "image lighter", 5)
  5. show_hist_with_matplotlib_rgb(hist_color_added_image, "color histogram", 6, ['b', 'g', 'r'])
  6. # 其他图像的可视化方法类似,不再赘述
  7. # ...

由上图可以看出,仅均衡 HSV 图像的 V 通道得到的结果比均衡 BGR 图像的所有通道的效果要好很多,也可以将这种方法用于其他包含亮度/强度通道的色彩空间( YuvLabHSL )。

3. 对比度受限的自适应直方图均衡化

在本节中,将介绍如何应用对比度受限的自适应直方图均衡化 ( Contrast Limited Adaptive Histogram Equalization, CLAHE ) 来均衡图像,CLAHE 是自适应直方图均衡化( Adaptive Histogram Equalization, AHE )的一种变体,区别在于其对比度的增大是受限的。图像相对均匀区域中的噪声被 AHE 过度放大,而 CLAHE 通过限制对比度增大来解决这个问题。该算法通过创建原始图像的多个直方图,并使用这些直方图来重新分配图像的亮度,用于提高图像的对比度。
接下来,将 CLAHE 应用于灰度和彩色图像。应用 CLAHE 时,有两个重要参数,第一个是 clipLimit,它设置对比度限制的阈值,默认值为 40;第二个是 tileGridSize ,它设置行和列中的 tiles 数量。应用 CLAHE 时,图像被分成称为 tiles (默认为 8 x 8 )的小块以执行其计算。
将 CLAHE 应用于灰度图像,需要使用以下代码:

  1. # 加载图像
  2. image = cv2.imread('example.png')
  3. gray_image = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
  4. # 灰度图像应用 CLAHE
  5. clahe = cv2.createCLAHE(clipLimit=2.0)
  6. gray_image_clahe = clahe.apply(gray_image)
  7. # 使用不同 clipLimit 值
  8. clahe.setClipLimit(5.0)
  9. gray_image_clahe_2 = clahe.apply(gray_image)
  10. clahe.setClipLimit(10.0)
  11. gray_image_clahe_3 = clahe.apply(gray_image)
  12. clahe.setClipLimit(20.0)
  13. gray_image_clahe_4 = clahe.apply(gray_image)

然后,我们将 CLAHE 应用于彩色图像,类似于彩色图像对比度均衡的方法,创建四个函数以仅在不同颜色空间的亮度通道上使用 CLAHE 来均衡化彩色图像:

  1. def equalize_clahe_color_hsv(img):
  2. cla = cv2.createCLAHE(clipLimit=4.0)
  3. H, S, V = cv2.split(cv2.cvtColor(img, cv2.COLOR_BGR2HSV))
  4. eq_V = cla.apply(V)
  5. eq_image = cv2.cvtColor(cv2.merge([H, S, eq_V]), cv2.COLOR_HSV2BGR)
  6. return eq_image
  7. def equalize_clahe_color_lab(img):
  8. cla = cv2.createCLAHE(clipLimit=4.0)
  9. L, a, b = cv2.split(cv2.cvtColor(img, cv2.COLOR_BGR2Lab))
  10. eq_L = cla.apply(L)
  11. eq_image = cv2.cvtColor(cv2.merge([eq_L, a, b]), cv2.COLOR_Lab2BGR)
  12. return eq_image
  13. def equalize_clahe_color_yuv(img):
  14. cla = cv2.createCLAHE(clipLimit=4.0)
  15. Y, U, V = cv2.split(cv2.cvtColor(img, cv2.COLOR_BGR2YUV))
  16. eq_Y = cla.apply(Y)
  17. eq_image = cv2.cvtColor(cv2.merge([eq_Y, U, V]), cv2.COLOR_YUV2BGR)
  18. return eq_image
  19. def equalize_clahe_color(img):
  20. cla = cv2.createCLAHE(clipLimit=4.0)
  21. channels = cv2.split(img)
  22. eq_channels = []
  23. for ch in channels:
  24. eq_channels.append(cla.apply(ch))
  25. eq_image = cv2.merge(eq_channels)
  26. return eq_image
  27. # 彩色图像应用 CLAHE
  28. image_clahe_color = equalize_clahe_color(image)
  29. image_clahe_color_lab = equalize_clahe_color_lab(image)
  30. image_clahe_color_hsv = equalize_clahe_color_hsv(image)
  31. image_clahe_color_yuv = equalize_clahe_color_yuv(image)
  32. # 可视化
  33. show_img_with_matplotlib(cv2.cvtColor(gray_image, cv2.COLOR_GRAY2BGR), "gray", 1)
  34. show_img_with_matplotlib(cv2.cvtColor(gray_image_clahe, cv2.COLOR_GRAY2BGR), "gray CLAHE clipLimit=2.0", 2)
  35. show_img_with_matplotlib(cv2.cvtColor(gray_image_clahe_2, cv2.COLOR_GRAY2BGR), "gray CLAHE clipLimit=5.0", 3)
  36. # 其他图像的可视化方法类似,不再赘述
  37. # ...

将所有这些函数应用于测试图像后比较结果,如下图所示:

在上图中,我们可以看到改变 clipLimit 参数在测试图像上应用 CLAHE 后的不同效果,同时也可以看到在不同颜色空间( LAB、HSV 和 YUV )的亮度通道上应用 CLAHE 后的不同结果。其中,可以看到在 BGR 图像的三个通道上应用 CLAHE 与仅在不同颜色空间的亮度通道上使用 CLAHE 的不同效果。

4. 比较 CLAHE 和直方图均衡化

为完整起见,在 compare_hist_equalization_clahe.py 脚本中,可以看到 CLAHE 和直方图均衡化 ( cv2.equalizeHist() ) 如何在同一图像上工作,同时可视化生成的图像和生成的直方图。

  1. image = cv2.imread('2.png')
  2. gray_image = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
  3. hist = cv2.calcHist([gray_image], [0], None, [256], [0, 256])
  4. # 直方图均衡化
  5. gray_image_eq = cv2.equalizeHist(gray_image)
  6. # 计算直方图
  7. hist_eq = cv2.calcHist([gray_image_eq], [0], None, [256], [0, 256])
  8. # 创建 clahe:
  9. clahe = cv2.createCLAHE(clipLimit=4.0)
  10. # 在灰度图像上应用 clahe
  11. gray_image_clahe = clahe.apply(gray_image)
  12. # 计算直方图
  13. hist_clahe = cv2.calcHist([gray_image_clahe], [0], None, [256], [0, 256])

可视化的结果如下图所示:

通过以上对比,可以肯定地说,在许多情况下,CLAHE 比应用直方图均衡化有更好的结果和性能。

5. 直方图的比较

OpenCV 提供的另一个与直方图相关的函数是 cv2.compareHist(),该函数可用于计算两个直方图的匹配程度。由于直方图反映了图像中像素值的强度分布,因此该函数也可以用于比较图像,但是由于直方图仅显示统计信息,而不显示像素的位置。因此,图像比较的常用方法是将图像划分为一定数量的区域(通常大小相同),计算每个区域的直方图,最后将所有直方图连接起来,创建图像的特征表示。为了简单起见,示例将仅使用一个区域(完整图像),并不会将图像划分多个区域。
cv2.compareHist() 函数的用法:

  1. cv2.compareHist(H1, H2, method)

这里,H1H2 是被比较的直方图,method表示度量方法。OpenCV 提供了四种不同的度量方法( method )来计算匹配程度:

度量方法解释
cv2.HISTCMP_CORREL计算两个直方图之间的相关性,此指标返回 [-1, 1] 范围内的值,其中 1 表示完美匹配,-1 表示完全不匹配
cv2.HISTCMP_CHISQR计算两个直方图之间的卡方距离,此指标返回 [0, unbounded] 范围内的值,其中 0 表示完美匹配,而不匹配则使用 unbounded 表示
cv2.HISTCMP_INTERSECT计算两个直方图之间的交集,如果直方图被归一化,则该指标返回范围 [0, 1] 内的值,其中 1 表示完全匹配,0 表示完全不匹配
cv2.HISTCMP_BHATTACHARYYA计算两个直方图之间的 Bhattacharyya 距离。此指标返回 [0, 1] 范围内的值,其中 0 是完美匹配,1 完全不匹配

为了对比不同的度量方法,我们首先图像并对其进行变换,然后使用所有度量方法计算这些图像和测试图像之间的相似度。

  1. # 加载图像
  2. image = cv2.imread('15.png')
  3. # 转换为灰度图像
  4. gray_image = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
  5. M = np.ones(gray_image.shape, dtype='uint8') * 30
  6. # 所有像素值加上 30
  7. added_image = cv2.add(gray_image, M)
  8. # 所有像素值减去 30
  9. subtracted_image = cv2.subtract(gray_image, M)
  10. # 使用模糊滤镜
  11. blurred_image = cv2.blur(gray_image, (10,10))
  12. def load_all_test_images():
  13. images = []
  14. images.append(gray_image)
  15. images.append(added_image)
  16. images.append(subtracted_image)
  17. images.append(blurred_image)
  18. return images

使用四种不同的度量方法计算这些图像和测试图像之间的相似度:

  1. for img in test_images:
  2. # 计算直方图
  3. hist = cv2.calcHist([img], [0], None, [256], [0, 256])
  4. # 直方图归一化
  5. hist = cv2.normalize(hist, hist, norm_type=cv2.NORM_L1)
  6. hists.append(hist)
  7. # 使用 cv2.HISTCMP_CORREL 度量方法
  8. gray_gray = cv2.compareHist(hists[0], hists[1], cv2.HISTCMP_CORREL)
  9. gray_grayblurred = cv2.compareHist(hists[0], hists[1], cv2.HISTCMP_CORREL)
  10. gray_addedgray = cv2.compareHist(hists[0], hists[2], cv2.HISTCMP_CORREL)
  11. gray_subgray = cv2.compareHist(hists[0], hists[3], cv2.HISTCMP_CORREL)
  12. # 使用 cv2.HISTCMP_CHISQR 度量方法
  13. gray_gray = cv2.compareHist(hists[0], hists[0], cv2.HISTCMP_CHISQR)
  14. gray_grayblurred = cv2.compareHist(hists[0], hists[1], cv2.HISTCMP_CHISQR)
  15. gray_addedgray = cv2.compareHist(hists[0], hists[2], cv2.HISTCMP_CHISQR)
  16. gray_subgray = cv2.compareHist(hists[0], hists[3], cv2.HISTCMP_CHISQR)
  17. # 使用 cv2.HISTCMP_INTERSECT 度量方法
  18. gray_gray = cv2.compareHist(hists[0], hists[0], cv2.HISTCMP_INTERSECT)
  19. gray_grayblurred = cv2.compareHist(hists[0], hists[1], cv2.HISTCMP_INTERSECT)
  20. gray_addedgray = cv2.compareHist(hists[0], hists[2], cv2.HISTCMP_INTERSECT)
  21. gray_subgray = cv2.compareHist(hists[0], hists[3], cv2.HISTCMP_INTERSECT)
  22. # 使用 cv2.HISTCMP_BHATTACHARYYA 度量方法
  23. gray_gray = cv2.compareHist(hists[0], hists[0], cv2.HISTCMP_BHATTACHARYYA)
  24. gray_grayblurred = cv2.compareHist(hists[0], hists[1], cv2.HISTCMP_BHATTACHARYYA)
  25. gray_addedgray = cv2.compareHist(hists[0], hists[2], cv2.HISTCMP_BHATTACHARYYA)
  26. gray_subgray = cv2.compareHist(hists[0], hists[3], cv2.HISTCMP_BHATTACHARYYA)

程序的输出如下所示:

从上图可以看出,img 1 所有指标都完美匹配,因为它是相同的图像,img 2 也具有较好的匹配指标,这是因为 img 2 是查询图像的平滑版本,而 img 3img 4 给出的匹配程度很差,这是因为直方图发生了偏移。

小结

在处理直方图时,直方图均衡化也是一个重要方法和功能,我们学习了如何对灰度和彩色图像进行直方图均衡化,也看到了直方图比较对于执行图像比较的重要帮助,OpenCV 提供的四种度量方式来衡量两个直方图之间的相似性,分别是 cv2.HISTCMP_CORRELcv2.HISTCMP_CHISQRcv2.HISTCMP_INTERSECTcv2.HISTCMP_BHATTACHARYYA

相关文章