[Python图像处理] 三十七.OpenCV直方图统计两万字详解(掩膜直方图、灰度直方图对比、黑夜白天预测)

该系列文章是讲解Python OpenCV图像处理知识,前期主要讲解图像入门、OpenCV基础用法,中期讲解图像处理的各种算法,包括图像锐化算子、图像增强技术、图像分割等,后期结合深度学习研究图像识别、图像分类应用。希望文章对您有所帮助,如果有不足之处,还请海涵~

前面一篇文章介绍了图像几何变换,包括:图像平移变换、图像缩放变换、图像旋转变换、图像镜像变换、图像仿射变换和图像透视变换;这篇文章将详细介绍直方图统计,包括Matplotlib和OpenCV绘制直方图、掩膜直方图、灰度直方图对比及通过直方图预测黑夜白天,万字长文整理,希望对您有所帮助。 同时,该部分知识均为作者查阅资料撰写总结,并且开设成了收费专栏,为小宝赚点奶粉钱,感谢您的抬爱。当然如果您是在读学生或经济拮据,可以私聊我给你每篇文章开白名单,或者转发原文给你,更希望您能进步,一起加油喔~

前文参考:


图像灰度直方图(Histogram)是灰度级分布的函数,是对图像中灰度级分布的统计。灰度直方图是将数字图像中的所有像素,按照灰度值的大小,统计其出现的频率并绘制相关图形。本章主要讲解Matplotlib和OpenCV绘制直方图的两种方法,对比了灰度处理算法前后的直方图,实现掩膜直方图绘制、图像H-S直方图、直方图判断黑夜白天等内容。

一.图像直方图概述

灰度直方图是灰度级的函数,描述的是图像中每种灰度级像素的个数,反映图像中每种灰度出现的频率。假设存在一幅6×6像素的图像,接着统计其1至6灰度级的出现频率,并绘制如图1所示的柱状图,其中横坐标表示灰度级,纵坐标表示灰度级出现的频率。

在这里插入图片描述

如果灰度级为0-255(最小值0为黑色,最大值255为白色),同样可以绘制对应的直方图,如图2所示,左边是一幅灰度图像(Lena灰度图),右边是对应各像素点的灰度级频率。

在这里插入图片描述

为了让图像各灰度级的出现频数形成固定标准的形式,可以通过归一化方法对图像直方图进行处理,将待处理的原始图像转换成相应的标准形式[3]。假设变量r表示图像中像素灰度级,归一化处理后会将r限定在下述范围:

在这里插入图片描述

在灰度级中,r为0时表示黑色,r为1时表示白色。对于一幅给定图像,每个像素值位于[0,1]区间之内,接着计算原始图像的灰度分布,用概率密度函数P®实现。为了更好地进行数字图像处理,必须引入离散形式。在离散形式下,用rk表示离散灰度级,P(rk)代替P®,并满足公式(2)。

在这里插入图片描述

公式中,nk为图像中出现rk这种灰度的像素数,n是图像中像素总数,是概率论中的频数,l是灰度级总数(通常l为256级灰度)。接着在直角坐标系中做出rk和P(rk)的关系图,则成为灰度级的直方图。

假设存在一幅3×3像素的图像,其像素值如公式(9-3)所示,则归一化直方图的步骤如下:

在这里插入图片描述

  • 首先统计各灰度级对应的像素个数。用x数组统计像素点的灰度级,y数组统计具有该灰度级的像素个数。其中,灰度为1的像素共3个,灰度为2的像素共1个,灰度为3的像素共2个,灰度为4的像素共1个,灰度为5的像素共2个。

在这里插入图片描述

  • 接着统计总像素个数,如公式(5)所示。

在这里插入图片描述

  • 最后统计各灰度级的出现概率,通过公式(6)进行计算,其结果如下:

在这里插入图片描述

绘制的归一化图行如图3所示,横坐标表示图像中各个像素点的灰度级,纵坐标表示出现这个灰度级的概率。

在这里插入图片描述

直方图被广泛应用于计算机视觉领域,在使用边缘和颜色确定物体边界时,通过直方图能更好地选择边界阈值,进行阈值化处理。同时,直方图对物体与背景有较强对比的景物的分割特别有用,可以应用于检测视频中场景的变换及图像中的兴趣点,简单物体的面积和综合光密度IOD也可以通过图像的直方图计算而得。


二.Matplotlib绘制直方图

Matplotlib是Python强大的数据可视化工具,主要用于绘制各种2D图形。本小节Python绘制直方图主要调用matplotlib.pyplot库中hist()函数实现,它会根据数据源和像素级绘制直方图。其函数主要包括五个常用的参数,如下所示:

  • n, bins, patches = plt.hist(arr, bins=50, normed=1, facecolor=‘green’, alpha=0.75)
    – arr表示需要计算直方图的一维数组
    – bins表示直方图显示的柱数,可选项,默认值为10
    – normed表示是否将得到的直方图进行向量归一化处理,默认值为0
    – facecolor表示直方图颜色
    – alpha表示透明度
    – n为返回值,表示直方图向量
    – bins为返回值,表示各个bin的区间范围
    – patches为返回值,表示返回每个bin里面包含的数据,是一个列表

