如果需要处理的原图及代码,请移步小编的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

OpenCV计算机视觉学习(9)——图像直方图 & 直方图均衡化的更多相关文章

  1. OpenCV计算机视觉学习(13)——图像特征点检测(Harris角点检测,sift算法)

    如果需要处理的原图及代码,请移步小编的GitHub地址 传送门:请点击我 如果点击有误:https://github.com/LeBron-Jian/ComputerVisionPractice 前言 ...

  2. OpenCV计算机视觉学习(12)——图像量化处理&图像采样处理(K-Means聚类量化,局部马赛克处理)

    如果需要处理的原图及代码,请移步小编的GitHub地址 传送门:请点击我 如果点击有误:https://github.com/LeBron-Jian/ComputerVisionPractice 准备 ...

  3. OpenCV计算机视觉学习(11)——图像空间几何变换(图像缩放,图像旋转,图像翻转,图像平移,仿射变换,镜像变换)

    如果需要处理的原图及代码,请移步小编的GitHub地址 传送门:请点击我 如果点击有误:https://github.com/LeBron-Jian/ComputerVisionPractice 图像 ...

  4. OpenCV计算机视觉学习(5)——形态学处理(腐蚀膨胀,开闭运算,礼帽黑帽,边缘检测)

    如果需要处理的原图及代码,请移步小编的GitHub地址 传送门:请点击我 如果点击有误:https://github.com/LeBron-Jian/ComputerVisionPractice 形态 ...

  5. OpenCV计算机视觉学习(10)——图像变换(傅里叶变换,高通滤波,低通滤波)

    如果需要处理的原图及代码,请移步小编的GitHub地址 传送门:请点击我 如果点击有误:https://github.com/LeBron-Jian/ComputerVisionPractice 在数 ...

  6. OpenCV计算机视觉学习(1)——图像基本操作(图像视频读取,ROI区域截取,常用cv函数解释)

    1,计算机眼中的图像 我们打开经典的 Lena图片,看看计算机是如何看待图片的: 我们点击图中的一个小格子,发现计算机会将其分为R,G,B三种通道.每个通道分别由一堆0~256之间的数字组成,那Ope ...

  7. OpenCV计算机视觉学习(2)——图像算术运算 & 掩膜mask操作(数值计算,图像融合,边界填充)

    在OpenCV中我们经常会遇到一个名字:Mask(掩膜).很多函数都使用到它,那么这个Mask到底是什么呢,下面我们从图像基本运算开始,一步一步学习掩膜. 1,图像算术运算 图像的算术运算有很多种,比 ...

  8. OpenCV计算机视觉学习(3)——图像灰度线性变换与非线性变换(对数变换,伽马变换)

    如果需要处理的原图及代码,请移步小编的GitHub地址 传送门:请点击我 如果点击有误:https://github.com/LeBron-Jian/ComputerVisionPractice 下面 ...

  9. OpenCV计算机视觉学习(7)——图像金字塔(高斯金字塔,拉普拉斯金字塔)

    如果需要处理的原图及代码,请移步小编的GitHub地址 传送门:请点击我 如果点击有误:https://github.com/LeBron-Jian/ComputerVisionPractice 本节 ...

随机推荐

  1. MMC & SD 发展历史

    一.概述 MMC 卡和 SD 卡都是基于 Nand Flash 技术的移动存储卡. MMC(MultiMediaCard) 卡于 1997 年由西门子和 Sandisk 推出,SD (Secure D ...

  2. python_用户登录验证

    登录验证,三次机会,如何做? 1. 一个用户列表记录合法密码和用户名,一个小黑屋列表记录输错3次的用户, 一个中间列表记录所有用户输入,统计某个用户是否输错3次 2. 通过 in 判断一个元素是否在一 ...

  3. 022 01 Android 零基础入门 01 Java基础语法 03 Java运算符 02 算术运算符

    022 01 Android 零基础入门 01 Java基础语法 03 Java运算符 02 算术运算符 本文知识点:Java中的算术运算符 算术运算符介绍 算术运算符代码示例 注意字符串连接问题和整 ...

  4. Python实现的数据结构与算法之基本搜索详解

    一.顺序搜索 顺序搜索 是最简单直观的搜索方法:从列表开头到末尾,逐个比较待搜索项与列表中的项,直到找到目标项(搜索成功)或者 超出搜索范围 (搜索失败). 根据列表中的项是否按顺序排列,可以将列表分 ...

  5. 批处理文件的@echo off

    转载:https://blog.csdn.net/zl1zl2zl3/article/details/79218448  @echo off  关闭回显     @echo on  打开回显      ...

  6. vue显示后端传递的图片流

    一.显示部分(组件我使用的vuetify) <template> <v-container fluid> <v-card width="100%" m ...

  7. Flink深入浅出: 资源管理(v1.11)

    -- 图片来自 <国家地理中文网>-- 往期推荐: Flink深入浅出:部署模式 Flink深入浅出:内存模型 Flink深入浅出:JDBC Source从理论到实战 Flink深入浅出: ...

  8. 非阻塞I/O和阻塞I/O

    1.简介 等待队列实现在事件上的条件等待:希望等待特定事件的进程把自己放进合适的等待队列,并放弃控制权.可用于: - 中断处理 - 进程同步 - 定时 2.等待队列头数据结构 1 typedef st ...

  9. linux处理l2tp协议的示意图

  10. 多测师讲解requests __上_高级讲师肖sir

    1.三种接口接口请求方式 # # 在python当中接口的请求方式有哪些:# import requests # 导入requests接口库# # # # 请求方式有三种:# # # # 第一种:# ...