在前面几篇直方图相关的文章中介绍了直方图均衡、直方图匹配、局部直方图处理、基于直方图统计信息进行图像增强处理等图像处理与直方图相关的内容 具体相关内容请参考《《数字图像处理》第三章学习总结感悟2 直方图处理 https://blog.csdn.net/LaoYuanPython/article/details/119856466》 在《数字图像处理 图像直方图基础知识介绍 https://blog.csdn.net/LaoYuanPython/article/details/120477375》补充介绍了直方图的基础知识 本节将介绍利用OpenCV-Python和MATLAB进行直方图生成和展现相关实现。
二、OpenCV图像直方图的计算在OpenCV中 图像的直方图计算使用函数calcHist 在C语言中 该函数有多种重载形式 参数也比较多 而在Python中则不一样。
下面是C 版本的calcHist一个重载函数
void cv::calcHist ( const Mat * images, int nimages, const int * channels, InputArray mask, OutputArray hist, int dims, const int * histSize, const float ** ranges, bool uniform true, bool accumulate false
相关介绍网上到处都是 在此就不展开说 而OpenCV-Python关于该函数的资料却很少见 有的也基本是官方文档的简单翻译 下面就calcHist的Python版本介绍一下。
Python中因为不支持函数的多态形式 因此只有一个函数 其定义如下
hist cv2.calcHist(images, channels, mask, histSize, ranges[, hist[, accumulate]])
这些参数说明如下
images 输入图像的列表 如果有多幅图像 则是列表的多个元素 但根据老猿测试 4.3的版本虽然允许输入多个图像 但处理时只处理一幅图像才能正常处理 相关情况请见后面说明channels 需要处理通道的列表 需要统计的通道索引 维度通道序列 第一幅图像的通道标号从0~image[0].channels( )-1。Image[0]表示图像数组中的第一幅图像 channels()表示该图像的通道数量。同理 图像阵列中的第二幅图像 通道标号从image[0].channerls( )开始 到image[0].channerls image[1].channels( )-1为止 第三、四幅图像的通道标号顺序依此类推 也就是说图像阵列中的所有图像的通道根据图像排列顺序 排成一个通道队列 根据老猿测试 这个多通道支持也基本不可用mask 图像掩膜 是和图像大小相同的8bit灰度图像矩阵 如果只计算输入图像部分区域的直方图时 可通过掩膜来设置参与计算的有效区域 掩膜有效区域的像素值非0 histSize 每个图像维度参与直方图计算的直方图组数ranges 为一个列表 表示参与直方图计算的每个维度的数值范围 即横坐标的最小值和最大值 注意最小值是参与计算的最小值 最大值本身不参与计算 即这个最小值和最大值构成的区间是个半闭半开区间 如要求灰度图的所有像素值都参与计算 则值为[0,256] 如果要求计算灰度值在100-250的像素值 则应该设置为[100,251] 如果是多个维度 则每个维度的计算范围最小、最大值分别追加到列表中 如有2个维度 都是0到256 则表示为 [0,256,0,256]hist 可选输出结果直方图阵列accumulate 是否累积计算标记 如果设置为True 则直方图在计算时不会将结果清除 此功能能够从多组图像中叠加计算出一个直方图 经老猿验证 OpenCV4.3-Python中好像不起作用可以看到相比C 版本 python版本变化如下
参数少了nimages 因为images就是一个列表 无需再传图像数量参数少了dims 因为数字图像一般就一个物理量-灰度 因此在Python中dims默认为1 不能传值参数少了uniform 这表示是否为均匀直方图 Python中强制为均匀直方图range参数比C 用法中少了非均匀直方图的表示法 三、直方图计算简单案例下面构造一个简单的图像数组 来进行直方图的计算 看看计算出来的直方图数据
import cv2 def test(): img np.array([[[1,2,3],[1,2,3],[1,2,3]],[[4,5,6],[4,5,6],[4,5,6]],[[7,8,9],[7,8,9],[7,8,9]]],dtype np.uint8) B, G, R cv2.split(img) hb cv2.calcHist([B], [0], None, [9], [1,10],accumulate True) hg cv2.calcHist([G], [0], None, [9], [3, 8],accumulate True) hr cv2.calcHist([R], [0], None, [9], [1, 5], accumulate True) print( 蓝色通道的直方图矩阵如下 ) print(hb.shape,hb.reshape(hb.shape[0])) print( 绿色通道的直方图矩阵如下 ) print(hg.shape, hg.reshape(hg.shape[0])) print( 红色通道的直方图矩阵如下 ) print(hr.shape, hr.reshape(hr.shape[0])) test()
上段程序首先构建了3*3的BGR格式的图像矩阵 像素值每个通道由1-9中的数字构成。使用cv2.split做了各像素的三通道拆分 拆分后三通道的像素值如下
B通道
G通道
R通道
然后分别计算三个通道的直方图数据 注意B通道指定的范围是[1,10] G是[3,8] R是[1,5] 三者中绿色通道指定组数是6 其他2个通道指定的组数是9。我们来看看最终输出结果
蓝色通道的直方图矩阵如下 (9, 1) [3. 0. 0. 3. 0. 0. 3. 0. 0.] 绿色通道的直方图矩阵如下 (6, 1) [0. 0. 3. 0. 0. 0.] 红色通道的直方图矩阵如下 (9, 1) [0. 0. 0. 0. 3. 0. 0. 0. 0.]
可以看到直方图的矩阵是一个组数×1的二阶数组。
蓝色通道中1、 4、 7三个数字各自出现了三次 组设设置ranges设置为【1-10】 组数设置为9 由于上限10不参与计算 因此一个数字就是一个组 1、 4、 7三个数字分别在0组、3组和6组 因此直方图数组展开后的最终结果是[3. 0. 0. 3. 0. 0. 3. 0. 0.]。
绿色通道中2、 5、 8三个数字各自出现了三次 组设设置ranges设置为【3-8】 组数设置为6 由于上限8不参与计算 实际参与直方图计算的数字是3、4、5、6、7共5个 因此2、 5、 8三个数字实际上只有5能纳入直方图统计中 分为6组后 每组的宽度为(8-3)/6 0.833 5在第3组 组号2 因此数组展开后的最终结果是[0. 0. 3. 0. 0. 0.]。
类似地 红色通道中3、 6、 9三个数字各自出现了三次 组设设置ranges设置为【1-5】 组数设置为9 由于上限5不参与计算 实际参与直方图计算的数字是1、2、3、4共5个 因此3、 6、 9三个数字实际上只有3能纳入直方图统计中 分为9组后 每组的宽度为(5-1)/9 0.444 3在第5组 组号4 因此数组展开后的最终结果是[0. 0. 0. 0. 3. 0. 0. 0. 0.]。
**从上面的结果可以看到 参数accumulate True没有起到作用。**不知道老猿的测试方法是否存在问题。
四、关于多图像直方图的计算从前面calcHist函数的参数介绍时 可以知道从参数上OpenCV-Python的calcHist函数理论上可以支持多图像直方图的一起计算。但前面也说了 老猿测试没有通过。
下面就介绍一下测试代码
import cv2 import numpy as np from collections import Counter def test(): img np.array([[[1,2,3],[1,2,3],[1,2,3]],[[4,5,6],[4,5,6],[4,5,6]],[[7,8,9],[7,8,9],[7,8,9]]],dtype np.uint8) B, G, R cv2.split(img) #cv2.calcHist(images, channels, mask, histSize, ranges[, hist[, accumulate]]) h1 cv2.calcHist([B, G,R], [0,1,2], None, [9,9,9], [1, 10,1,10,1,10] ) print( 直方图矩阵h1的维数及统计数据为 ) print(h1.shape,Counter(h1.reshape(h1.shape[0]*h1.shape[1]*h1.shape[2]))) h2 cv2.calcHist([B, G, R], [0, 1, 2], None, [9, 1, 1], [1, 10, 1, 10, 1, 10]) print( 直方图矩阵h2的维数为 ) print(h2.shape,h2.reshape(h2.shape[0])) h3 cv2.calcHist([B, G, R], [0, 1, 2], None, [9], [1, 10, 1, 10, 1, 10]) print( 直方图矩阵h3的维数为 ) print(h3.shape, h3.reshape(h3.shape[0])) test()
上述测试代码中 将数字图像的三个通道数据分离后 合在一起进行直方图统计。在传参时 多个图像放到参数images的列表中 要参与直方图计算的channels也是顺序在channels参数列表中 histSize分成三种情况传参 一是[9,9,9] [9,1,1] [9]。
我们先看执行结果
直方图矩阵h1的维数及统计数据为
(9, 9, 9) Counter({0.0: 726, 3.0: 3})
直方图矩阵h2的维数为
(9, 1, 1) [3. 0. 0. 3. 0. 0. 3. 0. 0.]
Traceback (most recent call last):
File F:/study/python/project/OpenCV/cvtest/cvtest.py , line 163, in module
test()
File F:/study/python/project/OpenCV/cvtest/cvtest.py , line 155, in test
h3 cv2.calcHist([B, G, R], [0, 1, 2], None, [9], [1, 10, 1, 10, 1, 10])
cv2.error: OpenCV(4.3.0) C:projectsopencv-pythonopencvmodulesimgprocsrchistogram.cpp:1292: error: (-215:Assertion failed) rsz dims*2 || (rsz 0 images.depth(0) CV_8U) in function cv::calcHist
从上面结果可以看出来
channels参数为[9,9,9]、[9,1,1]时能正常运行 产生的直方图矩阵是三维矩阵而不是两维矩阵从第一个直方图的统计数据可以看到 直方图中数据中只出现了3个3 通过DEBUG跟踪可以看到这三个3的位置分别位于直方图结果数组H1的H1[0][1][2]、H1[3][4][5]、H1[6][7][8]三个位置 这个老猿没有想明白从第二个直方图数据也是三个三 从第二图数值3的位置可以看出 分别在1、 4、 7三个位置出现了3 与单独的蓝色通道出现的直方图结果一样第三个直方图计算系统报错 是因为channels参数这种传值方式不正确 用[9,1]测试也同样报错因此从上述测试代码可以看出 老猿在OpenCV4.3-Python多图像的测试结果与官方文档差别很多 基本上可以说明在OpenCV4.3-Python中多图像同时计算直方图不可行。
五、关于彩色图像的直方图计算上面都是用的灰度图进行的直方图计算 按照官方文档说明彩色图像也可以计算 我们来测试一下
import cv2 import numpy as np def test(): img np.array([[[1,2,3],[1,2,3],[1,2,3]],[[4,5,6],[4,5,6],[4,5,6]],[[7,8,9],[7,8,9],[7,8,9]]],dtype np.uint8) #cv2.calcHist(images, channels, mask, histSize, ranges[, hist[, accumulate]]) h1 cv2.calcHist([img], [0], None, [9], [1, 10] ) print( 直方图矩阵h1的维数和数据为 ) print(h1.shape,h1.reshape(h1.shape[0])) h2 cv2.calcHist([img], [0, 1], None, [9,1], [1, 10, 1, 10],accumulate True) print( 直方图矩阵h2的维数为 ) print(h2.shape, h2.reshape(h2.shape[0])) test()
看输出结果
直方图矩阵h1的维数和数据为 (9, 1) [3. 0. 0. 3. 0. 0. 3. 0. 0.] 直方图矩阵h2的维数为 (9, 1) [3. 0. 0. 3. 0. 0. 3. 0. 0.]
从上面结果可以看到 确实可以使用彩色图像计算多通道的直方图 不过其计算结果与第一个通道的直方图计算结果相同。channels参数改成[9,9]之后 还是只有三个结果3 位置分别为h2[0][1]、h2[3][4]、h2[6][7]。因此这种多通道参与直方图计算实际上也没有用。
六、装载实际图像计算直方图的案例下面是加载一张图像的灰度图 进行直方图计算并展示源图、直方图曲线以及直方图的示例代码
import cv2,matplotlib from moviepy.editor import * import numpy as np import matplotlib.pyplot as plt def test(): img cv2.imread(r f:picfans.jpg ,cv2.IMREAD_GRAYSCALE) #cv2.calcHist(images, channels, mask, histSize, ranges[, hist[, accumulate]]) h cv2.calcHist([img], [0], None, [8], [0,256]) plt.rcParams[ font.sans-serif ] [ SimHei ] # 用来正常显示中文标签 w3 plt.subplot(212) w1 plt.subplot(221) w2 plt.subplot(222) plt.sca(w1) plt.imshow(img, gray ) plt.title( 源图像 ) plt.sca(w2) plt.plot(h,color b ) plt.title( 直方图函数曲线 ) plt.sca(w3) w3.spines[ right ].set_color( none ) w3.spines[ top ].set_color( none ) w3.spines[ bottom ].set_position(( axes , 0.0296)) w3.spines[ left ].set_position(( axes , 0.0080)) w3.margins(x 0) w3.set_xlim(0,276) x range(0,277,16) plt.xticks(x,rotation 70) plt.hist(img.ravel(),bins 16,density None,facecolor b ,edgecolor r ,alpha 1,histtype bar ) plt.title( Hist ) plt.ylabel( 像素数 ) plt.xlabel( 灰度级 ) ymin,ymax w3.get_ylim() xmin,xmax w3.get_xlim() print(xmin,xmax,ymin,ymax) annotation1 plt.annotate( , [xmax*0.996,0], [xmax*0.996,0]) annotation2 plt.annotate( ^ , [0,ymax*0.93], [ 0,ymax*0.93]) plt.show()