图像直方图的Python实现代码如下所示,该示例主要是通过matplotlib.pyplot库中的hist()函数绘制的。注意,读取的“picture.bmp”图像的像素为二维数组,而hist()函数的数据源必须是一维数组,通常需要通过函数ravel()拉直图像。

# -*- coding: utf-8 -*-
# By:Eastmount CSDN 2021-02-05
import cv2  
import numpy as np  
import matplotlib.pyplot as plt
 
#读取图片
img = cv2.imread('lena.bmp')

#灰度转换
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
 
#直方图均衡化处理
result = cv2.equalizeHist(gray)

#显示图像
plt.subplot(221)
plt.imshow(gray, cmap=plt.cm.gray), plt.axis("off"), plt.title('(a)') 
plt.subplot(222)
plt.imshow(result, cmap=plt.cm.gray), plt.axis("off"), plt.title('(b)') 
plt.subplot(223)
plt.hist(img.ravel(), 256), plt.title('(c)') 
plt.subplot(224)
plt.hist(result.ravel(), 256), plt.title('(d)') 
plt.show()

读取显示的“lena”灰度图像如图4所示。

在这里插入图片描述

最终的灰度直方图如图5所示,它将Lena图256级灰度和各个灰度级的频数绘制出来,其中x轴表示图像的256级灰度,y轴表示各个灰度级的频数。

在这里插入图片描述

如果调用下列函数,则绘制的直方图是经过标准化处理,并且颜色为绿色、透明度为0.75的直方图,如图6所示。

  • plt.hist(src.ravel(), bins=256, density=1, facecolor=‘green’, alpha=0.75)

在这里插入图片描述

彩色直方图是高维直方图的特例,它统计彩色图片RGB各分量出现的频率,即彩色概率分布信息。彩色图片的直方图和灰度直方图一样,只是分别画出三个通道的直方图,然后再进行叠加,其代码如下所示。Lena彩色原始图像如图7所示。

在这里插入图片描述

#coding:utf-8
# By:Eastmount CSDN 2021-02-05
import cv2  
import numpy as np
import matplotlib.pyplot as plt

#读取图像
src = cv2.imread('Lena.png')

#获取BGR三个通道的像素值
b, g, r = cv2.split(src)
print(r,g,b)

#绘制直方图
plt.figure("Lena")
#蓝色分量
plt.hist(b.ravel(), bins=256, density=1, facecolor='b', edgecolor='b', alpha=0.75)
#绿色分量
plt.hist(g.ravel(), bins=256, density=1, facecolor='g', edgecolor='g', alpha=0.75)
#红色分量
plt.hist(r.ravel(), bins=256, density=1, facecolor='r', edgecolor='r', alpha=0.75)
plt.xlabel("x")
plt.ylabel("y")
plt.show()

#显示原始图像
cv2.imshow("src", src)
cv2.waitKey(0)
cv2.destroyAllWindows()

绘制的彩色直方图如图8所示,包括红色、绿色、蓝色三种对比。

在这里插入图片描述

如果希望将三个颜色分量的柱状图分开绘制并进行对比,则使用如下代码实现,调用plt.figure(figsize=(8, 6))函数绘制窗口,以及plt.subplot()函数分别绘制4个子图。

#coding:utf-8
# By:Eastmount CSDN 2021-02-05
import cv2  
import numpy as np
import matplotlib.pyplot as plt
import matplotlib

#读取图像
src = cv2.imread('Lena.png')

#转换为RGB图像
img_rgb = cv2.cvtColor(src, cv2.COLOR_BGR2RGB)

#获取BGR三个通道的像素值
b, g, r = cv2.split(src)
print(r,g,b)

plt.figure(figsize=(8, 6))

#设置字体
matplotlib.rcParams['font.sans-serif']=['SimHei']

#原始图像
plt.subplot(221)
plt.imshow(img_rgb)
plt.axis('off')
plt.title("(a)原图像")

#绘制蓝色分量直方图
plt.subplot(222)
plt.hist(b.ravel(), bins=256, density=1, facecolor='b', edgecolor='b', alpha=0.75)
plt.xlabel("x")
plt.ylabel("y")
plt.title("(b)蓝色分量直方图")

#绘制绿色分量直方图
plt.subplot(223)
plt.hist(g.ravel(), bins=256, density=1, facecolor='g', edgecolor='g', alpha=0.75)
plt.xlabel("x")
plt.ylabel("y")
plt.title("(c)绿色分量直方图")

