图像直方图是图像处理中非常重要的像素统计结果,反映了图像像素点的概率分布情况。
图像直方图简单来说就是首先统计图像中每个灰度值的个数,然后将图像灰度值作为横轴,以灰度值个数或者灰度值所占比率作为纵轴绘制的统计图。
由于同一物体无论是旋转还是平移在图像中都具有相同的灰度值,因此直方图具有平移不变性、放缩不变性等优点,因此可以用来查看图像整体的变化形式,例如图像是否过暗、图像像素灰度值主要集中在哪些范围等,在特定的条件下也可以利用图像直方图进行图像的识别,例如对数字的识别。
图像直方图广泛应用于空间域处理、特征描述及特征匹配等领域。
在OpenCV 提供了图像直方图的统计函数calcHist(),该函数能够统计出图像中每个灰度值的个数,根据这个函数的统计结果我们可以绘制图像的一维直方图或多维直方图。
函数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 )
hist=cv.calcHist(images, channels, mask, histSize, ranges[, hist[, accumulate]])
各参数意义:
images---待统计直方图的图像数组,是一个Mat类型的数组,数组中所有的图像应具有相同的尺寸和数据类型,并且数据类型只能是CV_8U、CV_16U和CV_32F三种中的一种,但是不同图像的通道数可以不同。
nimages---待统计直方图的图像数量。
channels---需要参与形成多维直方图的通道索引数组。
第一个图像的通道索引从0到images[0].channels()-1;第二个图像通道索引从images[0].channels()到images[0].channels()+ images[1].channels()-1,以此类推。
举个例子,假如有三张图像,都是三通道。那么第一张图像三个通道的索引值分别为0、1、2;第二张图像的三个通道的索引值分别为3、4、5;第三张图像的三个通道的索引值分别为6、7、8。现在如果我们要计算一个二维直方图,参与计算的通道为第一张图像的第一通道和第三张图像的第三通道,那么数组channels的定义和初始化如下:
int channels[2]={0,8}
现在如果我们要计算一个三维直方图,参与计算的通道为第一张图像的第一通道、第二张图像的第二通道、第三张图像的第三通道。那么数组channels的定义和初始化如下:
int channels[3]={0,4,8}
请注意:这里计算的是多维直方图,而不是每个通道单独的一维直方图,如果需要计算每个通道单独的一维直方图,则需要反复调用函数calcHist(),并且每次调用时数组channels的大小都只能为1。
mask---可选的操作掩码矩阵,如果是空矩阵则表示图像中所有位置的像素都计入直方图中,如果矩阵不为空,则必须与输入图像尺寸相同且数据类型为CV_8U。当不为空的时候,那些掩码值不为0的掩码对应的像素被纳入统计范围,而那些掩码值为0的掩码对应的像素则不被纳入统计。
hist---存储直方图统计结果的矩阵,是一个dims维度的数组。问dims是什么东西?即下一个要介绍的参数。
dims---需要计算的直方图的维度,必须是整数,并且不能大于CV_MAX_DIMS,在OpenCV3和OpenCV 4.0中都为32。通常来说数组channels的大小为多少,这个值就写为多少。当然如果你有三个通道,也可以将其值写为1或2,经博主的实测,当写为1的时候计算的是通道索引数组中的第1个通道的直方图,当写为2的时候计算的是通道索引数组中的第1个通道和第2个通道的二维直方图。什么叫二维直方图?请参看链接 https://www.hhai.cc/thread-209-1-1.html
histSize---每个维度的直方图尺寸。这个参数不太好理解,但是对于这个参数的理解决定了是否能正确理解并使用这个函数。这里昊虹君举例子来详解这个参数,具体的举例说明请参看本博文的原文,本博文的原文链接 https://www.hhai.cc/thread-208-1-1.html
uniform--是否对灰度值范围进行均匀划分的标志。当采用均匀分划时,即uniform=ture时,显然我们只需知道每个维度的直方图尺寸和其灰度值范围我们便可以通过均匀划分确定出每个统计子区间的范围,此时ranges只需要两个数就行了,一个数表示灰度值范围的左端,一个数表示灰度值范围的右端。但是如果我们对灰度值不采用均匀划分,即uniform=false,那我们的函数calcHist()便不知道该如何划分灰度值范围的子区间了,这时候我们就需手动指定每个区间的范围,此时就要求ranges[i]中的元素个数为histSize[i]+1。举个例子,假如histSize[0]=3,那么就是说有三个统计区间,三个统计区间需要的边界点个数为3+1=4,即histSize[0]+1=3+1=4。
accumulate—累加标志。关于这个参数,我专门写了博文来介绍它,链接 https://www.hhai.cc/thread-200-1-1.html
示例代码:
在上面对参数histSize介绍的过程中,已经给出了函数calcHist()的Python示例代码,所以接下来就只给C++的示例代码了。
//出处:昊虹AI笔记网(hhai.cc)
//用心记录计算机视觉和AI技术
//博主微信/QQ 2487872782
//QQ群 271891601
//欢迎技术交流与咨询
//OpenCV版本 OpenCV3.0
#include <opencv2/opencv.hpp>
#include <iostream>
using namespace std;
using namespace cv;
int main()
{
cv::Mat ImageGray = (cv::Mat_<uchar>(2, 4) << 0, 1, 2, 3,
4, 5, 6, 7);
// 设置直方图参数
const int channels[1] = { 0 };
const int histSize[1] = { 3 };
float pranges[2] = { 0, 8 };
const float* ranges[1] = { pranges };
cv::MatND hist;
cv::calcHist(&ImageGray, 1, channels, cv::Mat(), hist, 1, histSize, ranges);
std::cout << hist << std::endl;
return 0;
}
运行结果如下:
代码说明:
在上面的代码中,由于histSize被设置为3,所以把0-8划分为三个区间,三个区间分别如下:
第一个区间:[0, 3)
第二个区间:[3, 6)
第三个区间:[6, 8)
再结合矩阵ImageGray中的具体数据值,很容易得到上面的运行结果。