【OpenCV 学习笔记】第十三章: 直方图处理

第十三章: 直方图处理

直方图是图像处理中一种非常重要的分析工具。直方图主要是根据灰度图像进行统计和绘制的,所以反映的是灰度图像的统计特性。
从直方图的角度对图像进行描述,可以获得很多图像的重要信息。从直方图的角度对图像进行处理,可以达到增强图像显示效果的目的。

一、什么是图像直方图(image histogram)?

先将彩色图像转化为灰度图像,然后把灰度图像上的所有像素点安装像素值的大小和像素值出现的次数,绘制在坐标系中的图,就是直方图。
横坐标X轴的范围是0-255,表示有255个灰度级,x轴左侧的值较小,对应着图像中较暗的区域,x轴右侧的值较大,对应着图像中较亮的区域。有时我们也可以把x轴分箱,分成多个bins。
纵坐标y轴可以是次数也可以是概率值,如果是概率值,y轴的范围就在0-1,如果是次数,就表示灰度值出现的次数。

上图中左侧的图像较暗的区域多,右侧图像较亮的区域多,所以对应的直方图,一个像素点都集中在直方图的左侧,一个都集中在右侧。

直方图的意义:
1、直方图是图像中像素强度分布的图形表达方式。
2、它统计了每一个强度值所具有的像素个数。
3、不同的图形的直方图可能是相同的。

二、绘制图像直方图

  • 1、使用matplotlib.pyplot.hist(x, bins)函数绘制直方图
      x:数据,必须是一维的。图像数据通常是二维的,所以要用ravel()函数将其拉成一维的数据后再作为参数使用。
      bins:表示x轴分多少组。

  • 2、使用hist = cv2.calcHist(img, channels, mask, histSize, ranges, accumulate)函数统计直方图,然后用plt.plot()函数将其绘制出来
      这个函数里面的参数都要加[]括起来,这是底层代码写死的,不然无法识别。
      img:原图像
      channels:如果输入的图像是灰度图,它的值就是[0],如果是彩色图像,传入的参数可以是[0]、[1]、[2],分布对应着b,g,r。
      mask:掩膜图像。要统计整幅图像的直方图就把这个参数设为None,如果要统计掩膜部分的图像的直方图,就需要这个参数。
      histSize:bins的个数,就是分多少个组距。例如[256]或者[16],都要用方括号括起来。
      ranges:像素值范围,通常为[0, 256]
      accumulate:累计(累积、叠加)标识,默认值是False。
      这个函数返回的对象hist是一个一维数组,数组内的元素是各个灰度级的像素个数。

    #例13.1 用hist()和cv2.calcHist()函数绘制直方图  
    import cv2
    import numpy as np
    import matplotlib.pyplot as plt
    
    img = cv2.imread(r'C:\Users\25584\Desktop\girl.bmp')   #img.shape返回(576, 720, 3)
    img_gray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
    
    #---------使用hist()函数绘图---注意:用这个函数画图像直方图时一定要用灰度图像,如果非用彩图,那得按通道画,不然没有什么意义----------------
    plt.figure(figsize=(10,3))
    plt.subplot(131), plt.imshow(img[:,:,::-1])   #原图
    plt.subplot(132), plt.hist(img_gray.ravel(), 256)  #将灰度级划分为256个等级
    plt.subplot(133), plt.hist(img_gray.ravel(), 16, color='red')   #将灰度级划分为16个等级
    
    #---------使用cv2.calcHist()函数绘图----这个函数可以传入彩图,因为它还有一个channel参数,就把通道分开了---------------------
    hist_b = cv2.calcHist([img], [0], None, [256], [0, 256])  
    hist_g = cv2.calcHist([img], [1], None, [256], [0, 256])  
    hist_r = cv2.calcHist([img], [2], None, [256], [0, 256])  
    hist_gray1 = cv2.calcHist([img_gray], [0], None, [256], [0, 256])  
    hist_gray2 = cv2.calcHist([img_gray], [0], None, [16], [0, 256])  
    
    plt.figure(figsize=(10,3))
    plt.subplot(131), plt.plot(hist_b, color='b'), plt.plot(hist_g, color='g'), plt.plot(hist_r, color='r')
    plt.subplot(132), plt.plot(hist_gray1, color='gray') 
    plt.subplot(133), plt.plot(hist_gray2, color='gray') 
    plt.show()

  • 3、绘制掩膜部分的图像的直方图
    掩膜在遥感影像处理中使用较多,当提取道路或者河流或者房屋时,通过一个掩膜矩阵来对原图进行过滤,然后将我们感兴趣的物体凸显出来。
    #例13.2 使用掩膜绘制直方图  
    import cv2
    import numpy as np
    import matplotlib.pyplot as plt
    
    img = cv2.imread(r'C:\Users\25584\Desktop\girl.bmp')  
    mask = np.zeros(img.shape, np.uint8)
    mask[200:400, 200:400]=255
    img_mask = cv2.bitwise_and(img, mask)
    
    hist_img = cv2.calcHist([img], [0], None, [256], [0,256])
    hist_img_mask = cv2.calcHist([img], [0], mask[:,:,0], [256], [0,256])
    hist_mask = cv2.calcHist([img_mask[200:400, 200:400]], [0], None, [256], [0,256])
    
    #可视化
    plt.figure(figsize=(12,3))
    plt.subplot(151), plt.imshow(img[:,:,::-1])
    plt.subplot(152), plt.imshow(img_mask[:,:,::-1]) 
    plt.subplot(153), plt.plot(hist_img), plt.plot(hist_img_mask) #无掩膜和有掩膜的直方图画到一起
    plt.subplot(154), plt.plot(hist_img_mask)   #单独划出有掩膜的直方图
    plt.subplot(155), plt.plot(hist_mask)     #单独把mask部分图像的直方图画出来,和上面的一模一样
    plt.show()