#绘制红色分量直方图
plt.subplot(224)
plt.hist(r.ravel(), bins=256, density=1, facecolor='r', edgecolor='r', alpha=0.75)
plt.xlabel("x")
plt.ylabel("y")
plt.title("(d)红色分量直方图")
plt.show()

最终输出的图形如图9所示,,图9(a)表示原图像,图9(b)表示蓝色分量直方图,图9©表示绿色分量直方图,图9(d)表示红色分类直方图。

在这里插入图片描述


三.OpenCV绘制直方图

前一小节讲解了如何调用matplotlib库绘制直方图,接下来讲解使用OpenCV库绘制直方图的方法。在OpenCV中可以使用calcHist()函数计算直方图,计算完成之后采用OpenCV中的绘图函数,如绘制矩形的rectangle()函数,绘制线段的line()函数来完成。其中,cv2.calcHist()的函数原型及常见六个参数如下:

  • hist = cv2.calcHist(images, channels, mask, histSize, ranges, accumulate)
    – hist表示直方图,返回一个二维数组
    – images表示输入的原始图像
    – channels表示指定通道,通道编号需要使用中括号,输入图像是灰度图像时,它的值为[0],彩色图像则为[0]、[1]、[2],分别表示蓝色(B)、绿色(G)、红色(R)
    – mask表示可选的操作掩码。如果要统计整幅图像的直方图,则该值为None;如果要统计图像的某一部分直方图时,需要掩码来计算
    – histSize表示灰度级的个数,需要使用中括号,比如[256]
    – ranges表示像素值范围,比如[0, 255]
    – accumulate表示累计叠加标识,默认为false,如果被设置为true,则直方图在开始分配时不会被清零,该参数允许从多个对象中计算单个直方图,或者用于实时更新直方图;多个直方图的累积结果用于对一组图像的直方图计算

接下来的代码是计算图像各灰度级的大小、形状及频数,接着调用plot()函数绘制直方图曲线。

#encoding:utf-8
#By:Eastmount CSDN 2021-02-05
import cv2  
import numpy as np
import matplotlib.pyplot as plt
import matplotlib

#读取图像
src = cv2.imread('lena.bmp')

#计算256灰度级的图像直方图
hist = cv2.calcHist([src], [0], None, [256], [0,255])

#输出直方图大小、形状、数量
print(hist.size)
print(hist.shape)
print(hist)

#设置字体
matplotlib.rcParams['font.sans-serif']=['SimHei']

#显示原始图像和绘制的直方图
plt.subplot(121)
plt.imshow(src, 'gray')
plt.axis('off')
plt.title("(a)Lena灰度图像")

plt.subplot(122)
plt.plot(hist, color='r')
plt.xlabel("x")
plt.ylabel("y")
plt.title("(b)直方图曲线")
plt.show()

上述代码绘制的“Lena”灰度图像所对应的直方图曲线如图10所示,图10(a)表示原图像,图10(b)表示对应的灰度直方图曲线。

在这里插入图片描述

同时输出直方图的大小、形状及数量,如下所示:

256
(256L, 1L)
[[7.000e+00]
 [1.000e+00]
 [0.000e+00]
 [6.000e+00]
 [2.000e+00]
 ....
 [1.000e+00]
 [3.000e+00]
 [2.000e+00]
 [1.000e+00]
 [0.000e+00]]

彩色图像调用OpenCV绘制直方图的算法与灰度图像一样,只是从B、G、R三个放量分别进行计算及绘制,详见代码。

#encoding:utf-8
#By:Eastmount CSDN 2021-02-05
import cv2  
import numpy as np
import matplotlib.pyplot as plt
import matplotlib

#读取图像
src = cv2.imread('Lena.png')

#转换为RGB图像
img_rgb = cv2.cvtColor(src, cv2.COLOR_BGR2RGB)

#计算直方图
histb = cv2.calcHist([src], [0], None, [256], [0,255])
histg = cv2.calcHist([src], [1], None, [256], [0,255])
histr = cv2.calcHist([src], [2], None, [256], [0,255])

#设置字体
matplotlib.rcParams['font.sans-serif']=['SimHei']

#显示原始图像和绘制的直方图
plt.subplot(121)
plt.imshow(img_rgb, 'gray')
plt.axis('off')
plt.title("(a)Lena原始图像")

plt.subplot(122)
plt.plot(histb, color='b')
plt.plot(histg, color='g')
plt.plot(histr, color='r')
plt.xlabel("x")
plt.ylabel("y")
plt.title("(b)直方图曲线")
plt.show()

最终绘制的“Lena”彩色图像及其对应的彩色直方图曲线如图11所示,其中图11(a)表示Lena原始图像,图11(b)表示对应的彩色直方图曲线。

在这里插入图片描述


四.掩膜直方图

