python的opencv操作记录(13)-增强之直方图均衡化


前段时间忙活深度网络和android的东西去了,好久没讲讲传统图像处理了,这一篇继续来说说opencv中的传统图像处理部分——图像增强之直方图增强。

图像增强是一种基本的图像处理操作,简单的来说就是把图像变的更清晰,或者说感兴趣的某个区域需要变的更加清晰。

而清晰度这个概念,在清晰度计算这一章节中提到过,一般来说,像素之间的梯度越大,图像就越清晰。

而直方图是用于统计像素分布的一个工具,计算每幅图像的直方图是传统图像处理中的一种基本操作,直方图表现了一张图像所有的像素的分布情况,直方图增强就是通过调整直方图的分布来实现图像的增强,简单的说就是把图像像素重新分布一下,提高图像中像素的整体梯度,让图像变得更清晰。

这个过程被称作直方图均衡化

附上之前直方图的基本计算方式:

https://blog.csdn.net/pcgamer/article/details/124989015?spm=1001.2014.3001.5501

清晰度计算:

https://blog.csdn.net/pcgamer/article/details/127942102?spm=1001.2014.3001.5501

直方图增强基本逻辑-均衡化

上面提到了均衡化的过程,其实就是把图像像素的分布改变一下。那么问题来了,怎么变?根据什么变?

首先来看一下opencv中的函数*equalizeHist()*的方式。

  • 首先说下累计分布函数CDF(cumulative distribution function),这个函数可以这么理解:

    • 直方图就是统计了某个灰度值的像素个数,比如灰度为100的像素个数有50个,总共有256中灰度值,那么可以记做:
      n 100 = 50 n_{100} = 50 n100=50
      ,或者是$ n_i = 100, i = 100, 0<i<255$

    • 归一化到[0, 1]的范围内,其实就是求这个灰度值出现的概率: p ( i ) = n i n p(i) = \frac{n_i}{n} p(i)=nni,n为像素总数。

    • 那么累计分布函数就是:
      H ( x ) = ∑ j = 1 i p x ( j ) H(x) = \sum_{j=1}^ip_x(j) H(x)=j=1ipx(j)
      也就是某个灰度值所有的累计分布,比如灰度值100的H(x)就是从0-100的所有灰度值概率分布之和。

    • 通过把每个点的像素值通过 H ( x ) H(x) H(x)来进行转换获得新的目标图像的灰度值。

  • 有意思的是为什么要这么进行转换,这个证明过程不复杂,可以简单列一列。

    • 首先可以认为原始图S,和目标图D。

    • 原始图S的直方图分布记做 H A ( S ) H_A(S) HA(S),目标图D的直方图分布记做 H B ( D ) H_B(D) HB(D)

    • 我们的目的就是要找到一个映射关系 f f f,可以把原始图S中的像素值映射到目标图D,也就是说 D = f ( S ) D = f(S) D=f(S)

    • 直方图归一化到[0, 1]后,实际上就是某个灰度值的概率,所有的概率之和都是等于。

    • 对于原图的直方图表示: ∑ 0 S H ( S ) \sum_0^SH(S) 0SH(S)就表示所有的概率之和。那么目标图的表示就是 ∑ 0 D H ( D ) \sum_0^DH(D) 0DH(D), 两者是相等的。可以表示为:
      ∑ 0 S H ( S ) = ∑ 0 D H ( D ) \sum_0^SH(S) = \sum_0^DH(D) 0SH(S)=0DH(D)

    • 最理想的目标图分布是均匀分布,也就是 H ( D ) = A N H(D) = \frac{A}{N} H(D)=NA, 其中的A表示每种像素值的值(每种都是相同的)。那么上面的公式就可以写成:
      ∑ 0 S H ( S ) = ∑ 0 D H ( D ) = D A N \sum_0^SH(S) = \sum_0^DH(D) = \frac{DA}{N} 0SH(S)=0DH(D)=NDA
      其中 D = f ( S ) D=f(S) D=f(S)
      所有有可以写成:
      ∑ 0 S H ( S ) = f ( S ) A N \sum_0^SH(S) = \frac{f(S)A}{N} 0SH(S)=Nf(S)A

      换一下项就可以得到:
      f ( S ) = N A ∑ 0 S H ( S ) f(S) = \frac{N}{A}\sum_0^SH(S) f(S)=AN0SH(S)

      最右边的那一坨中的H(S)就是上面提到的累积概率分布,只是这里要对整张图像的像素再做一次求和或者积分。

    • 具体怎么计算,这里就不说了,有兴趣的朋友可以去了解下。

    • 当然,这里有个问题,上面的理想状态是不太可能达到的,如果某些图像的直方图在某个小区域出现比较大的聚集的话,可能就没法非常好的进行平均分布了。

calcHist && equalizeHist

在opencv中,用于直方图均衡化的函数就是equalizeHist:

先上代码:

  img = cv2.imread("xxxx")

  img = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)

  hist = cv2.calcHist([img], [0], None, [256], [0, 255])

  plt.title('Gray Histogram Contour')
  plt.xlabel('gray level')
  plt.ylabel('number of pixels')

  plt.figure(1)
  plt.plot(hist)

  dst = cv2.equalizeHist(img)

  hist_new = cv2.calcHist([dst], [0], None, [256], [0, 255])

  plt.title('Gray Histogram Contour new')
  plt.xlabel('gray level')
  plt.ylabel('number of pixels')

  plt.figure(2)
  plt.plot(hist_new)

  plt.show()

  cv2.imshow("src", img)
  cv2.imshow("new", dst)

  cv2.waitKey()
  cv2.destroyAllWindows()

calcHist

在调用均衡化之前,先要计算图像的直方图,用于后续进行对比实验。
计算图像直方图的函数:调用opencv中的calcHist函数:

  • calcHist函数接受下面几个参数
  • [img],以列表的形式作为参数,img为需要计算直方图的图像。
  • [0],通道数,如果是灰度图,就传0
  • None,mask,用于ROI的掩码,传None就是统计整张图像。
  • [256],histSize,就是说分成多少类,如果全部统计的话就是256个类。
  • [0, 255],就是说哪些像素值需要被统计,[0, 255]就表示所有的像素值都需要被统计(8位)

然后再通过plt库进行绘图展示。

equalizeHist

直方图均衡化,这个函数就相对比较简单了,直接从源图到目标图进行转换。

我们看一下上面代码的结果,用来处理经典的一张图:

  • 源图:
    在这里插入图片描述

  • 源图直方图
    在这里插入图片描述

  • 目标图
    在这里插入图片描述

  • 目标图直方图
    在这里插入图片描述

从直方图分布可以看出,均衡化已经将像素点从相对集中变成了相对平衡的分布了。而这个累积分布概率函数的转换就是表明希望通过这样一个转换,使得像素尽量去满足一个像素值平均分布。
从最终形成的图像上来看,也是把一幅雾蒙蒙的图像变得相对清晰了。
但是存在一个这个方法典型的确定,马赛克现象比较严重。

自适应直方图均衡化

上面提到的均衡化方法有两个比较明显的不足:

  1. 马赛克现象,我理解是因为灰度是一个离散的点,会造成某个小区域发生阶跃性的变化,造成这种现象。
  2. 噪声被放大的现象,因为在整张图像上把像素值拉平的原因。

进一步的一个算法就是自适应直方图均衡化。
简单来说就是在上面的算法上做了两点改动:

  1. 利用局部特征,或者说局部的ROI特征进行CDF变换。也就是某个像素点周边的一个W*W的区域。这样就可以让灰度的阶跃变小。因为在一个小的区域里变换,噪声被放大的影响也不会太大。
  2. 上一步的改进中,会造成区域与区域之间被认为造成一些“边界”,所以这些边界需要通过双线性插值来进行“模糊”,让图像过渡比较连续。(双线性插值可以参考https://blog.csdn.net/pcgamer/article/details/125426351?spm=1001.2014.3001.5502)
  3. 为了防止局部对比度过于夸张,增加了一个限制对比度的参数,如果超过这个阈值,则会通过某种规则把这些灰度值分摊到区域中的其他像素值上去,让整个局部直方图更加的平缓。

代码也挺简单:

    clahe = cv2.createCLAHE(clipLimit=2.0, tileGridSize=(8, 8))
    cl1 = clahe.apply(img)

    hist_cl1 = cv2.calcHist([cl1], [0], None, [256], [0, 255])

    plt.title('Gray Histogram Contour hist_cl1')
    plt.xlabel('gray level')
    plt.ylabel('number of pixels')

    plt.figure(3)
    plt.plot(hist_cl1)

    plt.show()

    cv2.imshow("hist_cl1", cl1)

其中的方法cv2.createCLAHE(clipLimit=2.0, tileGridSize=(8, 8))是创建了一个CLANE类,其中的两个参数

  • clipLimit = 2.0,对比度限制这个参数是用每块的直方图的每个bins的数和整图的平均灰度分布数的比值来限制的。 裁剪则是将每块图像直方图中超过ClipLimit的bins多出的灰度像素数去除超出部分,然后将所有bins超出的像素数累加后平均分配到所有bins。具体怎么分配的就不是特别清楚了。
  • tileGridSize就是每个小区域的大小
  • 再通过apply应用到图像上去
  • CLANE是opencv算法库中的一种。
    在这里插入图片描述

在这里插入图片描述

相比之前算法的图,有两点改进:

  1. 对比度与源图更类似,而不是整体上改变了源图的对比度,相对于普通的均衡化方法,分布的不是那么的均匀,但是更好的代表了源图的某些特征。
  2. 马赛克现象得到了缓解。

当然,不是在所有的图像上,自适应方法都可以比普通的均衡化方法更好的,需要根据图像的特征来进行判断。``

  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

新兴AI民工

码字不易,各位看客随意

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

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

打赏作者

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

抵扣说明:

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

余额充值