Fork me on GitHub

OpenCV计算机视觉学习(9)——图像直方图 & 直方图均衡化

如果需要处理的原图及代码,请移步小编的GitHub地址

  传送门:请点击我

  如果点击有误:https://github.com/LeBron-Jian/ComputerVisionPractice

1,如何提高图像像素

  对曝光过度或者逆光拍摄的图片可以通过直方图均衡化的方法用来增强局部或者整体的对比度。

  对于相机采集的原始图像经常会出现一种现象,即图像所有像素的灰度值分布不均匀,而是集中在某一特定的小区域,导致图像中的所有信息的灰度值都很接近,即对比度差,很难从图像中分辨出某一特征的信息。而质量较高的图像中,像素的强度应该均衡的分布。

  为了提高图像处理的效果,经常会在图像处理之前进行直方图均衡化,即将图像的直方图灰度级别由集中在某一小部分灰度级分散成在所有灰度级别都有一定的覆盖,所以通过直方图均衡化的方法用来增强局部或整体的对比度。

  具体的思路就是通过找出图像中最亮和最暗的像素值将之映射到纯黑和纯白之后再将其他的像素值按照某种算法映射到纯黑和纯白之间的值。另外一种方法就是寻找图像中像素的平均值作为中间灰度值,然后扩展范围以达到尽量充满可显示的值。

  一个好的图像会有来自图像的所有区域的像素。所以你需要把这个直方图拉伸到两端(如上图所给出的),这就是直方图均衡的作用(用简单的话说)。这通常会改善图像的对比度。

2,直方图均衡化的原理

2.1  直方图的介绍

  具体直方图实现的原理是什么呢?请看下图:

   左图是一个图像的像素组合,我们拿到的是一个12*20 大小的图像像素;右图就是他的直方图展示,横轴表示在0~255之间的区间块,我们将其分为16个bin,统计图像中每个像素的个数,右图反映的时图像中每个像素出现的频率,横轴是像素区间,纵坐标是像素出现的频率。

  看到上面两个图,大概直方图的解释应该很明显了。

2.2  用实验数据展示什么是直方图?

  我们可以把直方图看做一个图,它给我们一个关于图像的强度分布的总体思路。它是一个带有像素值的图(从0到255, 不总是)在X轴上,在y轴上的图像对应的像素个数。

  这只是理解图像的另一种方式,通过观察图像的直方图,我们可以直观的了解图像的对比度,亮度,亮度分布等。(下图来至于Cambridge in Color website的图片,建议去访问这个网站,了解更多细节。)

  你可以看到图像和它的直方图。(这个直方图是用灰度图像绘制的,而不是彩色图像)。在直方图中,横坐标表示图像中各个像素点的灰度级,纵坐标表示具有该灰度级的像素个数。直方图的左边部分显示了图像中较暗像素的数量,右边区域显示了更明亮的像素。从直方图中可以看到,深色区域的像素数量比亮色区域更多,而中间色调的数量(中值大约在127左右)则少得多。

2.3 直方图均衡化的原理

  有时图像的视觉上的缺陷并不在强度值集中在很窄的范围内。而是某些强度值的使用频率很大。在完美均衡的直方图中,每个柱的值都应该相等。即50%的像素值应该小于128,25%的像素值应该小于64.总结出的经验可定义为:在标准的直方图中 p% 的像素拥有的强度值一定小于或等于 255*p%,将该规律用于均衡直方图中:强度 i 的灰度值应该在对应的像素强度低于 i 的百分比的强度中。因此,所需要的查询表可由下面的式子建立:

lut[i] = int(255.0 *p[i]) #p[i]是是强度值小于或等于i的像素的数目。

  p[i] 即直方图累计值,这是包含小于给点强度值的像素的直方图,以代替包含指定强度值像素的数目。比如第一幅图像的累计直方图如下图中的蓝线:

   而完美均衡的直方图,其累积直方图应为一条斜线,如上图中均衡化之后的红线。

  更专业一点,这种累计直方图应该称为累计分布(cumulative  distribition)。在Numpy中有一个专门的函数来计算。这个在后面说。

  所以直方图均衡化就是对图像使用一种特殊的查询表。通常来说,直方图均衡化大大增加了图像的表象。但是根据图像可视内容的不同,不同图像的直方图均衡化产生的效果不尽相同。下面我们具体学习一下。

  比如下图小狗,我们画出原图,并展示出其像素直方图分布范围:

   我们对直方图进行均衡化,均衡化的图,如下:

   最终我们得到了小狗直方图均衡化后的图像。那其计算原理如下图:

   简单解释一下,上面两张图是我们取了图片中一点像素,是我们直方图均衡化前后的两张表的对比。那么如何进行直方图均衡化的计算,也就是将左图的像素点转换为右图呢,我们就需要下图的计算过程了。图很明显,我就不再赘述了。

  下面首先对直方图的计算进行学习,然后学习直方图均衡化。

