OpenCV系列教程三:图像直方图及阈值处理、图像轮廓、形态学操作、车辆统计项目

一、图像直方图及阈值处理

1.1 图像直方图基本概念

参考《相机直方图:色调和对比度》

  图像直方图是一种显示图像中像素值分布情况的统计图表。它表示图像中各个像素强度值出现的频率,可以用来分析图像的对比度、亮度、动态范围等特性。直方图的横轴表示像素值(如果是灰度图,0表示最暗,255表示最亮),纵轴表示各像素值的像素数量。

在这里插入图片描述
  如上图所示,左图水面整体偏亮,此部分对应于图像直方图右侧高亮度区域。右图将其分为上中下三个部分分别进行统计,上部像素分布均匀;中间是水面,像素过于集中;下方整体偏亮。

图像直方图既可以统计灰度图,也可以统计彩色图:

  1. 灰度图像直方图:横轴为0-255的灰度值,纵轴为该灰度值出现的频率。
  2. 彩色图像直方图:对RGB图像,可以为每个通道(红、绿、蓝)绘制单独的直方图,显示各通道像素值的分布。

1.2 统计直方图

1.2.1 直接统计

由于图像直方图是统计图像中像素值分布情况,所以可以直接使用plt.hist对图像的灰度值进行统计。

import cv2
import numpy as np
import matplotlib.pyplot as plt

# 读取图像并转换为灰度
img = cv2.imread('./lena.png')
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)

# 创建图形
plt.figure(figsize=(12, 5))
ax1 = plt.subplot(121);ax1.imshow(gray, cmap='gray');ax1.axis('off');ax1.set_title('Grayscale Image')
ax2 = plt.subplot(122);ax2.hist(gray.ravel(), 256, [0, 256]);ax2.set_title('Histogram')

# tight_layout自调整子图参数,使之填充整个图像区域,同时确保子图之间的标签和标题不会重叠。
plt.tight_layout()
plt.show()

在这里插入图片描述

使用plt.subplot的方式并排显示,由于其默认显示坐标轴,两张图坐标会互相重叠

1.2.2 使用OpenCV统计图像直方图

OpenCV 中可以使用cv2.calcHist进行图像直方图计算,其语法为:

calcHist(images, channels, mask, histSize, ranges[, hist[, accumulate]]) -> hist
  1. images:输入图像(列表形式,可以对一批图像进行统计)。即使只传入一张图像,也要放在列表中。

  2. channels:需要计算直方图的通道。对于灰度图只能为[0](单通道);对于彩色图像, [0][1][2] 分别表示蓝、绿、红三个通道。

  3. mask:掩膜图像。如果只想计算图像某一部分的直方图,可以传入一个与原图像大小相同的二值掩膜图像,白色部分表示计算区域,黑色部分忽略。若不需要则设 None

  4. histSize:直方图的 bins 数量,一般设置为 [256],表示256个灰度值都单独统计。假设设为16,则每15个像素区间统计一次。
    在这里插入图片描述

  5. ranges:统计的像素值范围,一般为 [0, 256]

  6. accumulate:是否累积,默认为False。如果对一组图像进行统计,可以设为True,表示统计图像时,在上一个直方图的基础上累积结果,而不是从0开始。

import cv2
import numpy as np
import matplotlib.pyplot as plt

# 读取图像
img = cv2.imread('./lena.png')
img_rgb = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)

# 统计直方数据
histb = cv2.calcHist([img], [0], None, [256], [0, 255])
histg = cv2.calcHist([img], [1], None, [256], [0, 255])
histr = cv2.calcHist([img], [2], None, [256], [0, 255])

# 创建一个图形窗口,包含两个子图
fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(14, 5))

# 在第一个子图中显示原图
ax1.imshow(img_rgb);ax1.set_title('Original Image');ax1.axis('off')  # 不显示坐标轴

# 在第二个子图中绘制直方图
ax2.plot(histb, color='b', label='blue');ax2.plot(histg, color='g', label='green');
ax2.plot(histr, color='r', label='red');ax2.set_title('Histogram using opencv');ax2.legend()

plt.tight_layout()
plt.show()

在这里插入图片描述

1.2.3 使用掩膜

  我们可以通过使用掩膜,只统计图中感兴趣的区域。掩膜是与原图像大小相同的二值掩膜图像,只有白色区域会被统计。
在这里插入图片描述

# 生成灰度图,并创建掩膜
img = cv2.imread('./lena.png')
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
mask = np.zeros(gray.shape, np.uint8)				# 生成掩膜图像
mask[200:400, 200: 400] = 255						# 直接设置掩码区域