如果要统计图像的某一部分直方图,就需要使用掩码(蒙板)来进行计算。假设将要统计的部分设置为白色,其余部分设置为黑色,然后使用该掩膜进行直方图绘制,其完整代码如下所示。

#coding:utf-8
#By:Eastmount CSDN 2021-02-05
import cv2  
import numpy as np
import matplotlib.pyplot as plt
import matplotlib

#读取图像
img = cv2.imread('yxz.png')

#转换为RGB图像
img_rgb = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)

#设置掩膜
mask = np.zeros(img.shape[:2], np.uint8)
mask[100:300, 100:300] = 255
masked_img = cv2.bitwise_and(img, img, mask=mask)

#图像直方图计算
hist_full = cv2.calcHist([img], [0], None, [256], [0,256]) #通道[0]-灰度图

#图像直方图计算(含掩膜)
hist_mask = cv2.calcHist([img], [0], mask, [256], [0,256])

plt.figure(figsize=(8, 6))

#设置字体
matplotlib.rcParams['font.sans-serif']=['SimHei']

#原始图像
plt.subplot(221)
plt.imshow(img_rgb, 'gray')
plt.axis('off')
plt.title("(a)原始图像")

#绘制掩膜
plt.subplot(222)
plt.imshow(mask, 'gray')
plt.axis('off')
plt.title("(b)掩膜")

#绘制掩膜设置后的图像
plt.subplot(223)
plt.imshow(masked_img, 'gray')
plt.axis('off')
plt.title("(c)图像掩膜处理")

#绘制直方图
plt.subplot(224)
plt.plot(hist_full)
plt.plot(hist_mask)
plt.title("(d)直方图曲线")
plt.xlabel("x")
plt.ylabel("y")
plt.show()

其运行结果如图12所示,它使用了一个200×200像素的掩膜进行实验。其中图12(a)表示原始图像,图12(b)表示200×200像素的掩膜,图12©表示原始图像进行掩膜处理,图12(d)表示直方图曲线,蓝色曲线为原始图像的灰度值直方图分布情况,绿色波动更小的曲线为掩膜直方图曲线。

在这里插入图片描述


五.图像灰度变换直方图对比

前面详细介绍了图像灰度变换和阈值变换,本小节将结合直方图分别对比图像灰度变换前后的变化,方便读者更清晰地理解灰度变换和阈值变换。

1.灰度上移变换图像直方图对比

图像灰度上移变换使用的表达式为:

  • DB=DA+50

该算法将实现图像灰度值的上移,从而提升图像的亮度,结合直方图对比的实现代码如下所示。

#coding:utf-8
#By:Eastmount CSDN 2021-02-05
import cv2  
import numpy as np
import matplotlib.pyplot as plt

#读取图像
img = cv2.imread('lena.bmp')

#图像灰度转换
grayImage = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)

#获取图像高度和宽度
height = grayImage.shape[0]
width = grayImage.shape[1]
result = np.zeros((height, width), np.uint8)

#图像灰度上移变换 DB=DA+50
for i in range(height):
    for j in range(width):
        if (int(grayImage[i,j]+50) > 255):
            gray = 255
        else:
            gray = int(grayImage[i,j]+50)
            
        result[i,j] = np.uint8(gray)

#计算原图的直方图
hist = cv2.calcHist([img], [0], None, [256], [0,255])

#计算灰度变换的直方图
hist_res = cv2.calcHist([result], [0], None, [256], [0,255])

#原始图像
plt.figure(figsize=(8, 6))
plt.subplot(221), plt.imshow(img, 'gray'), plt.title("(a)"), plt.axis('off')

#绘制掩膜
plt.subplot(222), plt.plot(hist), plt.title("(b)"), plt.xlabel("x"), plt.ylabel("y")

#绘制掩膜设置后的图像
plt.subplot(223), plt.imshow(result, 'gray'), plt.title("(c)"), plt.axis('off')

#绘制直方图
plt.subplot(224), plt.plot(hist_res), plt.title("(d)"), plt.xlabel("x"), plt.ylabel("y")
plt.show()

其运行结果如图13所示,其中(a)表示原始图像,(b)表示对应的灰度直方图,(c)表示灰度上移后的图像,(d)是对应的直方图。对比发现,图13(d)比图13(b)的灰度级整体高了50,曲线整体向右平移了50个单位。

在这里插入图片描述


2.灰度减弱图像直方图对比

该算法将减弱图像的对比度,使用的表达式为:

  • DB=DA×0.8

Python结合直方图实现灰度对比度减弱的代码如下所示。

#coding:utf-8
#By:Eastmount CSDN 2021-02-05
import cv2  
import numpy as np
import matplotlib.pyplot as plt

#读取图像
img = cv2.imread('lena.bmp')

#图像灰度转换
grayImage = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)