3,直方图的绘制

3.1  使用OpenCV统计绘制直方图

  OpenCV提供了cv.calcHist()函数来获取直方图,与C++中一样,都是cv.calcHist()。让我们熟悉一下这个函数及其参数:

def calcHist(images, channels, mask, histSize, ranges, hist=None, accumulate=None): 
    # real signature unknown; restored from __doc__
    """
    calcHist(images, channels, mask, histSize, ranges[, hist[, accumulate]]) -> hist
    .   @overload
    """
    pass
  • images:它是uint8类型或float32的源图像。当传入函数时,它要用方括号括起来,也就是”[img]”
  • channels:它也用方括号括起来。它是我们计算直方图的信道的索引。例如,如果输入是灰度图像,它的值是0。对于颜色图像,您可以通过0、1或2来分别计算蓝色、绿色或红色通道的直方图,即BGR通道
  • mask:遮罩图。为了找到完整图像的直方图,它被指定为“None”。但如果你想找到图像的特定区域的直方图,你必须为它创建一个遮罩图,并将其作为遮罩。
  • histSize:这代表了我们的BINS数。需要用方括号来表示。在整个范围内,我们通过了256。
  • ranges:强度值范围,通常是 [ 0,256 ]

  让我们从一个样本图像开始,只需要在灰度模式下加载图像并找出其完整的直方图:

#_*_coding:utf-8_*_
import cv2  # opencv读取的格式是BGR
import numpy as np
import matplotlib.pyplot as plt  # Matplotlib读取的格式是RGB

img = cv2.imread('cat.jpg', 0)   #0 表示灰度图
hist = cv2.calcHist([img], [0], None, [256], [0, 256])
print(hist.shape)  #  (256, 1)

  hist是一个256x1阵列,每个值对应于该图像中的像素值机器对应的像素值。

3.2  使用Numpy计算直方图

  Numpy中提供了np.histogram()方法,用于对一位数组进行直方图统计,其参数列表入下:

Histogram(a,bins=10,range=None,normed=False,weights=None)
  • a:是保存待统计的数组
  • bins:指定统计的区间个数,即对统计范围的等分数
  • range:是一个长度为2的元组,表示统计范围的最大值和最小值,默认值为None,表示范围由数据的范围决定,即(a.min(), a.max))。
  • normed:当normed参数为False时,函数返回数组a中的数据在每个区间的个数,否则对个数进行正规化处理,使它等于每个区间的概率密度。
  • weights:weights参数和 bincount()的类似

返回值(有两个)

  • hist : hist和之前计算的一样,每个区间的统计结果。
  • bins : 数组,存储每个统计区间的起点。range为[0,256]时,bins有257个元素,因为Numpy计算bins是以0-0.99,1-1.99等,所以最后一个是255-255.99。为了表示这一点,他们还在bins的末端添加了256。但我们不需要256。到255就足够了。

  让我们从一个样本图像开始。只需在灰度模式下加载图像并找到其完整的直方图

hist, bins = np.histogram(img.ravel(), 255, [0,256])

  Numpy还有另一个函数,np.bincount(),比np.histograme()要快得多(大约10X)。对于一维直方图,你可以试一下。不要忘记在np.bincount中设置minlength=256。例如,hist=np.bincount(img.ravel(),minlength=256)

  OpenCV函数比np.histogram()快(大约40X)。所以考虑效率的时候坚持用OpenCV函数。

3.3   使用matplotlib绘制直方图

  Matplotlib中有一个绘制直方图的函数:

matplotlib.pyplot.hist()

  参数:数据源必须是一维数组,通常要通过函数 ravel() 拉直图像,像素一般是256,表示[0, 256]。

  函数ravel() 将多维数组降为一维,格式为:一维数组 =  多维数组.ravel()

  hist()直接找到直方图绘制。您不需要使用calcHist()或np.histogram()函数来找到直方图。看下面的代码:

import numpy as np
import cv2 as cv
from matplotlib import pyplot as plt