# 生成掩码部分的灰度图
# gray和gray做与运算结果还是gray, 结果再和mask做与运算,黑色部分置0,白色部分不变
gray_mask=cv2.bitwise_and(gray, gray, mask=mask)    

# 对是否使用掩膜进行分别统计
hist_mask = cv2.calcHist([gray], [0], mask, [256], [0, 255])
hist_gray = cv2.calcHist([gray], [0], None, [256], [0, 255])


plt.figure(figsize=[10,5])
plt.subplot(121); plt.imshow(gray_mask,cmap='gray'); plt.title("gray_mask");
plt.subplot(122); plt.plot(hist_mask, label='mask');plt.plot(hist_gray, label='gray');plt.legend();

在这里插入图片描述

1.3 直方图均衡化

  有的时候拍出的图片整体偏亮或偏暗,或者亮度很不均匀。直方图均衡化可以改善图像的对比度。它通过重新分配图像像素的灰度值,使得图像中灰度值的分布更加均匀,从而增强细节,使图像看起来更清晰(使灰度值扩展到整个范围,从而增加图像的全局对比度)。

直方图均衡化的实现原理:

  1. 计算图像的直方图: 首先统计出原始图像中每个灰度级(0-255)所出现的频率,即构建图像的直方图。
    在这里插入图片描述

  2. 计算累积直方图

在这里插入图片描述
|

  1. 映射原始像素值:将累计直方图结果直接乘以255就是最终均衡直方图的结果

在这里插入图片描述
  在OpenCV中,可以使用cv2.equalizeHist()函数来实现直方图均衡化。该函数只适用于灰度图像。下面我们对一张图进行整体增亮和增暗处理,然后进行直方图均衡化看看效果。

img=cv2.imread('lena.png')
matrix = np.ones(img.shape, dtype = "uint8") * 50

img_brighter = cv2.add(img, matrix) 
img_darker   = cv2.subtract(img, matrix)

# Show the images
plt.figure(figsize=[18,5])
plt.subplot(131); plt.imshow(img_darker[:,:,::-1]);  plt.title("Darker");
plt.subplot(132); plt.imshow(img[:,:,::-1]);         plt.title("Original");
plt.subplot(133); plt.imshow(img_brighter[:,:,::-1]);plt.title("Brighter");

在这里插入图片描述
  彩色图像有多个通道(如 RGB 或 HSV 颜色空间),直接对每个通道进行直方图均衡化可能会导致颜色失真。因此,通常不会对 RGB 三个通道直接进行均衡化。比较常见的方法是将图像转换到亮度通道可分离的颜色空间(如 YUV 或 HSV),然后只对亮度通道进行直方图均衡化,再将处理后的图像转换回原来的颜色空间。

# 将图像从 BGR 转换到 YUV 颜色空间
yuv_darker=cv2.cvtColor(img_darker, cv2.COLOR_BGR2YUV)
yuv_brighter=cv2.cvtColor(img_brighter, cv2.COLOR_BGR2YUV)

# 对 Y 通道(亮度通道)进行直方图均衡化
yuv_darker[:,:,0] = cv2.equalizeHist(yuv_darker[:,:,0])
yuv_brighter[:,:,0] = cv2.equalizeHist(yuv_brighter[:,:,0])

# 将图像从 YUV 转换回 BGR 颜色空间
darker_equ=cv2.cvtColor(yuv_darker, cv2.COLOR_YUV2BGR)
brighter_equ=cv2.cvtColor(yuv_brighter, cv2.COLOR_YUV2BGR)

# # 显示原图和均衡化后的图像
plt.figure(figsize=[18,5])
plt.subplot(131); plt.imshow(darker_equ[:,:,::-1]);  plt.title("darker_equ");
plt.subplot(132); plt.imshow(img[:,:,::-1]);         plt.title("Original");
plt.subplot(133); plt.imshow(brighter_equ[:,:,::-1]);plt.title("brighter_equ");

在这里插入图片描述

1.4 自适应直方图均衡化 (CLAHE)

1.4.1 实现原理