#获取图像高度和宽度
height = grayImage.shape[0]
width = grayImage.shape[1]
result = np.zeros((height, width), np.uint8)

#图像对比度减弱变换 DB=DA×0.8
for i in range(height):
    for j in range(width):
        gray = int(grayImage[i,j]*0.8)
        result[i,j] = np.uint8(gray)

#计算原图的直方图
hist = cv2.calcHist([img], [0], None, [256], [0,255])

#计算灰度变换的直方图
hist_res = cv2.calcHist([result], [0], None, [256], [0,255])

#原始图像
plt.figure(figsize=(8, 6))
plt.subplot(221), plt.imshow(img, 'gray'), plt.title("(a)"), plt.axis('off')

#绘制掩膜
plt.subplot(222), plt.plot(hist), plt.title("(b)"), plt.xlabel("x"), plt.ylabel("y")

#绘制掩膜设置后的图像
plt.subplot(223), plt.imshow(result, 'gray'), plt.title("(c)"), plt.axis('off')

#绘制直方图
plt.subplot(224), plt.plot(hist_res), plt.title("(d)"), plt.xlabel("x"), plt.ylabel("y")
plt.show()

其运行结果如图14所示,其中(a)和(b)表示原始图像和对应的灰度直方图,(c)和(d)表示灰度减弱或对比度缩小的图像及对应的直方图。图14(d)比图14(b)的灰度级整体缩小了0.8倍,绘制的曲线更加密集。

在这里插入图片描述


3.图像反色变换直方图对比

该算法将图像的颜色反色,对原图像的像素值进行反转,即黑色变为白色,白色变为黑色,使用的表达式为:

  • DB=255-DA

实现代码如下所示。

#coding:utf-8
#By:Eastmount CSDN 2021-02-05
import cv2  
import numpy as np
import matplotlib.pyplot as plt

#读取图像
img = cv2.imread('lena.bmp')

#图像灰度转换
grayImage = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)

#获取图像高度和宽度
height = grayImage.shape[0]
width = grayImage.shape[1]
result = np.zeros((height, width), np.uint8)

#图像灰度反色变换 DB=255-DA
for i in range(height):
    for j in range(width):
        gray = 255 - grayImage[i,j]
        result[i,j] = np.uint8(gray)

#计算原图的直方图
hist = cv2.calcHist([img], [0], None, [256], [0,255])

#计算灰度变换的直方图
hist_res = cv2.calcHist([result], [0], None, [256], [0,255])

#原始图像
plt.figure(figsize=(8, 6))
plt.subplot(221), plt.imshow(img, 'gray'), plt.title("(a)"), plt.axis('off')

#绘制掩膜
plt.subplot(222), plt.plot(hist), plt.title("(b)"), plt.xlabel("x"), plt.ylabel("y")

#绘制掩膜设置后的图像
plt.subplot(223), plt.imshow(result, 'gray'), plt.title("(c)"), plt.axis('off')

#绘制直方图
plt.subplot(224), plt.plot(hist_res), plt.title("(d)"), plt.xlabel("x"), plt.ylabel("y")
plt.show()

其运行结果如图15所示,其中(a)和(b)表示原始图像和对应的灰度直方图,(c)和(d)表示灰度反色变换图像及对应的直方图。图15(d)与图15(b)是反相对称的,整个灰度值满足DB=255-DA表达式。

在这里插入图片描述


4.图像对数变换直方图对比

该算法将增加低灰度区域的对比度,从而增强暗部的细节,使用的表达式为:

在这里插入图片描述

下面的代码实现了图像灰度的对数变换及直方图对比。

#coding:utf-8
#By:Eastmount CSDN 2021-02-05
import cv2  
import numpy as np
import matplotlib.pyplot as plt

#读取图像
img = cv2.imread('lena.bmp')

#图像灰度转换
grayImage = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)

#获取图像高度和宽度
height = grayImage.shape[0]
width = grayImage.shape[1]
result = np.zeros((height, width), np.uint8)

#图像灰度对数变换
for i in range(height):
    for j in range(width):
        gray = 42 * np.log(1.0 + grayImage[i,j])
        result[i,j] = np.uint8(gray)

#计算原图的直方图
hist = cv2.calcHist([img], [0], None, [256], [0,255])

#计算灰度变换的直方图
hist_res = cv2.calcHist([result], [0], None, [256], [0,255])

#原始图像
plt.figure(figsize=(8, 6))
plt.subplot(221), plt.imshow(img, 'gray'), plt.title("(a)"), plt.axis('off')

#绘制原始图像直方图
plt.subplot(222), plt.plot(hist), plt.title("(b)"), plt.xlabel("x"), plt.ylabel("y")

#灰度变换后的图像
plt.subplot(223), plt.imshow(result, 'gray'), plt.title("(c)"), plt.axis('off')