三、直方图均衡化

  • 1、什么是直方图均衡化?为啥要均衡化?
    如果一幅图像的灰度值都集中在一个小的范围内,那么这幅图看起来就是一个色调,图像里面的画面也没有什么对比度。
    如果一幅图像的灰度值均匀的分布在所有灰度级上,那这幅画看起来就有较高的色彩对比度,也就是画面更加清晰,色彩丰富。
    直方图均衡化的注意目的就是:将原始图像的灰度级均匀的映射到整个灰度级范围内,得到一个灰度级分布均匀的图像。并且这种均衡化,既实现了灰度值统计上的概率均衡,还实现了人类视觉系统(human visual system, HVS)上的视觉均衡。

 直方图均衡化是对图像进行非线性拉伸,重新分配图像像素值,使一定灰度范围内的像素数量大致相同。这种方法可以提高图像整体的对比度,特别是有用数据的像素值分布比较接近时,在X光图像中使用广泛,可以提高骨架结构的显示,另外在曝光过度或者曝光不足的图像中可以更好的突出细节。另外通过均衡化处理后,图像特征也更突出,便于后面的处理,所以广泛用于图像处理、车票识别、人脸识别等领域。

  • 2、直方图均衡化的原理
    直方图均衡化算法主要包括2个步骤:
    (1)计算累计直方图
    (2)对累计直方图进行区间转换
    说明:因为人眼对像素值越大的点看得越清晰,所以用累计概率。当像素值越大,累计概率就越大,此时就越有更多的像素点被映射到像素值更大的区间。

图像均衡化的具体步骤:
(1)读取一幅图像。一般情况下我们图像均衡化处理的都是灰度图,如果非要处理彩图,就必须按通道分别处理。
(2)将图像转化为灰度图像。
(3)统计灰度级和各个灰度级上的像素个数。灰度级就是灰度图像中各个像素点的像素值。就是统计所有像素值以及这些像素值对应的像素个数。
(4)计算归一化统计直方图。就是将各个灰度级下的像素个数/总像素个数,变成概率的形式。
(5)计算累计概率。就是将各个灰度级下的概率变成累计概率。
(6)对累计概率进行区间转换。就是转换各个累计概率对应的灰度级。这里有2种情况:
情况1:在原来的灰度范围内转换:用原来的最大灰度值x累计概率,得到新的灰度级,统计新的灰度级下对应的像素个数,即可得到均衡化的直方图。也就是原来的像素点都映射到新的灰度级上了,图像就实现了均衡化。
情况2:不在原来的灰度范围内转换:用你想要的的灰度范围,比如像要[0,255]256个灰度级的范围内均衡化,就用255x累计概率=新的灰度级。然后统计新的灰度级下对应的像素个数,即可得到均衡化后的直方图,进而得到均衡化后的图像。

