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
输入的数据指针,要具备相同的尺寸和数据类型,可以有不同的通道数量,如果要计算n 个Mat数据,可以这么做:Mat images[n];images[0]=imread(),images[1]=imread()……
nimages
就是上面的n,指明传入了多少个Mat数据
channels
通道列表,看看下面这张图,传入了这样2个Mat数据,每个Mat各自包含3个通道,通道的索引依次是0,1,2,3,4,5。const int channels[3]={0,2,4},表示索引序号是0,2,4的三个通道会参与计算。
mask
表示掩码,与images尺寸相同的8bits 数组,传入一个空Mat即可。如果不是空的, 所有掩码非零数据对应的源数据才会参与计算。
hist
表示计算结果,用一个Mat保存便可。1维的结果是一个n行1列的矩阵,2维的结果是个n×m的 矩阵……,具体还要看下面这个维度参数。
dims
结果数据的维数,1维、2维…..32维,要跟其他参数结合起来。
histSize
是一个列表,对应每个维度的数据分组。如果dims=1,结果是1维的,这里可以这么做: const int histSize[1]={32},表示1维被划分成32份。如果dims=2,结果是2维的,可以这么 做:const int histSize[2]={32,64},表示1维被划分成32份,2维被划分成64份。
ranges
是数组的数组,数组中的每个元素是一个指针,这个指针指向一个数组。举例说明如下:
//读取一张图像
Mat image[1];
image[0] = imread("../Image/apple.jpg");
//取图像的0通道数据
const int channels[1] = {0};
//最终结果会有8行1列
const int histSize[1] = {8};
//参与计算的数据范围[0,256),也就是uchar类型的全部数据
//如果范围是[m,n),范围外的数据不会参与计算
const float range[2] = {0, 256};
const float *ranges[1] = {range};
//计算直方图
Mat hist;
calcHist(image, 1, channels, Mat(), hist, 1, histSize, ranges);
//输出直方图
cout << hist << endl;
输出结果是:[7281; 137833; 90340; 25579; 990; 80; 31; 10]
7281+ 137833+90340+25579+990+80+31+ 10=262144=512×512,这些数量加起来就是图片所有的像素。
如果把const float range[2] = {0, 256};改成const float range[2] = {50, 200};
结果变成了[95967; 51565; 39066; 6193; 961; 294; 51; 26]
95967+51565+39066+ 6193+ 961+294+ 51+ 26=194123<512×512,说明{50, 200}范围外的一些像素没参与计算。
Uniform
这个参数表示是否均匀。也就是const float range[2] = {0, 256}这个范围是否被均匀分成8份
(const int histSize[1] = {8})。如果不是均匀的,就应该这样const float range[9] =
{0,50,75,100,120,160,200,220,256},用9个数表示8份数据的边界。
//读取一张图像
Mat image[1];
image[0] = imread("../Image/apple.jpg");
//取图像的0通道数据
const int channels[1] = {0};
//最终结果会有8行1列
const int histSize[1] = {8};
//参与计算的数据范围[0,256),也就是uchar类型的全部数据
//如果范围是[m,n),范围外的数据不会参与计算
const float range[9] = {0,50,75,100,120,160,200,220,256};
const float *ranges[1] = {range};
//计算直方图
Mat hist;
calcHist(image, 1, channels, Mat(), hist, 1, histSize, ranges, false);
//输出直方图
cout << hist << endl;
结果是这样[67991; 113817; 62353; 15946; 1916; 91; 14; 16]
Accumulate
表示是否累积计算,如果true,就不会清空hist,会把上次结果累积到下次。
当Accumulate=flase 时:
//计算直方图
Mat hist;
calcHist(image, 1, channels, Mat(), hist, 1, histSize, ranges, true, false);
//输出直方图
cout << hist << endl;
calcHist(image, 1, channels, Mat(), hist, 1, histSize, ranges, true, false);
//输出直方图
cout << hist << endl;
两次输出结果是:
[7281; 137833; 90340; 25579; 990; 80; 31; 10]
[7281; 137833; 90340; 25579; 990; 80; 31; 10]
当Accumulate=true时:
Accumulate=true
//计算直方图
Mat hist;
calcHist(image, 1, channels, Mat(), hist, 1, histSize, ranges, true, false);
//输出直方图
cout << hist << endl;
calcHist(image, 1, channels, Mat(), hist, 1, histSize, ranges, true, false);
//输出直方图
cout << hist << endl;
结果变成了这样:
[7281; 137833; 90340; 25579; 990; 80; 31; 10]
[14562; 275666; 180680; 51158; 1980; 160; 62; 20]
也就是下次的结果在上次结果基础上累积。
2维数据
上面都是1维数据的情况,看一下2维数据。
//读取一张图像
Mat image[1];
image[0] = imread("../Image/apple.jpg");
//取图像的0、1通道数据
const int channels[2] = {0, 1};
//最终结果会有8行6列
const int histSize[2] = {8, 6};
//参与计算的数据范围[0,256),也就是uchar类型的全部数据
//如果范围是[m,n),范围外的数据不会参与计算
const float range0[2] = {0, 256};
const float range1[2] = {0, 256};
const float *ranges[2] = {range0, range1};
//计算直方图
Mat hist;
calcHist(image, 1, channels, Mat(), hist, 2, histSize, ranges);
//输出直方图
cout << hist << endl;
结果就是这个const int histSize[2] = {8, 6}维度数据:
[1309, 1219, 3754, 762, 237, 0;
484, 30587, 94245, 11228, 997, 292;
0, 2518, 33548, 45328, 8108, 838;
0, 0, 676, 14186, 10072, 645;
0, 0, 0, 399, 155, 436;
0, 0, 0, 0, 26, 54;
0, 0, 0, 0, 3, 28;
0, 0, 0, 0, 0, 10]
这些数据加起来还是521×512=262144,仍然是总的像素数量。这些数据太大了,为了用图像显示,将数据处理一下:
//数据太大了,处理一下
hist += 1;
log(hist, hist);
Mat dst;
normalize(hist, dst, 0, 255, NORM_MINMAX);
Mat convert;
dst.convertTo(convert, CV_8UC1);
//输出直方图
cout << convert << endl;
namedWindow("convert", WINDOW_NORMAL);
imshow("convert", convert);
用图像表示就是上图,channel[0]被划分成8份,channel[1]被划分成6份,然后对image[0]中每个像素进行统计。比如位置(i,j)对应的0通道数据属于上图的第3行,1通道数据属于上图的5通道,那么上图中的格子(3,5)就会计数加1。
处理之后的数据已经无法反应像素的准确数量,但是像素分布状况还是正确的
[160, 158, 183, 148, 122, 0;
138, 230, 255, 208, 154, 126;
0, 174, 232, 239, 200, 150;
0, 0, 145, 213, 205, 144;
0, 0, 0, 133, 112, 135;
0, 0, 0, 0, 73, 89;
0, 0, 0, 0, 31, 75;
0, 0, 0, 0, 0, 53]
上面的数据用3D显示能直观的看出像素分布状况。更高维度的计算结果,不知该如何以直观的方式显示了。