#灰度变换图像的直方图
plt.subplot(224), plt.plot(hist_res), plt.title("(d)"), plt.xlabel("x"), plt.ylabel("y")
plt.show()

其运行结果如图16所示,其中(a)和(b)表示原始图像和对应的灰度直方图,(c)和(d)表示灰度对数变换图像及对应的直方图。

在这里插入图片描述


5.图像阈值化处理直方图对比

该算法原型为:

  • threshold(Gray,127,255,cv2.THRESH_BINARY)

当前像素点的灰度值大于thresh阈值时(如127),其像素点的灰度值设定为最大值(如9位灰度值最大为255);否则,像素点的灰度值设置为0。二进制阈值化处理及直方图对比的Python代码如下所示。

#coding:utf-8
#By:Eastmount CSDN 2021-02-05
import cv2  
import numpy as np
import matplotlib.pyplot as plt

#读取图像
img = cv2.imread('lena.bmp')

#图像灰度转换
grayImage = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)

#获取图像高度和宽度
height = grayImage.shape[0]
width = grayImage.shape[1]
result = np.zeros((height, width), np.uint8)

#图像灰度对数变换
for i in range(height):
    for j in range(width):
        gray = 42 * np.log(1.0 + grayImage[i,j])
        result[i,j] = np.uint8(gray)

#计算原图的直方图
hist = cv2.calcHist([img], [0], None, [256], [0,255])

#计算灰度变换的直方图
hist_res = cv2.calcHist([result], [0], None, [256], [0,255])

#原始图像
plt.figure(figsize=(8, 6))
plt.subplot(221), plt.imshow(img, 'gray'), plt.title("(a)"), plt.axis('off')

#绘制原始图像直方图
plt.subplot(222), plt.plot(hist), plt.title("(b)"), plt.xlabel("x"), plt.ylabel("y")

#灰度变换后的图像
plt.subplot(223), plt.imshow(result, 'gray'), plt.title("(c)"), plt.axis('off')

#灰度变换图像的直方图
plt.subplot(224), plt.plot(hist_res), plt.title("(d)"), plt.xlabel("x"), plt.ylabel("y")
plt.show()

其运行结果如图17所示,其中(a)和(b)表示原始图像和对应的灰度直方图,(c)和(d)表示图像阈值化处理及对应的直方图,图17(d)中可以看到,灰度值仅仅分布于0(黑色)和255(白色)两种灰度级。

在这里插入图片描述


六.图像H-S直方图

为了刻画图像中颜色的直观特性,常常需要分析图像的HSV空间下的直方图特性。HSV空间是由色调(Hue)、饱和度(Saturation)、以及亮度(Value)构成,因此在进行直方图计算时,需要先将源RGB图像转化为HSV颜色空间图像,然后将对应的H和S通道进行单元划分,再其二维空间上计算相对应直方图,再计算直方图空间上的最大值并归一化绘制相应的直方图信息,从而形成色调-饱和度直方图(或H-S直方图)。该直方图通常应用在目标检测、特征分析以及目标特征跟踪等场景。

由于H和S分量与人感受颜色的方式是紧密相连,V分量与图像的彩色信息无关,这些特点使得HSV模型非常适合于借助人的视觉系统来感知彩色特性的图像处理算法。下面代码是具体的实现代码,使用matplotlib.pyplot库中的imshow()函数来绘制具有不同颜色映射的2D直方图。

#coding:utf-8
#By:Eastmount CSDN 2021-02-05
import cv2  
import numpy as np
import matplotlib.pyplot as plt

#读取图像
img = cv2.imread('Lena.png')

#转换为RGB图像
img_rgb = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)

#图像HSV转换
hsv = cv2.cvtColor(img,cv2.COLOR_BGR2HSV)

#计算H-S直方图
hist = cv2.calcHist(hsv, [0,1], None, [180,256], [0,180,0,256])

#原始图像
plt.figure(figsize=(8, 6))
plt.subplot(121), plt.imshow(img_rgb, 'gray'), plt.title("(a)"), plt.axis('off')

#绘制H-S直方图
plt.subplot(122), plt.imshow(hist, interpolation='nearest'), plt.title("(b)")
plt.xlabel("x"), plt.ylabel("y")
plt.show()

图18(a)表示原始输入图像,图18(b)是原图像对应的彩色直方图,其中X轴表示饱和度(S),Y轴表示色调(H)。在直方图中,可以看到H=140和S=130附近的一些高值,它对应于艳丽的色调。

在这里插入图片描述


七.直方图判断黑夜白天