img = cv.imread('cat.jpg', 0)
plt.hist(img.ravel(), 256, [0,256])
plt.show()

  效果如下:

  或者我们可以用常规的matplotlib的plot函数绘制直方图,适合绘制BGR图像直方图。为此,我们需要首先找到直方图的数据,代码如下:

import numpy as np
import cv2 as cv
from matplotlib import pyplot as plt

img = cv.imread('cat.jpg')
color = ('b', 'g', 'r')
for i, col in enumerate(color):
    histr = cv.calcHist([img], [i], None, [256], [0,256])
    plt.plot(histr, color=col)
    plt.xlim([0,256])
plt.show()

  效果如下:

4   彩色图像不同通道的直方图

  下面来看下彩色图像的直方图处理,首先读取并分离各通道,接着计算每个通道的直方图,这里将其封装成函数,接着调用,代码如下:

import cv2
import numpy as np


def calcAndDrawHist(image, color):
    hist = cv2.calcHist([image], [0], None, [256], [0.0, 255.0])
    minVal, maxVal, minLoc, maxLoc = cv2.minMaxLoc(hist)
    histImg = np.zeros([256, 256, 3], np.uint8)
    hpt = int(0.9*256)

    for h in range(256):
        intensity = int(hist[h] * hpt / maxVal)
        cv2.line(histImg, (h, 256), (h, 256-intensity), color)

    return histImg

def show_histphoto(photo_path):
    image = cv2.imread(photo_path)
    b, g, r = cv2.split(image)

    histImgB = calcAndDrawHist(b, [255, 0, 0])
    histImgG = calcAndDrawHist(b, [0, 255, 0])
    histImgR = calcAndDrawHist(b, [0, 0, 255])

    cv2.imshow('histImgB', histImgB)
    cv2.imshow('histImgG', histImgG)
    cv2.imshow('histImgR', histImgR)
    cv2.imshow('Img', image)
    cv2.waitKey(0)
    cv2.destroyAllWindows()


if __name__ == '__main__':
    photo_path = 'cat.jpg'
    show_histphoto(photo_path)

  三个通道的直方图如下:

  图如下:

4.1  更进一步

  这样做有些繁琐,参考(https://plus.google.com/118298613334549762938)的做法,无需分离通道,用折现来描绘直方图的边界可在一副图中同时绘制三个通道的直方图。方法如下:

def Line_chart(photo_path):
    image = cv2.imread(photo_path)
    # 创建用于绘制直方图的全0 图像
    h = np.zeros((256, 256, 3))
    # 直方图中各bin的顶点位置
    bins = np.arange(256).reshape(256, 1)
    # BGR 三种颜色
    color = [(255, 0, 0), (0, 255, 0), (0, 0, 255)]
    for ch, col in enumerate(color):
        originHist = cv2.calcHist([image], [ch], None, [256], [0, 256])
        cv2.normalize(originHist, originHist, 0, 255*0.9, cv2.NORM_MINMAX)
        hist = np.int32(np.around(originHist))
        pts = np.column_stack((bins, hist))
        cv2.polylines(h, [pts], False, col)

    h = np.flipud(h)
    cv2.imshow('colorhist', h)
    cv2.waitKey(0)

if __name__ == '__main__':
    photo_path = 'cat.jpg'
    # show_histphoto(photo_path)
    Line_chart(photo_path)

  结果如下:

代码说明:

  这里的for循环是对三个通道遍历一次,每次绘制相应通道的直方图的折线。for循环的第一行是计算对应通道的直方图,经过上面的介绍,应该很容易就能明白。

  这里所不同的是没有手动的计算直方图的最大值再乘以一个系数,而是直接调用了OpenCV的归一化函数。该函数将直方图的范围限定在0-255×0.9之间,与之前的一样。下面的hist= np.int32(np.around(originHist))先将生成的原始直方图中的每个元素四舍六入五凑偶取整(cv2.calcHist函数得到的是float32类型的数组),接着将整数部分转成np.int32类型。即61.123先转成61.0,再转成61。注意,这里必须使用np.int32(...)进行转换,numpy的转换函数可以对数组中的每个元素都进行转换,而Python的int(...)只能转换一个元素,如果使用int(...),将导致only length-1 arrays can be converted to Python scalars错误。

  下面的pts = np.column_stack((bins,hist))是将直方图中每个bin的值转成相应的坐标。比如hist[0] =3,...,hist[126] = 178,...,hist[255] = 5;而bins的值为[[0],[1],[2]...,[255]]。使用np.column_stack将其组合成[0, 3]、[126, 178]、[255, 5]这样的坐标作为元素组成的数组。

  最后使用cv2.polylines函数根据这些点绘制出折线,第三个False参数指出这个折线不需要闭合。第四个参数指定了折线的颜色。

  当所有完成后,别忘了用h = np.flipud(h)反转绘制好的直方图,因为绘制时,[0,0]在图像的左上角。这在直方图可视化一节中有说明。

 

5,直方图均衡化

5.1   使用OpenCV绘制直方图均衡化

  我们可以调整直方图的值和它的bin值,让它看起来像x,y坐标,这样你就可以用cv.line()或cv.polyline()函数来绘制它,从而生成与上面相同的图像。这已经是OpenCV-Python2官方的样本了。查看sampl/python/hist.py的代码。我们用cv.calcHist()函数来找一张完整的图片的直方图。但是我们只要图片的一部分的直方图呢?在你想要找到的区域中,创建一个带有白色的遮罩图像。然后把它作为遮罩

  我们可以拿到一幅图像进行mask操作,并且可以看一下其直方图分布:

#_*_coding:utf-8_*_
import cv2  # opencv读取的格式是BGR
import numpy as np
import matplotlib.pyplot as plt  # Matplotlib读取的格式是RGB

img = cv2.imread('cat.jpg', 0)

# 创建 mask
mask = np.zeros(img.shape[:2], np.uint8)
print(mask.shape)  # (414, 500)
mask[100:300, 100:400] = 255

masked_img = cv2.bitwise_and(img, img, mask=mask)  # 与操作

hist_full = cv2.calcHist([img], [0], None, [256], [0, 256])
hist_mask = cv2.calcHist([img], [0], mask, [256], [0, 256])

plt.subplot(221), plt.imshow(img, 'gray'), plt.title('gray image')
plt.subplot(222), plt.imshow(mask, 'gray'), plt.title('mask image')
plt.subplot(223), plt.imshow(masked_img, 'gray'), plt.title('image bitwise and mask')
plt.subplot(224), plt.plot(hist_full), plt.plot(hist_mask), plt.title('hist image')
plt.xlim([0, 256])
plt.show()

  效果如下:

  OpenCV有一个函数可以这样做,cv.equalizeHist(),它封装好了计算cdf和cdf重映射以及根据cdf表生成直方图均衡图像的过程。它的输入只是灰度图像,输出是我们的直方图均衡图像。

img = cv.imread('cat,jpg', 0)
equ = cv.equalizeHist(img)
res = np.hstack((img, equ)) # 并排叠加图片
cv.imwrite('res.png', res)

  所以现在你可以用不同的光条件来拍摄不同的图像,平衡它,并检查结果。

  当图像的直方图被限制在一个特定的区域时,直方图均衡是很好的。在那些有很大强度变化的地方,直方图覆盖了一个大区域,比如明亮的和暗的像素,这样的地方就不好用了。

  我们可以看看直方图均衡化之前和之后的直方图分布图。

  补充代码如下:

plt.hist(img.ravel(), 256)
plt.hist(equ.ravel(), 256)
plt.show()

   展示在一个直方图,效果如下:

5.2   使用OpenCV绘制自适应直方图均衡化

   局部直方图均衡化,即把图像分成许多小块(比如按 8*8 作为一个小块),那么对每个小块进行均衡化。这种方法主要对图像直方图不是那么单一的(比如存在多峰情况)的图像比较实用。

  直方图自适应直方图均衡化的源码如下:

def createCLAHE(clipLimit=None, tileGridSize=None): # real signature unknown; restored from __doc__
    """
    createCLAHE([, clipLimit[, tileGridSize]]) -> retval
    .   @brief Creates a smart pointer to a cv::CLAHE class and initializes it.
    .   
    .   @param clipLimit Threshold for contrast limiting.
    .   @param tileGridSize Size of grid for histogram equalization. Input image will be divided into
    .   equally sized rectangular tiles. tileGridSize defines the number of tiles in row and column.
    """
    pass

  参数说明:

  • clipLimit:颜色对比度的阈值
  • titleGridSize:进行像素均衡化的网格大小,即在多少网格下进行直方图的均衡化操作

  代码如下:

#_*_coding:utf-8_*_
import cv2  # opencv读取的格式是BGR
import numpy as np
import matplotlib.pyplot as plt  # Matplotlib读取的格式是RGB

img = cv2.imread('cat.jpg', 0)

equ = cv2.equalizeHist(img)
clahe = cv2.createCLAHE(clipLimit=2.0, tileGridSize=(8, 8))

res_clahe = clahe.apply(img)


res = np.hstack((img, equ, res_clahe)) 
cv2.imshow('res', res)
cv2.waitKey(0)
cv2.destroyAllWindows()

   效果如下:

  示例1:单通道的灰阶图的直方图均衡化

import cv2 as cv
import numpy as np 
from matplotlib import pyplot as plt


im = cv.imread("test.png", 0)
cv.imshow("before", im)

# Histogram Equalization
im2 = cv.equalizeHist(im)
print(im2)

cv.imshow("after", im2)
plt.show()
cv.waitKey(0)

  

  示例2:彩图的直方图均衡

import cv2 as cv
import numpy as np 
from matplotlib import pyplot as plt


im = cv.imread("test.jpg")
cv.imshow("before", im)

# split g,b,r
g = im[:,:,0]
b = im[:,:,1]
r = im[:,:,2]


# Histogram Equalization
r2 = cv.equalizeHist(r)
g2 = cv.equalizeHist(g)
b2 = cv.equalizeHist(b)

im2 = im.copy()
im2[:,:,0] = g2
im2[:,:,1] = b2
im2[:,:,2] = r2

print(im2)

cv.imshow("after", im2)
plt.show()
cv.waitKey(0)

  

  示例3:带遮罩的直方图均衡化

import cv2 as cv
import numpy as np 
from matplotlib import pyplot as plt

im = cv.imread("test.png", 0)
cv.imshow("before", im)
mask = cv.imread("test_mask2.png", 0)
cv.imshow("mask", mask)

# calculate histogram with mask
hist_mask = cv.calcHist([im], [0], mask, [256], [0,256])

# calculate cdf with mask
cdf = hist_mask.cumsum()

# Histogram Equalization
cdf = (cdf-cdf[0])*255/(cdf[-1]-1)
cdf = cdf.astype(np.uint8)# Transform from float64 back to unit8

# generate img after Histogram Equalization
im2 = np.zeros((384, 495, 1), dtype =np.uint8)
im2 = cdf[im]

# im2 = cv.equalizeHist(im)
print(im2)

cv.imshow("after", im2)
plt.show()
cv.waitKey(0)

  

5.2,使用Numpy进行直方图均衡化

  计算累积和的 cumsun()函数

numpy.cumsum(a, axis=None, dtype=None, out=None)

  这个函数的功能是返回给定axis上的累积和

>>>import numpy as np  
>>> b=[1,2,3,4,5,6,7]  
>>> np.cumsum(a)  
array([  1,   3,   6,  10,  15,  21,  28,  36,  45,  55,  75, 105])

  直方图均衡化

import numpy as np
import cv2 as cv
from matplotlib import pyplot as plt

img = cv.imread('wiki.jpg', 0)

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()

  你可以看到直方图位于更亮的区域。我们需要让它充满整个频谱。为此,我们需要一个转换函数,它将更亮区域的输入像素映射到全区域的输出像素。这就是直方图均衡所做的。

  现在我们找到了最小的直方图值(不包括0),并应用了在wiki页面中给出的直方图均衡等式。

cdf = (cdf-cdf[0]) *255/ (cdf[-1]-1)
cdf = cdf.astype(np.uint8)

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

img2 = cdf[img]

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

 

  示例1:单通道的灰阶图的直方图均衡化

import cv2 as cv
import numpy as np 
from matplotlib import pyplot as plt

img = cv.imread("test.png", 0)
cv.imshow("before", img)

# calculate hist
hist, bins = np.histogram(img, 256)
# calculate cdf
cdf = hist.cumsum()
# plot hist
plt.plot(hist,'r')

# remap cdf to [0,255]
cdf = (cdf-cdf[0])*255/(cdf[-1]-1)
cdf = cdf.astype(np.uint8)# Transform from float64 back to unit8

# generate img after Histogram Equalization
img2 = np.zeros((384, 495, 1), dtype =np.uint8)
img2 = cdf[img]

hist2, bins2 = np.histogram(img2, 256)
cdf2 = hist2.cumsum()
plt.plot(hist2, 'g')

cv.imshow("after", img2)
plt.show()
cv.waitKey(0)

  我们可以看到,直方图均衡化后的图像对比度增强了。

  示例2:彩图的直方图均衡化

  首先是分离通道,对比三个通道分别进行处理,合并三通道颜色到图片

import cv2 as cv
import numpy as np 
from matplotlib import pyplot as plt


img = cv.imread("test.png")
cv.imshow("before", img)

# split g,b,r
g = img[:,:,0]
b = img[:,:,1]
r = img[:,:,2]

# calculate hist
hist_r, bins_r = np.histogram(r, 256)
hist_g, bins_g = np.histogram(g, 256)
hist_b, bins_b = np.histogram(b, 256)

# calculate cdf
cdf_r = hist_r.cumsum()
cdf_g = hist_g.cumsum()
cdf_b = hist_b.cumsum()

# remap cdf to [0,255]
cdf_r = (cdf_r-cdf_r[0])*255/(cdf_r[-1]-1)
cdf_r = cdf_r.astype(np.uint8)# Transform from float64 back to unit8
cdf_g = (cdf_g-cdf_g[0])*255/(cdf_g[-1]-1)
cdf_g = cdf_g.astype(np.uint8)# Transform from float64 back to unit8
cdf_b = (cdf_b-cdf_b[0])*255/(cdf_b[-1]-1)
cdf_b = cdf_b.astype(np.uint8)# Transform from float64 back to unit8

# get pixel by cdf table
r2 = cdf_r[r]
g2 = cdf_g[g]
b2 = cdf_b[b]

# merge g,b,r channel
img2 = img.copy()
img2[:,:,0] = g2
img2[:,:,1] = b2
img2[:,:,2] = r2

# show img after histogram equalization
cv.imshow("img2", img2)

cv.waitKey(0)

  

  示例三:带遮罩的直方图均衡化

  如果想要在做直方图均衡化的时候不考虑图像的某一部分,比如我们不想考虑图片右上角的云彩,那么可以使用遮罩在计算hist和cdf时不考虑这一部分像素。

import cv2 as cv
import numpy as np 
from matplotlib import pyplot as plt


img = cv.imread("test.png", 0)
cv.imshow("src", img)

# load mask img
mask = cv.imread("test_mask2.png", 0)
cv.imshow("mask", mask)

# apply mask to src
masked_img = np.ma.masked_array(img, mask = mask)
masked_img = np.ma.filled(masked_img,0).astype('uint8')
# print(masked_img)
masked_img = np.ma.masked_equal(masked_img,0)
# print(masked_img)
cv.imshow("masked_img", masked_img)


# calculate hist
hist, bins = np.histogram(masked_img.compressed(), 256) # img have to be compressed() to let mask work
# calculate cdf
cdf = hist.cumsum()

print(cdf)
# plot hist
plt.plot(hist,'r')

# remap cdf to [0,255]
cdf = (cdf-cdf[0])*255/(cdf[-1]-1)
cdf = cdf.astype(np.uint8)# Transform from float64 back to unit8

# generate img after Histogram Equalization
img2 = np.zeros((384, 495, 1), dtype =np.uint8)
img2 = cdf[img]

hist2, bins2 = np.histogram(img2, 256)
cdf2 = hist2.cumsum()
plt.plot(hist2, 'g')

cv.imshow("dst", img2)
plt.show()
cv.waitKey(0)

  

6,使用查找表(LUT)来拉伸直方图

  在图像处理中,直方图均衡化一般用来均衡图像的强度,或增加图像的对比度。在介绍使用直方图均衡化来拉伸图像的直方图之前,先学习使用查询表的方法。

  直方图的绘制代码如下:

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


def show_histphoto(photo_path):
    image = cv2.imread(photo_path, 0)
    print(image.shape)
    hist = cv2.calcHist([image], [0], None, [256], [0.0, 256.0])
    # print(hist.shape)
    plt.plot(hist)

def Line_chart(photo_path):
    image = cv2.imread(photo_path)
    # 创建用于绘制直方图的全0 图像
    h = np.zeros((256, 256, 3))
    # 直方图中各bin的顶点位置
    bins = np.arange(256).reshape(256, 1)
    # BGR 三种颜色
    color = [(255, 0, 0), (0, 255, 0), (0, 0, 255)]
    for ch, col in enumerate(color):
        originHist = cv2.calcHist([image], [ch], None, [256], [0, 256])
        cv2.normalize(originHist, originHist, 0, 255*0.9, cv2.NORM_MINMAX)
        hist = np.int32(np.around(originHist))
        pts = np.column_stack((bins, hist))
        cv2.polylines(h, [pts], False, col)

    h = np.flipud(h)
    cv2.imshow('colorhist', h)
    cv2.waitKey(0)

if __name__ == '__main__':
    photo_path = 'test.jpg'
    # show_histphoto(photo_path)
    Line_chart(photo_path)

  (一种借助matplotlib,代码简单,一种使用opencv,需要转化)

   观察上图中原始图像的直方图,很容易发现大部分强度值范围都没有用到。因此先检测图像非0的最低(imin)强度值和最高(imax)强度值。将最低强度值imin设置为0,最高值 imax 设为255。中间的按照255.0*(i - imin)/(imax - imin)+ 0.5)的形式设置。

  实现的任务主要集中在查询表的创建中,代码如下:

minBinNo, maxBinNo = 0, 255

# 计算从左起第一个不为0的直方图位置
for binNo, binValue in enumerate(hist):
    if binValue != 0:
        minBinNo = binNo
        break

# 计算从右起第一个不为0的直方图位置
for binNo, binValue in enumerate(reversed(hist)):
    if binValue != 0:
        maxBinNo = 255 - binNo
        break

# 生成查找表
for i, v in enumerate(lut):
    if i < minBinNo:
        lut[i] = 0
    elif i > maxBinNo:
        lut[i] = 255
    else:
        lut[i] = int(255.0*(i-minBinNo)/(maxBinNo-minBinNo)+0.5)

  查询表创建完成后,就直接调用相应的OpenCV函数,这里调用的时 cv2.LUT函数:

#计算
result = cv2.LUT(image, lut)

  cv2.LUT 函数只有两个参数,分别为输入图像和查找表,其返回处理的结果。

  完整代码如下:

import cv2
import numpy as np

image = cv2.imread('wiki.jpg', 0)
# 创建空的查找表
lut = np.zeros(256, dtype=image.dtype)
# OpenCV提供了cv.calcHist()函数来获取直方图
hist = cv2.calcHist([image],  # 计算图像的直方图
                    [0],  # 使用的通道
                    None,  # 没有使用mask
                    [256],  # it is a 2D histogram
                    [0.0, 255.0])
