OpenCVRGB直⽅图计算与绘制----calcHist()函数、normalize()函数
1-BGR直⽅图
在OpenCV中,彩⾊图像存储是通过多通道的数组来实现的,对CV_8UF3来⾔,其每个数组通道中的元素可取值为0到255。颜⾊分布直⽅图描述的是不同⾊彩在整幅图像中所占的⽐例,⽽并不关⼼每种⾊彩所处的空间位置。
因此,对彩⾊图像求其直⽅图,可先提取彩⾊图像的各个通道,然后对每个通道进⾏直⽅图计算,最后利⽤图像融合技术合并通道信息,求解出图像颜⾊分布直⽅图。
===========================分割线=========================2-换个⾓度认识图像(直⽅图)
第⼀个就是当我们⾯对图像的时候,我们⾯对的是抽象的矩阵,如下图,下⾯是0-255的灰度图像的表⽰,密密⿇⿇的。
那么我们做的直⽅图,其实就是对这些像素值的统计。例如:⾸先,我们需要把0-255分成 17 个 区域(bin),如下图所⽰:
我们对每个范围中的灰度值进⾏统计排序,做出如下的表格:
我们是以图像的灰度为例⼦说明这个直⽅图,当然直⽅图不仅仅⽤于灰度特种统计排序,还可以⽤于图像的梯度、⽅向等特征。
灰度直⽅图是灰度级的函数,描述图像中该灰度级的像素个数(或该灰度级像素出现的频率):其横坐标是灰度级,纵坐标表⽰图像中该灰度级出现的个数(频率)。
在以上的过程中,我们使⽤到⼀些重要的参数,理解这些参数帮助我们更好的使⽤API函数。
dims:需要统计的特征的数⽬,我们上⾯只统计了 灰度值这个特征,所以, dims =1。
bins:⼀般翻译为箱⼦,看上图,⼀共有16个bins,其实就和我们平时见得简单函数差不多。在图像直⽅图中,你可以把⼀个灰度值设置为⼀个bins,0~255强度的灰度值⼀共就需要256个bins,是不是很简单。
Range:就是范围啦,规定⼀个bins能够达到的最⼤和最⼩的范围。⽐如⼀张图⽚10*10,那么就有100个像素。然后前⾯已经说过,直⽅图是按照亮度统计像素数量,那么范围就是0~100啦。这⾥有⼀个地⽅要说⼀下,刚刚0~100还是对于⽐较⼩的图像,那么对于⽐较⼤的图像1000*1000,那么范围太⼤了。我们统计像素数量的时候肯定没有问题,但是要画直⽅图的时候,难道有⼀个包含100000个像素,岂不是要化的很长?所以,⼀般在画直⽅图的时候,会有⼀个⽐例缩放的过程,⽐如我提前定好我直⽅图最⼤的⾼度只能够是256,那么你就可以⽤(最⼤的⾼度/最⼤的像素量)统计到的像素量来进⾏缩放。这样就简单多了。我这⾥提到的缩放⽅式只是⼀种,你可以随便定义喜欢的缩放⽅式。⼀维直⽅图的结构表⽰为:
再结合上⾯画的⽰意图,应该就很好理解了。
基本的概念其实很简答,想的时候要不要想复杂了,那么基本概念就到这⾥了。===============分割线===============3-相关函数说明
当然了,⾸先介绍的是计算直⽅图的函数了。
直⽅图计算:calcHist()函数
1 void calcHist( const Mat* images, int nimages,
2 const int* channels, InputArray mask,
3 OutputArray hist, int dims, const int* histSize,
4 const float** ranges, bool uniform=true, bool accumulate=false );
参数解释:
参数1:输⼊源图像。注意这⾥的格式是const Mat*,也就是说,你要传⼊⼀个地址,输⼊的数组(图⽚)或者数组集(⼀堆图⽚)需要为相同的深度(CV_8U或CV_32F)和相同的尺⼨。
参数2:int类型的nimages,输⼊数组的个数,也就是第⼀个参数中存放了多少张“图像”,有⼏个原数组。
参数3:const int*类型的channels,⽤来计算直⽅图的channes的数组,需要统计的通道(dim)索引。第⼀个数组通道从0到images[0].channels()-1,⽽第⼆个数组通道从images[0]计算到images[0].channels()+images[1].channels()-1,以此类推。⽐如输⼊是2副图像,第⼀副图像有0,1,2共三个channel,第⼆幅图像只有0⼀个channel,那么输⼊就⼀共有4个channes,如果int channels[3] = {3, 2, 0},那么就表⽰是使⽤第⼆副图像的第⼀个通道和第⼀副图像的第2和第0个通道来计算直⽅图。(这句表⽰没看懂)。
参数4:InputArray类型的mask,可选的操作掩码。如果此掩码不为空,那么它必须为8位(CV_8U)的数组,并且与images[i]有同样⼤⼩的尺⼨,值为1的点将⽤来计算直⽅图。这⾥的⾮零掩码元素⽤于标记出统计直⽅图的数组元素数据。参数5:OutputArray类型的hist,输出的计算出来的直⽅图,⼀个⼆维数组。
参数6:int类型dims,需要计算的直⽅图的维度,必须是正数,且不⼤于CV_MAX_DIMS。(32)
参数7:const int*类型的histSize,存放每个维度的直⽅图尺⼨的数组。简单把直⽅图看作⼀个⼀个的竖条的话,就是每⼀维上竖条的个数。
参数8:const float**类型的ranges,表⽰每⼀个维度数组(第6个参数dims)的每⼀维的边界阵列,可以理解为每⼀维数值的取值范围。⽐如 float rang1[] ={0, 20};float rang2[] = {30, 40}; const float*rangs[] = {rang1, rang2};那么就是对0,20和30,40范围的值进⾏统计。参数9:bool类型的uniform,表⽰直⽅图是否均匀的标识符,即每⼀个竖条的宽度是否相等。有默认值true。
参数10:bool类型的accumulate,累计标识符,有默认值false。若其为true,直⽅图在配置阶段不会被清零。此功能主要是允许从多个阵列中计算单个直⽅图,或者⽤于在特定的时间更新直⽅图。
=====================间隔线======================归⼀化:normalize()函数
功能:缩放和移位数组元素,以便指定的标准(alpha)或最⼩(alpha)和最⼤(beta)数组值获得指定的值。1 void normalize( InputArray src, OutputArray dst, double alpha=1, double beta=0,
2 int norm_type=NORM_L2, int dtype=-1, InputArray mask=noArray());
参数解释:
参数1:InputArray类型的src,输⼊数组(图像)。
参数2:OutputArray类似的dst,输出数组(图像),与输⼊图像类型尺⼨⼀样。参数3:alpha,表⽰range normalization模式的最⼩值。有默认值为1。
参数4:beta,表⽰range normalization模式的最⼤值,不⽤于norm normalization(范数归⼀化)模式。有默认值为0。参数5:normType,表⽰归⼀化的类型,可以有以下的取值:
---------------NORM_MINMAX:数组的数值被平移或缩放到⼀个指定的范围,线性归⼀化,⼀般较常⽤。
---------------NORM_INF:此类型的定义没有查到,根据OpenCV 1的对应项,可能是归⼀化数组的C-范数(绝对值的最⼤值)。---------------NORM_L1:归⼀化数组的L1-范数(绝对值的和)。---------------NORM_L2:归⼀化数组的(欧⼏⾥德)L2-范数。
参数6:有默认值为-1。dtype为负数时,输出数组的type与输⼊数组的type相同;否则,输出数组与输⼊数组只是通道数相同,⽽tpye=CV_MAT_DEPTH(dtype)。
参数7:操作掩膜,⽤于指⽰函数是否仅仅对指定的元素进⾏操作。=====================间隔线======================绘图⽅法详情请看官⽅⽂档: 基本绘图
=======================分割线=====================4-代码演⽰
1 //---------------------------------------------------------- 2 //功能:BGR三通道直⽅图实现
3 //---------------------------------------------------------- 4
5 #include 6 #include 7 #include 8 #include 9 using namespace std;10 using namespace cv;1112 int main()13 {
14 //------------【1】读取源图像并检查图像是否读取成功-------------------------------- 15 Mat srcImage = imread(\"D:/OutPutResult/ImageTest/aurora.jpg\");16 if (!srcImage.data)17 {
18 cout << \"读取图⽚错误,请重新输⼊正确路径!\\n\";19 system(\"pause\");20 return -1;21 }
22 namedWindow(\"【源图像-RGB颜⾊空间】\");23 imshow(\"【源图像-RGB颜⾊空间】\", srcImage);
24 //-------------【2】图像通道的分离,3个通道B、G、R------------------------25 vector rgb_channel;26 split(srcImage, rgb_channel);27 //-------------【3】初始化直⽅图计算参数---------------------------------------28 int bins = 256;
29 int histsize[] = { bins };30 float range[] = { 0, 256 };
31 const float* histRange = { range };
32 Mat b_Hist, g_Hist, r_Hist;
33 //-------------【4】计算各个通道的直⽅图--------------------------------------34 calcHist(&rgb_channel[0], 1, 0, Mat(), b_Hist, 1, histsize, &histRange, true, false); //B-通道35 calcHist(&rgb_channel[1], 1, 0, Mat(), g_Hist, 1, histsize, &histRange, true, false); //G-通道36 calcHist(&rgb_channel[2], 1, 0, Mat(), r_Hist, 1, histsize, &histRange, true, false); //R-通道37 //-------------【5】设置直⽅图绘图参数----------------------------------------------------38 int hist_h = 360;39 int hist_w = bins * 3;
40 int bin_w = cvRound((double)hist_w / bins);
41 Mat histImage(hist_h, hist_w, CV_8UC3, Scalar(0, 0, 0));//创建⼀个⿊底的图像,为了可以显⽰彩⾊,所以该绘制图像是⼀个8位的3通道图像 42 //-------------【6】将直⽅图归⼀化到[0,histImage.rows] ------------------------------------------------------------------------------43 normalize(b_Hist, b_Hist, 0, histImage.rows, NORM_MINMAX, -1, Mat()); //B-通道44 normalize(g_Hist, g_Hist, 0, histImage.rows, NORM_MINMAX, -1, Mat()); //G-通道45 normalize(r_Hist, r_Hist, 0, histImage.rows, NORM_MINMAX, -1, Mat()); //R-通道46 //--------------【7】绘制直⽅图 ----------------------------------------------------------------47 for (int i = 1; i < bins; i++)48 {
49 //绘制B通道的直⽅图信息
50 line(histImage, Point(bin_w*(i - 1), hist_h - cvRound(b_Hist.at(i - 1))), Point(bin_w*(i), hist_h - cvRound(b_Hist.at(i))), Scalar(255, 0, 0), 2, 851 //绘制G通道的直⽅图信息52 line(histImage, Point(bin_w*(i - 1), hist_h - cvRound(g_Hist.at(i - 1))), Point(bin_w*(i), hist_h - cvRound(g_Hist.at(i))), Scalar(0, 255, 0), 2, 853 //绘制R通道的直⽅图信息54 line(histImage, Point(bin_w*(i - 1), hist_h - cvRound(r_Hist.at(i - 1))), Point(bin_w*(i), hist_h - cvRound(r_Hist.at(i))), Scalar(0, 0, 255), 2, 8, 55 }56 namedWindow(\"【RGB直⽅图】\");57 imshow(\"【RGB直⽅图】\", histImage);58 waitKey(0);59 return 0;60 }
=================================分割线======================5-显⽰结果
==============================分割线================================6-程序说明
先把源图像的通道进⾏分离,0-通道为B;1-通道为G;2-通道为R。然后计算每个通道的直⽅图并绘制显⽰处理。