calcHist函数在Opencv中是极难理解的一个函数,一方面是参数说明晦涩难懂,另一方面,说明书给出的实例也不足以令人完全搞清楚该函数的使用方式。最难理解的是第6,7,8个参数dims、histSize和ranges。以前一直都是想当然认为,该函数可以一次统计多张图片每个通道的灰度值数据,实际上calcHist函数一次只能统计一个通道上的直方图。我估计许多同学都犯过和我类似的错误,认为第5个参数hist,可以根据dims设定维度,比如dims=3,则输出的hist的维度就是3,并且会想当然的认为这个三维矩阵会保存三个通道上的直方图统计,悲哀的是,错了。它实际上是在三维坐标上统计的直方图,比如以第0个通道的灰度值有效统计范围(比如0~255)作为纵坐标,类似于笛卡尔坐标的y轴;以第1个通道灰度值统计有效范围作为横坐标,类似于笛卡尔坐标的x轴;以第2个通道的灰度值有效统计范围作为z轴。这三个轴坐标作为统计的依据。比如坐标(6,7,8),表示统计满足“0通道上灰度值为1;1通道上灰度为7;2通道上灰度为8的像素”,在整个图像上的像素个数。

当然,当dims=1时,就好理解了,就是对某一个单一通道上的灰度值直方图统计。

还有很重要一点,那就是,calcHist函数有bug。当图像尺度小于9*12,直方图均匀分布,并且直方条个数histSize为4时,会有统计错误,在后面的实例中我们会看到。

calcHist函数的声明如下:

OpenCV提供了两个重载的calcHist函数,它可以计算一系列阵列的直方图,这些系列通常是图像或像平面。它最多可以同时处理32个图像。

C++: void 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 )

C++: void calcHist(const Mat* images, int nimages, const int* channels, InputArray mask, SparseMat& hist, int dims, const int* histSize, const float** ranges, bool uniform = true, bool accumulate= false )

参数说明:

images –  源图像数组,它们有同样的位深CV_8U或 CV_32F ,同样的尺寸;图像阵列中的每一个图像都可以有任意多个通道

nimages –  源图像的数目。

channels – 维度通道序列,第一幅图像的通道标号从0~image[0].channels( )-1。Image[0]表示图像数组中的第一幅图像,channels()表示该图像的通道数量。同理,图像阵列中的第二幅图像,通道标号从image[0].channerls( )开始,到image[1].channels( )-1为止;第三、四幅图像的通道标号顺序依此类推;也就是说图像阵列中的所有图像的通道根据图像排列顺序,排成一个通道队列。

mask –    可选择的mask。如果该矩阵不空的话,它必须是一个8-bit的矩阵,与images[i]同尺寸。在图像中,只有被mask覆盖的区域的像素才参与直方图统计。如果这个参数想用默认值,输入Mat()就可以了。

hist –      输出直方图, 它是一个稠密或稀疏矩阵,具有dims个维度;

dims –     直方图的维度,一定是正值, CV_MAX_DIMS(当前OpenCV版本是32个);

histSize –   数组,即histSize[i]表示第i个维度上bin的个数;这里的维度可以理解为通道。

ranges –   当uniform=true时,ranges是多个二元数组组成的数组;当uniform=false时,ranges是多元数组组成的数组。当在每个维度(或通道)上每个直方条等宽时,即uniform=true时,灰度值的有效统计范围的下界用L表示,上界用U表示,角标中的i表示第i个维度(或通道),上下界值可以表示为hrange[i]={ L, U}, 在统计时, L和U不在统计范围内。而ranges={ hrange[0], hrange[1], …… , hrange[dims]}。ranges的元素个数由参数dims决定。
其中,L表示在该通道上第0个直方条(bin)的下边界,U表示最后一个直方条histSize[i]-1的上边界。在该维度上直方条的个数为histSize[i],如hrange[0]={ L,
U},hrange[1]={ L, U}, hrange[2]={
L, U}, …… , hrange[dims]={ L, U}。
当uniform=false时,ranges中的每个元素ranges[i]都是一个多元数组,元素个数为histSize[i]+1,它们分别是:L , U=L, U= L,
…… ,U , L , U
。所以,ranges[i]={ L , L, L, …… , L ,U}

uniform –  标识,用于说明直方条bin是否是均匀等宽的。

accumulate – 累积标识。如果该项设置,当直方图重新分配时,直方图在开始清零。这个特征可以使你通过几幅图像,累积计算一个简单的直方图,或者及时更新直方图。

函数calcHist可以计算一幅或多幅图像的直方图。在元组中增量一个直方图的时候,就是从输入图像组中的原位置提取一幅图像,并计算出它的直方图,并添加到元组中。

当参数dims>1时,输出矩阵Hist是二维矩阵。

calcHist示例:单通道灰度图像

int main()
{
Mat hist;
//这是一个9行12列的单通道图像
Mat img=(Mat_<uchar>(,)
<<,,,,,,,,,,,,
,,,,,,,,,,,,
,,,,,,,,,,,,
,,,,,,,,,,,,
,,,,,,,,,,,,
,,,,,,,,,,,,
,,,,,,,,,,,,
,,,,,,,,,,,,
,,,,,,,,,,,);

//灰度值有效范围是1~12,所以设定为hr1[]={1,13};将灰度范围均分为3部分,三个直方条取值范围分别为:第1部分1~4,第2部分5~8,第3部分9~12。
    //因此令histSize[1]={3},这里的3意味着整个图像灰度范围1~12被均分为3部分,分别统计像素个数。
    int histSize[]={};
int channels[]={};
float hr1[]={,};//矩阵b的灰度值有效范围是1<=V<13
const float* ranges[]={hr1}; calcHist(&img,,channels,Mat(),hist,,histSize,ranges,true);
cout<<"img="<<endl<<img<<endl;
cout<<"hist="<<endl<< hist<<endl;
return ;
}

输出结果如下:

Opencv中直方图函数calcHist-LMLPHP

本来一切都很完美,不过可惜,该函数有bug。

calcHist函数的bug

依然以上面的示例为例,当histSize[1]={3};或{6};或{12}都不会有问题。可是当histSize[1]={2};或{4}时,虽然uniform=true,但是在整个图像上灰度范围上,并没有被平均分割为2个或4个灰度直方条进行像素统计。输出结果如下所示:

Opencv中直方图函数calcHist-LMLPHP Opencv中直方图函数calcHist-LMLPHP

经过测试,猜测,大概直方条的宽度含有3的整数倍时,均匀直方统计就会失效。如将上例中的图像改成9*15,灰度取值范围1~15,直方条个数为5,即直方条的宽度为3,则该函数的均匀直方统计就会失效,如下左图。然而,当直方条宽度为5时,则有正确输出结果,如下右图:

Opencv中直方图函数calcHist-LMLPHP  Opencv中直方图函数calcHist-LMLPHP

不过也有解决的办法,就是将灰度值统计范围的左值,也就是L0,减1,即L0=L0-1。还是以上面的例子为例,灰度值有效范围:1~15。令bin=3,但是hr1={0,16},经验证可以得到正确的统计结果,如下图所示:

Opencv中直方图函数calcHist-LMLPHP

其他数据大家可以自行验证,都是正确的。

04-30 01:50