# print(hist.shape)  # (256, 1)
minBinNo, maxBinNo = 0, 255

# 计算从左起第一个不为0的直方图位置
for binNo, binValue in enumerate(hist):
    if binValue != 0:
        minBinNo = binNo
        break

# 计算从右起第一个不为0的直方图位置
for binNo, binValue in enumerate(reversed(hist)):
    if binValue != 0:
        maxBinNo = 255 - binNo
        break

# 生成查找表
for i, v in enumerate(lut):
    if i < minBinNo:
        lut[i] = 0
    elif i > maxBinNo:
        lut[i] = 255
    else:
        lut[i] = int(255.0 * (i - minBinNo) / (maxBinNo - minBinNo) + 0.5)

# 计算
result = cv2.LUT(image, lut)
print(result.shape)   # (534, 800)
cv2.imshow('result', result)
cv2.waitKey(0)
cv2.destroyAllWindows()

  效果如下:

 

7,直方图均衡化OpenCV实现和Numpy实现的对比

  图像为下图:

7.1  使用OpenCV函数实现

  用OpenCV函数实现直方图均衡化很简单,只需要调用一个函数即可:

def opencv_equalizeHist(image_path):
    img = cv2.imread(image_path, 0)
    equ = cv2.equalizeHist(img)
    cv2.imshow('equ', equ)
    cv2.waitKey(0)
    cv2.destroyAllWindows()

  这样就图像均衡化了。效果如下:

7.2,使用Numpy函数实现

  通过前面的介绍,可以明白直方图均衡化就是用一种特殊的查询表来实现的,所以这里用Numpy函数,以查找表的方式手动来实现图像直方图均衡化:

def numpy_equalizeHist(image_path):
    img = cv2.imread(image_path, 0)
    # 创建空的查询表
    lut = np.zeros(256, dtype=img.dtype)

    hist, bins = np.histogram(img.flatten(), 256, [0, 256])
    # 计算累计直方图
    cdf = hist.cumsum()
    # 除以直方图中的0值
    cdf_m = np.ma.masked_equal(cdf, 0)
    #等同于前面介绍的lut[i] = int(255.0 *p[i])公式
    cdf_m = (cdf_m - cdf_m.min())*255/(cdf_m.max()-cdf_m.min())
    #将掩模处理掉的元素补为0
    cdf = np.ma.filled(cdf_m, 0).astype('uint8')
    # 计算
    result2 = cdf[img]
    # result = cv2.LUT(img, cdf)

    # cv2.imshow("OpenCVLUT", result)
    cv2.imshow("NumPyLUT", result2)
    cv2.waitKey(0)
    cv2.destroyAllWindows()

  结果如下:

 

7.3,三种方法的直方图对比

  这里对比了使用查找表,使用OpenCV,使用numpy直方图均衡化生成的直方图:

  (注意:这里有将三种方法生成的直方图均衡化的图片保存下来,然后对此均衡化的图片画直方图)

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


