简单的直方图绘制

图像直方图是反映一个图像像素分布的统计表,其实横坐标代表了图像像素的种类,可以是灰度的,也可以是彩色,图像是由像素构成,因为反映像素分布的直方图往往可以作为图像一个很重要的特征。

在opencv中,直方图可以通过函数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 );

其中,参数1:images(arrays): 源输入(图像)数组,必须是相同深度的CV_8U或者CV_32F(即uchar或者float),相同大小,每一个可以是任意通道的;

   参数2:nimages(narrays): 源输入数组中的元素个数(本例只有一幅图像,故为1)

   参数3:channels: 图像的通道,它是一个数组,如果是灰度图像(单通道)则channels[1]={0},如果是彩色图像则channels[3]={0,1,2};如果是只是求彩色图像第2个通道的直方图,则channels[1]={1};

   参数4:mask: 可选掩码,是一个遮罩图像用于确定哪些点参与计算,实际应用中是个很好的参数,默认情况我们都设置为一个空图像,即:Mat()。

   参数5:hist: 输出直方图,是一个稠密或者稀疏的dims维的数组;

   参数6:dims: 直方图的维数,(得到的直方图的维数,灰度图像为1维,彩色图像为3维。)必须为正,并且不大于CV_MAX_DIMS(32)(本例中为1,因为统计的是每幅单通道图像的灰度直方图

   参数7:histSize:用于指出直方图数组每一维的大小的数组,即指出每一维的bin的个数的数组(本例中,只有1维,所以例子1中直接对int取地址作为参数,即该维的bin的个数为256

   参数8: ranges:用于指出直方图每一维的每个bin的上下界范围数组的数组。

   参数9:uniform:判断直方图是均匀与否(均匀:true,非均匀:false)

   参数10:累加标志(单幅图像不进行累计所以例子1中为false)

需要注意的是:

(1)如果channels参数为0,则nimages(narrays):和dims必须相等。

(2)当channels不是0的时候,用于计算直方图的图像是images(arrays)中由channels指定的通道的图像,channels与images(arrays)中的图像的对应关系,如channels的参数说明的,将images(arrays)中的图像从第0幅开始按照通道摊开排列起来,然后channels中的指定的用于计算直方图的就是这些摊开的通道;

例如images(arrays)中只有一幅三通道的图像image,那么nimages(narrays)应该为1,如果是想计算3维直方图【最大也只能是3维的】,想将images的通道2作为第一维,通道0作为第二维,通道1作为第三维,则可以将channels设置为channesl={2,0,1};这样calcHist函数计算时就按照这个顺序来统计直方图。
可以看出channels不为0时narrays可以和dims不相等,只要保证images(arrays)中至少有channels指定的通道就可以。


 下面给出一个详细的例子:

#include "stdafx.h"


#include <opencv2/core/core.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <opencv2/imgproc/imgproc.hpp>
#include <iostream>

#pragma comment(lib, "opencv_core246d.lib")
#pragma comment(lib, "opencv_highgui246d.lib")
#pragma comment(lib, "opencv_imgproc246d.lib")

using namespace cv;
using namespace std;

#define HIST_DIM1

int main( int argc, char** argv )
{
#ifdef HIST_DIM1
//----------------------example 1-------------------------------//
	Mat src, dst;
	/// Load image
	//加载图像
	src = imread("D:\\EgtProject\\image\\test.jpg");

	if( !src.data )
	{ 
		cout<<"load image failed"<<endl;
		return -1; 
	}

	/// Separate the image in 3 places ( R, G and B )
	vector<Mat> rgb_planes;
//#define SHOW_HSV
#ifdef SHOW_HSV
	Mat hsv;
	cvtColor(src, hsv, COLOR_BGR2HSV);
	split(hsv, rgb_planes ); 
#else
	split(src, rgb_planes ); 
#endif
	/// Establish the number of bins 
	int histSize = 256; //用于形成直方图的“收集箱”的个数(级灰度级)

	/// Set the ranges ( for R,G,B) )
	float range[] = { 0, 255 } ; 
	const float* histRange = { range };

	bool uniform = true; bool accumulate = false;

	Mat r_hist, g_hist, b_hist;

	/// Compute the histograms:
	calcHist( &rgb_planes[2], 1, 0, Mat(), r_hist, 1, &histSize, &histRange, uniform, accumulate );
	calcHist( &rgb_planes[1], 1, 0, Mat(), g_hist, 1, &histSize, &histRange, uniform, accumulate );
	calcHist( &rgb_planes[0], 1, 0, Mat(), b_hist, 1, &histSize, &histRange, uniform, accumulate );
	//r_hist中的data指针指向的数据,是每一个收集箱中,对应像素值得个数,即每个像素都有属于
	//它的一个收集箱
	for( int i = 1; i < histSize; i++ )
	{ 
		int rt=r_hist.at<float>(i-1);
		//r_hist.data[256]={14094,1373,1474,1188,...,722,825,1012}
		cout << "ii=" <<i<<"->"<<rt<<endl;
	}

	// Draw the histograms for R, G and B
	int hist_w = 600; int hist_h = 400;
	int bin_w = cvRound( (double) hist_w/histSize ); //每个收集箱的宽度

	Mat rgb_hist[3];
	for(int i=0; i<3; ++i)
	{
		rgb_hist[i] = Mat(hist_h, hist_w, CV_8UC3, Scalar::all(128)); //创建3个矩阵由于存储图像数据,背景填充灰色
	}

	Mat rgb_hist1[3];
	for(int i=0; i<3; ++i)
	{
		rgb_hist1[i] = Mat(hist_h, hist_w, CV_8UC3, Scalar::all(128)); //创建3个矩阵由于存储图像数据,背景填充灰色
	}
	
	Mat histImage(hist_h, hist_w, CV_8UC3, Scalar(128,128,128));     //创建矩阵用于存储图像数据,背景填充灰色

	/// Normalize the result to [ 0, histImage.rows-10]
	//将直方图归一化到了0~histImage.row-10之间,而histImage.row也即是上面的hist_h,即400,
	//后面画出的直方图线的原点是在左下方,而图像的原点是在左上方,所以应该是做一个减法,至于cvRound就是浮点转换为整数。
	//由由于收集箱中的数值比较到,为了绘图的方便,我们将收集箱中的数值归一化到[0,histImage-10]之间
	normalize(r_hist, r_hist, 0, histImage.rows-10, NORM_MINMAX);
	normalize(g_hist, g_hist, 0, histImage.rows-10, NORM_MINMAX);
	normalize(b_hist, b_hist, 0, histImage.rows-10, NORM_MINMAX);

	/// Draw for each channel 
	//绘制1条竖线
	for( int i = 1; i < histSize; i++ )
	{ 
		//遍历r_hist的data指向的区域中的每一个数据
		int rt=r_hist.at<float>(i-1);	//每个收集箱上面的值
		//由于图像的原点在左上角,故需要hist_h-cvRound(rt)
		line( histImage, Point( bin_w*(i-1), hist_h-cvRound(rt) ) , //r_hist[0-255]:存储的数据为,如:0对应3个点,1对应30个点,...,255对应20个点
			Point( bin_w*(i), hist_h-cvRound(r_hist.at<float>(i)) ),                   //r_hist.data={3,30,...,20}
			Scalar( 0, 0, 255), 4); //BGR

		line( histImage, Point( bin_w*(i-1), hist_h - cvRound(g_hist.at<float>(i-1)) ) , 
			Point( bin_w*(i), hist_h-cvRound(g_hist.at<float>(i)) ), 
			Scalar( 0, 255, 0), 4); 

		line( histImage, Point( bin_w*(i-1), hist_h - cvRound(b_hist.at<float>(i-1)) ) , 
			Point( bin_w*(i), hist_h-cvRound(b_hist.at<float>(i)) ), 
			Scalar( 255, 0, 0), 4);       
	}
		//将计数绘制到图像上(BGR)
      putText(histImage, "R", Point(300+10, 30), FONT_HERSHEY_SIMPLEX, 1, Scalar( 0, 0, 255), 3);  
	  putText(histImage, "G", Point(300-20, 250), FONT_HERSHEY_SIMPLEX, 1, Scalar(0, 255, 0), 3);  
	  putText(histImage, "B", Point(300+10, 300), FONT_HERSHEY_SIMPLEX, 1, Scalar( 255, 0, 0), 3);  

	//绘制一个矩形(条形)
	for (int j=0; j<histSize; ++j)
	{
		int val = saturate_cast<int>(r_hist.at<float>(j)); //每个收集箱(bin)中的数值
		//rgb_hist[0].row是rgb_hist[0]这个图像的高
		//由于图像的原点在左上角,因此rgb_hist[0].rows-val
		//点 Point(j*2+10, rgb_hist[0].rows):矩形的左下角,点Point((j+1)*2+10, rgb_hist[0].rows-val):矩形的右上角
		rectangle(rgb_hist[0], Point(j*2+10, rgb_hist[0].rows), Point((j+1)*2+10, rgb_hist[0].rows-val), Scalar(0,0,255),1,8);
		
		val = saturate_cast<int>(g_hist.at<float>(j));
		rectangle(rgb_hist[1], Point(j*2+10, rgb_hist[1].rows), Point((j+1)*2+10, rgb_hist[1].rows-val), Scalar(0,255,0),1,8);
		
		val = saturate_cast<int>(b_hist.at<float>(j));
		rectangle(rgb_hist[2], Point(j*2+10, rgb_hist[2].rows), Point((j+1)*2+10, rgb_hist[2].rows-val), Scalar(255,0,0),1,8);

		/**********直方图绘制倒立了-因为图像的原点从左上角开始的***********/

		val = saturate_cast<int>(r_hist.at<float>(j)); //每个收集箱(bin)中的数值
		//rgb_hist[0].row是rgb_hist[0]这个图像的高
		//由于图像的原点在左上角,因此rgb_hist[0].rows-val
		rectangle(rgb_hist1[0], Point(j*2+10, 0), Point((j+1)*2+10, val), Scalar(0,0,255),1,8);
		
		val = saturate_cast<int>(g_hist.at<float>(j));
		rectangle(rgb_hist1[1], Point(j*2+10, 0), Point((j+1)*2+10,val), Scalar(0,255,0),1,8);
		
		val = saturate_cast<int>(b_hist.at<float>(j));
		rectangle(rgb_hist1[2], Point(j*2+10, 0), Point((j+1)*2+10, val), Scalar(255,0,0),1,8);
	}

	/// Display 
	namedWindow("histImage", CV_WINDOW_AUTOSIZE );
	namedWindow("wnd");
	imshow("histImage", histImage );
	imshow("wnd", src);

	imshow("R", rgb_hist[0]);
	imshow("G", rgb_hist[1]);
	imshow("B", rgb_hist[2]);

	imshow("R1", rgb_hist1[0]);
	imshow("G1", rgb_hist1[1]);
	imshow("B1", rgb_hist1[2]);

#else
//----------------------example 2-------------------------------//
	Mat src, hsv;
	if(!(src=imread("D:\\EgtProject\\image\\test.jpg")).data)
		return -1;
	cvtColor(src, hsv, CV_BGR2HSV);
	// Quantize the hue to 30 levels
	// and the saturation to 32 levels
	int hbins = 60, sbins = 64;
	int histSize[] = {hbins, sbins};
	// hue varies from 0 to 179, see cvtColor
	float hranges[] = { 0, 180 };
	// saturation varies from 0 (black-gray-white) to
	// 255 (pure spectrum color)
	float sranges[] = { 0, 256};
	const float*ranges[] = { hranges, sranges };
	MatND hist;
	// we compute the histogram from the 0-th and 1-st channels
	int channels[] = {0, 1};
	calcHist( &hsv, 1, channels, Mat(),hist, 2, histSize, ranges,true, false );
	double maxVal=0;
	minMaxLoc(hist, 0, &maxVal, 0, 0);
	int scale = 8;
	Mat histImg = Mat::zeros(sbins*scale, hbins*scale, CV_8UC3);
	for( int h = 0; h < hbins; h++ )
	{
		for( int s = 0; s < sbins; s++ )
		{
			float binVal = hist.at<float>(h, s);
			int intensity = cvRound(binVal*255/maxVal);
			rectangle( histImg, Point(h*scale, s*scale),Point((h+1)*scale-1, (s+1)*scale-1), Scalar::all(intensity), CV_FILLED);
		}
	}
	namedWindow( "Source", 1 );
	imshow( "Source", src );
	namedWindow( "H-S Histogram", 1 );
	imshow( "H-S Histogram", histImg );
#endif	
//-------------------------------------------------------------------------//	
	waitKey(0);
	destroyAllWindows();
	return 0;
}

运行结果:


图1 原始图



图2 RGB三个通道的直方图曲线


图3 R通道的直方图(正立)


图4 R通道的直方图(倒立)


图5 G通道的直方图(正立)


图 6 G通道的直方图(倒立)


图 7 B通道的直方图(正立)


图8 B 通道的直方图(倒立)


分析:

    (1)出现倒立的原因是因为,opencv中图像默认是把图像的左上角作为原点。

    (2)在opencv中RGB图像中的排列顺序是:BGR,因此,在设置颜色的时候也需要注意,如设置颜色值为红色, 是使用Scalar( 0, 0, 255)。

    (3)在直方图中,横坐标是收集箱(bins)的个数,纵坐标是相应的频数,即与收集箱区域的值对应的像素个数的统计。

    (4)对于函数calcHist,它的输出变量hist,是一个Mat类型,这个变量对应的data指针是指向,收集箱(bins)的频数,如在上面例子中data指向序列{14094,1373,1474,1188,...,722,825,1012},其中14094是第1个bins中的频数,1373是第2个bin中的频数,...,1012是第256个bins中的频数(本例中收集箱(bins)的个数是256个)。

下面我们通过matlab,来测试一下,我们只打算输出R分量,同样我们打算设置收集箱(bins)的个数为256,一般情况下, bins的个数或者说条状的个数,可以认为是灰度级的个数,比如收集箱(bins)的个数为128,数据的变化范围是0-255,那么我们可以在0-255这256个数据区间中,分割128个区间,然后统计像素掉落在每个区间个数,即得到每个区间的频数。这128个区间,在数学上,可以这样分,[-0.5,1.5],[1.5,3.5],...,[253.5,255.5],也就是所分区间的最大值要比255稍微大,区间的最小值比0要稍微小,在这里,我们去了-0.5和255.5,下面是测试结果:


图9 matlab中测试的R分量的柱状图



图10 matlab中R分量的曲线图直方图


代码:

close all
clc,clear
img=imread('test.jpg');
r=img(:,:,1); %R分量
g=img(:,:,2); %G分量
b=img(:,:,3); %B分量


%nbins=128;    %区间(收集箱,即灰度级的个数)的个数


%[n,xout]=imhist(r,nbins); %n:是一个nbins*1大小的向量,记录了每个bin内的像素的个数
                          %xout是区间分界值
                          
h=imhist(r,256);
h1=h(1:1:256);
horz = 1:1:256;
bar(horz,h1);
axis([0,255,0 2600])
ylabel('频数');
xlabel('灰度级')
title('R分量')
figure
plot(1:256,h)
ylabel('频数');
xlabel('灰度级')
title('R分量')
axis([0,255,0 2600])


参考文献: 

1.OpenCV深入学习(5)--直方图之calcHist使用

2.OpenCV深入学习(6)--直方图之calcHist使用(补)


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值