接着讲述两个应用直方图的案例,第一个是通过直方图来判断一幅图像是黑夜或白天。常见的方法是通过计算图像的灰度平均值、灰度中值或灰度标准差,再与自定义的阈值进行对比,从而判断是黑夜还是白天。

  • 灰度平均值:该值等于图像中所有像素灰度值之和除以图像的像素个数。
  • 灰度中值:对图像中所有像素灰度值进行排序,然后获取所有像素最中间的值,即为灰度中值。
  • 灰度标准差:又常称均方差,是离均差平方的算术平均数的平方根。标准差能反映一个数据集的离散程度,是总体各单位标准值与其平均数离差平方的算术平均数的平方根。如果一幅图看起来灰蒙蒙的, 那灰度标准差就小;如果一幅图看起来很鲜艳,那对比度就很大,标准差也大。

下面的代码是计算灰度“Lena”图的灰度平均值、灰度中值和灰度标准差。

#coding:utf-8
#By:Eastmount CSDN 2021-02-05
import cv2  
import numpy as np
import matplotlib.pyplot as plt

#函数: 获取图像的灰度平均值
def fun_mean(img, height, width):
    sum_img = 0
    for i in range(height):
        for j in range(width):
            sum_img = sum_img + int(img[i,j])
    mean = sum_img / (height * width)
    return mean

#函数: 获取中位数
def fun_median(data):
    length = len(data)
    data.sort()
    if (length % 2)== 1: 
        z = length // 2
        y = data[z]
    else:
        y = (int(data[length//2]) + int(data[length//2-1])) / 2
    return y

#读取图像
img = cv2.imread('lena.bmp')

#图像灰度转换
grayImage = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)

#获取图像高度和宽度
height = grayImage.shape[0]
width = grayImage.shape[1]

#计算图像的灰度平均值
mean = fun_mean(grayImage, height, width)
print("灰度平均值:", mean)

#计算图像的灰度中位数
value = grayImage.ravel() #获取所有像素值
median = fun_median(value)
print("灰度中值:", median)

#计算图像的灰度标准差
std = np.std(value, ddof = 1)
print("灰度标准差", std)

其运行结果如图9-19所示,图9-19(a)为原始图像,图9-19(b)为处理结果。其灰度平均值为123,灰度中值为129,灰度标准差为48.39。

在这里插入图片描述

下面讲解另一种用来判断图像是白天还是黑夜的方法,其基本步骤如下:

  • 读取原始图像,转换为灰度图,并获取图像的所有像素值;
  • 设置灰度阈值并计算该阈值以下的像素个数。比如像素的阈值设置为50,统计低于50的像素值个数;
  • 设置比例参数,对比该参数与低于该阈值的像素占比,如果低于参数则预测为白天,高于参数则预测为黑夜。比如该参数设置为0.8,像素的灰度值低于阈值50的个数占整幅图像所有像素个数的90%,则认为该图像偏暗,故预测为黑夜;否则预测为白天。

具体实现的代码如下所示。

#encoding:utf-8
#By:Eastmount CSDN 2021-02-05
import cv2  
import numpy as np
import matplotlib.pyplot as plt

#函数: 判断黑夜或白天
def func_judge(img):
    #获取图像高度和宽度
    height = grayImage.shape[0]
    width = grayImage.shape[1]
    piexs_sum = height * width
    dark_sum = 0  #偏暗像素个数
    dark_prop = 0 #偏暗像素所占比例
    
    for i in range(height):
        for j in range(width):
            if img[i, j] < 50: #阈值为50
                dark_sum += 1

    #计算比例
    print(dark_sum)
    print(piexs_sum)
    dark_prop = dark_sum * 1.0 / piexs_sum 
    if dark_prop >=0.8:
        print("This picture is dark!", dark_prop)
    else:
        print("This picture is bright!", dark_prop)
               
#读取图像
img = cv2.imread('day.png')

#转换为RGB图像
img_rgb = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)

#图像灰度转换
grayImage = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)

#计算256灰度级的图像直方图
hist = cv2.calcHist([grayImage], [0], None, [256], [0,255])

#判断黑夜或白天
func_judge(grayImage)

#显示原始图像和绘制的直方图
plt.subplot(121), plt.imshow(img_rgb, 'gray'), plt.axis('off'), plt.title("(a)")
plt.subplot(122), plt.plot(hist, color='r'), plt.xlabel("x"), plt.ylabel("y"), plt.title("(b)")
plt.show()

第一张测试图输出的结果如图20所示,其中图20(a)为原始图像,图20(b)为对应直方图曲线,最终输出结果为“(‘This picture is bright!’, 0.010082704388303882)”,该预测为白天。

在这里插入图片描述

第二张测试图输出的结果如图21所示,其中图21(a)为原始图像,图21(b)为对应直方图曲线,最终输出结果为“(‘This picture is dark!’, 0.8511824175824175)”,该预测为黑夜。

在这里插入图片描述

最后补充一段3D直方图代码,也请同学们下来进行深入的理解及尝试。

# -*- coding: utf-8 -*-
import numpy as np
import cv2 as cv
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
from matplotlib import cm
from matplotlib.ticker import LinearLocator, FormatStrFormatter