def LookUpTable(photo_path):
    image = cv2.imread(photo_path, 0)
    # 创建空的查找表
    lut = np.zeros(256, dtype=image.dtype)
    # OpenCV提供了cv.calcHist()函数来获取直方图
    hist = cv2.calcHist([image],  # 计算图像的直方图
                        [0],  # 使用的通道
                        None,  # 没有使用mask
                        [256],  # it is a 2D histogram
                        [0.0, 255.0])
    # print(hist.shape)  # (256, 1)
    minBinNo, maxBinNo = 0, 255

    # 计算从左起第一个不为0的直方图位置
    for binNo, binValue in enumerate(hist):
        if binValue != 0:
            minBinNo = binNo
            break

    # 计算从右起第一个不为0的直方图位置
    for binNo, binValue in enumerate(reversed(hist)):
        if binValue != 0:
            maxBinNo = 255 - binNo
            break

    # 生成查找表
    for i, v in enumerate(lut):
        if i < minBinNo:
            lut[i] = 0
        elif i > maxBinNo:
            lut[i] = 255
        else:
            lut[i] = int(255.0 * (i - minBinNo) / (maxBinNo - minBinNo) + 0.5)

    # 计算
    lut = cv2.LUT(image, lut)
    cv2.imwrite('lut.jpg', lut)
    # cv2.imshow('lut', lut)
    # cv2.waitKey(0)
    # cv2.destroyAllWindows()
    return lut


def opencv_equalizeHist(image_path):
    img = cv2.imread(image_path, 0)
    equ = cv2.equalizeHist(img)
    cv2.imwrite('equ.jpg', equ)
    # cv2.imshow('equ', equ)
    # cv2.waitKey(0)
    # cv2.destroyAllWindows()
    return equ


def numpy_equalizeHist(image_path):
    img = cv2.imread(image_path, 0)
    hist, bins = np.histogram(img.flatten(), 256, [0, 256])
    # 计算累计直方图
    cdf = hist.cumsum()
    # 除以直方图中的0值
    cdf_m = np.ma.masked_equal(cdf, 0)
    # 等同于前面介绍的lut[i] = int(255.0 *p[i])公式
    cdf_m = (cdf_m - cdf_m.min()) * 255 / (cdf_m.max() - cdf_m.min())
    # 将掩模处理掉的元素补为0
    cdf = np.ma.filled(cdf_m, 0).astype('uint8')
    # 计算
    numpy_lut = cdf[img]
    cv2.imwrite('numpy_lut.jpg', numpy_lut)
    # cv2.imshow("NumPyLUT", numpy_lut)
    # cv2.waitKey(0)
    # cv2.destroyAllWindows()
    return numpy_lut


def show_allphoto():
    lut = cv2.imread('lut.jpg', 0)
    np_equ = cv2.imread('numpy_lut.jpg', 0)
    opencv_equ = cv2.imread('equ.jpg', 0)
    print(lut.shape,  np_equ.shape, opencv_equ.shape)

    lut = cv2.calcHist([lut], [0], None, [256], [0.0, 256.0])
    np_equ = cv2.calcHist([np_equ], [0], None, [256], [0.0, 256.0])
    opencv_equ = cv2.calcHist([opencv_equ], [0], None, [256], [0.0, 256.0])

    plt.subplot(311), plt.plot(lut)
    plt.subplot(312), plt.plot(np_equ)
    plt.subplot(313), plt.plot(opencv_equ)
    plt.show()



if __name__ == '__main__':
    photo_path = 'wiki.jpg'
    # lut = LookUpTable(photo_path)
    # np_equ = numpy_equalizeHist(photo_path)
    # opencv_equ = opencv_equalizeHist(photo_path)
    show_allphoto()

  结构如下:

   lut计算出来的和opencv和numpy计算的结果还是不太一样。但是 opencv和numpy计算的结果相似。具体原因不知道,再学习。

 

 

 https://blog.csdn.net/sunny2038/article/details/9403059

https://blog.csdn.net/v_xchen_v/article/details/79913245

posted @ 2020-10-30 09:15  战争热诚  阅读(7605)  评论(2编辑  收藏  举报