直方图均衡化的局限性:

  • 噪声增强:对于含有大量噪声的图像,均衡化可能会使噪声也得到增强,导致图像质量下降。
  • 细节丢失:直方图均衡化是一种全局处理方法,无法处理局部区域对比度问题。如果图像中存在不同亮度的区域,全局均衡化可能会使局部细节丢失。

  针对上述问题,OpenCV提供了自适应直方图均衡化(CLAHE, Contrast Limited Adaptive Histogram Equalization),它通过对图像的局部区域(称为“子图块”)分别进行直方图均衡化,从而增强局部对比度,同时避免过度增强噪声。

  1. 将图像分割成多个子图块: CLAHE将图像划分为多个较小的矩形区域(称为“子图块”或“窗口”,通常是8x8或16x16的网格)。每个子图块会单独进行直方图均衡化,这样可以增强每个局部区域的对比度。

  2. 对每个子图块进行直方图均衡化: 在每个子图块上执行和普通直方图均衡化类似的操作,计算该子图块的直方图,然后根据该直方图的累积分布函数 (CDF) 来重新分配像素值。

  3. 应用对比度限制: 在局部直方图均衡化时,某些子图块中的像素可能集中在特定的灰度范围内,导致对比度过度增强,尤其是在图像包含噪声时。因此,CLAHE引入了一个对比度限制参数clipLimit,用于限制每个灰度级的像素频率。当某个灰度级的频率超过 clipLimit 时,多余的部分会均匀分配到其他灰度级。

    • clipLimit:表示限制直方图中某个灰度级出现的最大频率,防止噪声被过度放大。
  4. 插值平滑: 对于每个像素,由于它位于多个子图块的边界上,CLAHE对这些子图块的均衡化结果进行插值平滑,避免由于直接均衡化子图块而产生块状效应(blocky effect)。通过插值,这些子图块的边界变得平滑,使得过渡更加自然。

CLAHE的效果:

  • 局部对比度增强:相比全局直方图均衡化,CLAHE能够有效增强图像中不同区域的对比度,因此在处理具有复杂光照或局部对比度差异大的图像时效果更好。
  • 防止过度增强噪声:由于引入了对比度限制参数,CLAHE可以防止对比度过度增强,从而避免了噪声的放大。
  • 适合自然图像:CLAHE常用于医学图像、卫星图像和低光照图像的处理,这些图像通常需要增强局部区域的对比度,而不希望整体图像变得太过刺眼。
属性 普通直方图均衡化 CLAHE(自适应直方图均衡化)
处理范围 全局 局部,分块处理
效果 提高全局对比度,可能导致局部细节丢失 提高局部对比度,增强细节
噪声处理 可能过度增强噪声 使用clipLimit限制对比度增强,避免噪声过度增强
适用场景 适用于灰度值集中分布的图像,全局对比度不高 适用于包含复杂光照或局部对比度差异大的图像(如医学、卫星图像)
常见问题 对局部细节处理不佳,可能丢失对比度 使用不当时,可能引入分块效应,不过插值技术可以有效减缓
1.4.2 代码实现

   OpenCV 中使用cv2.createCLAHE 函数进行自适应直方图均衡化,它生成一个 CLAHE 对象,可以通过该对象对图像应用自适应直方图均衡化。

createCLAHE([, clipLimit[, tileGridSize]]) -> retval
  1. clipLimit:对比度限制阈值,浮点型,默认为2.0clipLimit 限制了每个灰度级像素频率的最大值,超过 clipLimit 的频率会被平摊到其他灰度级,从而避免过度增强局部噪声。

    • 如果 clipLimit 值较低,对比度增强较弱。
    • 如果 clipLimit 值较高,则会增强对比度。
  2. tileGridSize:整型元组,表示子图块的大小。默认为(8, 8),即将图像分为 8×8 个子图块。对每个子图块单独进行直方图均衡化,然后在子图块之间进行插值以避免边界出现突变现象。

    • 值越大:处理的大块区域更多,图像整体的对比度调整幅度更大,但局部细节增强不明显。
    • 值越小:处理的小块区域更多,图像局部对比度更强,但可能会引入噪声和块效应。
# 将图像从 BGR 转换到 YUV 颜色空间
yuv_darker=cv2.cvtColor(img_darker, cv2.COLOR_BGR2YUV)
yuv_brighter=cv2.cvtColor(img_brighter, cv2.COLOR_BGR2YUV)

# 创建CLAHE对象,设定clipLimit和tileGridSize
clahe = cv2.createCLAHE(clipLimit=1.0, tileGridSize=(4, 4))
# 对 Y 通道(亮度通道)进行直方图均衡化
yuv_darker[:,:,0] = clahe.apply(yuv_darker[:,:,0])
yuv_brighter[:,:,0] = clahe.apply(yuv_brighter[:,:,0])

# 将图像从 YUV 转换回 BGR 颜色空间
darker_equ=cv2.cvtColor(yuv_darker, cv2.COLOR_YUV2BGR)
brighter_equ=cv2.cvtColor(yuv_brighter, cv2
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

神洛华

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值