一个小案例展示计算过程:左图是原图,右图是均衡化后的图像,下面的表格是计算的过程。

  • 3、API:dst = cv2.equalizeHist(img)
    img:灰度图像
    dst:均衡化后的图像
    #例13.3 实现图像均衡化处理    
    import cv2
    import numpy as np
    import matplotlib.pyplot as plt
    
    img = cv2.imread(r'C:\Users\25584\Desktop\equ.bmp')  
    img_gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
    #-----------------------图像均衡化处理----------------
    equ = cv2.equalizeHist(img_gray)            
    
    plt.figure(figsize=(10,2))
    plt.subplot(141), plt.imshow(img[:,:,::-1])
    plt.subplot(142), plt.imshow(img_gray, cmap='gray') 
    plt.subplot(143), plt.imshow(equ, cmap='gray') 
    plt.subplot(144), plt.imshow(equ, cmap='gray_r'),plt.axis('off') #cmap和axis小知识点
    
    #----------------------直方图对比----------------
    hist_img_gray = cv2.calcHist([img_gray], [0], None, [256], [0,256])  #生成灰度图像的直方图
    hist_equ = cv2.calcHist([equ], [0], None, [256], [0,256])   #生成均衡化后的图像的直方图
    
    plt.figure(figsize=(10,2))
    plt.subplot(141), plt.plot(hist_img_gray)
    plt.subplot(142), plt.plot(hist_equ) 
    plt.subplot(143), plt.hist(img_gray.ravel(), 256) 
    plt.subplot(144), plt.hist(equ.ravel(), 256) 
    plt.show()

    四、自适应的直方图均衡化

    上面讲的直方图均衡化是从整个图像的所有像素点进行均衡处理的。在很多情况下,这样做其实效果并不好,因为对比度改变了实际上我们会丢失很多信息,比如一些细小的边缘信息,实际上是会丢失的。为了解决这个问题,我们要进行自适应的直方图均衡化。

  • 具体操作区别有:
    (1)将整幅图像分成很多小块,然后对每个小块分别进行直方图均衡化,这样均衡化操作就会集中在一个个小的区域内。
    (2)图像被划分成很多小块,很容易出现小块与小块之间的边界,为避免这种情况,使用双线性插值,对每一个小块进行拼接。
    (3)如果图像有噪声的话,噪声就会被放大,为了避免这种情况,要使用对比度限制。就是对每个小块来说,如果直方图中的某些像素值的频率超过设定的上限的话,就把这些像素点平均分配到其他bins里,然后再进行直方图均衡化。

  • API:clahe = cv2.createCLAHE(clipLimit, tileGridSize) ,  clahe.apply(img)
    clipLimit:对比度限制,默认是40
    tileGridSize:分块的大小,默认是8x8

    #例13.4 实现自适应图像均衡化处理    
    import cv2
    import numpy as np
    import matplotlib.pyplot as plt
    
    img = cv2.imread(r'C:\Users\25584\Desktop\equ.bmp',0)  
    
    equ = cv2.equalizeHist(img)    #普通的均衡化处理     
    
    clahe = cv2.createCLAHE(clipLimit=2.0, tileGridSize=(8,8))  #自适应均衡化处理
    cl = clahe.apply(img)
    
    img_ = np.hstack((img, equ, cl))   #三张图片合到一起
    plt.imshow(img_, cmap='gray') 
    plt.show()

猜你喜欢

转载自blog.csdn.net/friday1203/article/details/125078116