#读取图像
img = cv.imread("yxz.png")
img = cv.cvtColor(img,cv.COLOR_BGR2GRAY)
imgd = np.array(img)      #image类转numpy

#准备数据
sp = img.shape
h = int(sp[0])        #图像高度(rows)
w = int(sp[1])        #图像宽度(colums) of image

#绘图初始处理
fig = plt.figure(figsize=(16,12))
ax = fig.gca(projection="3d")

x = np.arange(0, w, 1)
y = np.arange(0, h, 1)
x, y = np.meshgrid(x,y)
z = imgd
surf = ax.plot_surface(x, y, z, cmap=cm.coolwarm)  

#自定义z轴
ax.set_zlim(-10, 255)
ax.zaxis.set_major_locator(LinearLocator(10))   #设置z轴网格线的疏密
#将z的value字符串转为float并保留2位小数
ax.zaxis.set_major_formatter(FormatStrFormatter('%.02f')) 

# 设置坐标轴的label和标题
ax.set_xlabel('x', size=15)
ax.set_ylabel('y', size=15)
ax.set_zlabel('z', size=15)
ax.set_title("surface plot", weight='bold', size=20)

#添加右侧的色卡条
fig.colorbar(surf, shrink=0.6, aspect=8)  
plt.show()

输出结果如下图所示:

在这里插入图片描述


八.总结

写到这里,本文就介绍完毕。这篇文章主要讲解图像直方图相关知识点,包括Matplotlib和OpenCV两种统计及绘制直方图的方法,接着讲解了掩膜直方图和H-S直方图,并结合灰度变换对比了常见算法变换前后的直方图,应用直方图实现黑夜和白天的判断。同时,读者可以尝试之前的文章和直方图进行各种绘制,比如均衡化处理、特效处理等等,下图就是图像均衡化的直方图。

在这里插入图片描述

时光嘀嗒嘀嗒的流失,这是我在CSDN写下的第八篇年终总结,比以往时候来的更早一些。《敏而多思,宁静致远》,仅以此篇纪念这风雨兼程的一年,这感恩的一年。转眼小宝白天了,哈哈~提前祝大家新年快乐!

2020年8月18新开的“娜璋AI安全之家”,主要围绕Python大数据分析、网络空间安全、人工智能、Web渗透及攻防技术进行讲解,同时分享CCF、SCI、南核北核论文的算法实现。娜璋之家会更加系统,并重构作者的所有文章,从零讲解Python和安全,写了近十年文章,真心想把自己所学所感所做分享出来,还请各位多多指教,真诚邀请您的关注!谢谢。

在这里插入图片描述

(By:Eastmount 2021-02-06 晚上12点 http://blog.csdn.net/eastmount/ )


参考文献,在此感谢这些大佬,共勉!

  • [1] 罗子江, 杨秀璋. Python中的图像处理[M]. 2020.
  • [2]冈萨雷斯. 数字图像处理(第3版)[M]. 北京:电子工业出版社, 2013.
  • [3]张恒博, 欧宗瑛. 一种基于色彩和灰度直方图的图像检索方法[J]. 计算机工程, 2004.
  • [4]Eastmount. [数字图像处理] 四.MFC对话框绘制灰度直方图[EB/OL]. (2015-05-31). https://blog.csdn.net/eastmount/article/details/46237463.
  • [5]苗锡奎, 孙劲光, 张语涵. 图像归一化与伪Zernike矩的鲁棒水印算法研究[J]. 计算机应用研究, 2010.
  • [6]阮秋琦. 数字图像处理学(第3版)[M]. 北京:电子工业出版社, 2008.
  • [7]Eastmount. [Python图像处理] 十一.灰度直方图概念及OpenCV绘制直方图[EB/OL]. (2018-11-06). https://blog.csdn.net/Eastmount/article/details/83758402.
  • [8]李立源, 龚坚. 基于二维灰度直方图最佳一维投影的图像分割方法[J]. 自动化学报, 1996.
  • [9]杨秀璋, 颜娜. Python网络数据爬取及分析从入门到精通(分析篇)[M]. 北京:北京航天航空大学出版社, 2018.
  • [10]毛星云, 冷雪飞. OpenCV3编程入门[M]. 北京:电子工业出版社, 2015.
  • [11]深思海数_willschang. Opencv-Python学习笔记七——图像直方图 calcHist,直方图均衡化equalizeHist[EB/OL]. (2018-08-26). https://www.jianshu.com/p/bd12c4273d7d.
  • [12]ZJE_ANDY. python3+opencv 利用灰度直方图来判断图片的亮暗情况[EB/OL]. (2018-06-20). https://blog.csdn.net/u014453898/article/details/80745987.

猜你喜欢

转载自blog.csdn.net/Eastmount/article/details/113700710