opencv直方图该怎么画

图像直方图是反映图像中像素分布特性的统计表,一般显示如下:


其中横坐标代表的是图像像素的种类,或者说是灰度级,纵坐标代表的是每一级灰度下像素数或者该灰度级下像素数在所有图像总像素数总所占的百分比。

直方图反映了图像像素的整体分布,是图像的一个很重要的特征,直方图处理也是很多空间域图像处理的基础,在特征提取,图像增强,图像匹配等方面都占有一席之地。

直观上看,若直方图的分量主要集中在左侧低灰度级的区域,说明该图像整体灰度偏低,欠曝或者环境昏暗就有可能造成灰度偏低; 

若直方图的分量主要集中在右侧高灰度级的区域,说明图像整体灰度偏高,过曝或环境光过于明亮就有可能造成灰度偏高;

若直方图的分量在各个灰度级分布均匀,占据了整个灰度级,则说明该幅图像对比度明显,一般情况下,具有这种直方图分布的图像的灰度细节比较丰富,图像质量较好。

直方图并不包含位置信息,相同的图像一定对应同一个直方图,而相同的直方图有可能所对应的图像千差万别,一个明显的例子是一幅选择180°的图像跟原图像的直方图分布是一致的,但是图像在表现上已经把图像倒置了,是完全不同的两幅图像。


opencv中计算直方图使用的是calcHist()函数,函数原型:

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 );


第一个参数:const Mat类型的images指针,可以是单幅图像或数组;
第二个参数:int型的nimages,是number of images的缩写,表示 输入数组的个数,单通道值为1;
第三个参数:const int型的channesl,需要统计的通道 (dim)索引数,单通道灰度图像为0;
第四个参数:InputArray类型的Mat()掩码,传入Mat()表示未定义,不使用掩码;
第五个参数:OutputArray类型的r_hist,储存直方图的矩阵;
第六个参数:int型的dims,直方图维数;
第七个参数:const int型的histSize指针,表示直方图的bin数目,即灰度级;
第八个参数:const float型的histRange指针,表示每个维度的取值范围;
第九和第十个参数:uniform 和 accumulate,bin大小相同,清楚直方图痕迹,分别是指为true和false


直方图opencv calcHist函数实现:


#include "core/core.hpp"
#include "highgui/highgui.hpp"
#include "imgproc/imgproc.hpp"

using namespace cv;

int main(int argc,char *argv[])
{
	Mat image,imageGray,imageHist,imageNormalize;
	image=imread(argv[1]);
	if(!image.data)
	{
		return -1;
	}
	// RGB三通道图像转化成单通道灰度图像
	// 也可以直接在imread函数里第二个参数设为0获取灰度图像
	cvtColor(image,imageGray,CV_RGB2GRAY);  
	const int histSize=255;   //定义灰度级数量
	float histR[]={0,255};   //定义每个灰度级下取值范围
	const float *histRange=histR;

	//计算直方图
	calcHist(&imageGray,1,0,Mat(),imageHist,1,&histSize,&histRange,true,false);

	//直方图归一化到范围[0,histSize]
	normalize(imageHist,imageNormalize,0,histSize,NORM_MINMAX,-1,Mat());

	//创建直方图画布
	Mat imageShowHist(histSize,histR[1],CV_8UC3,Scalar(0,0,0));

	//分别画出每个灰度级下的直方图分布
	for(int i=0;i<histSize;i++)
	{
		line(imageShowHist,Point(i,histR[1]),Point(i,histR[1]-cvRound(imageNormalize.at<float>(i))),Scalar(0,0,255),1,8,0);
	}
	imshow("Moon",image); imshow("MoonGray",imageGray);
	imshow("Hist",imageShowHist);
	waitKey();
	return 0;
}


运行效果:
           

从直方图上特性上分析,灰度级基本上在每个数量级上都有分布,对比度和细节较为明显,但图像整体稍微偏暗,对比灰度图像,可以印证这些特性。


值得探讨的是我觉得在opencv文档里的教程中对直方图的计算应该有误,原文链接:

Opencv文档教程之直方图计算

例程代码照抄如下:

#include "opencv2/highgui/highgui.hpp"
#include "opencv2/imgproc/imgproc.hpp"
#include <iostream>
#include <stdio.h>

using namespace std;
using namespace cv;

/** @函数 main */
int main( int argc, char** argv )
{
	Mat src, dst;

	/// 装载图像
	src = imread( argv[1], 1 );

	if( !src.data )
	{ return -1; }

	/// 分割成3个单通道图像 ( R, G 和 B )
	vector<Mat> rgb_planes;
	split( src, rgb_planes );

	/// 设定bin数目
	int histSize = 255;

	/// 设定取值范围 ( R,G,B) )
	float range[] = { 0, 255 } ;
	const float* histRange = { range };

	bool uniform = true; bool accumulate = false;

	Mat r_hist, g_hist, b_hist;

	/// 计算直方图:
	calcHist( &rgb_planes[0], 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[2], 1, 0, Mat(), b_hist, 1, &histSize, &histRange, uniform, accumulate );

	// 创建直方图画布
	int hist_w = 400; int hist_h = 400;
	<span style="color:#ff0000;">int bin_w = cvRound( (double) hist_w/histSize );</span>

	Mat histImage( hist_w, hist_h, CV_8UC3, Scalar( 0,0,0) );

	/// 将直方图归一化到范围 [ 0, histImage.rows ]
	normalize(r_hist, r_hist, 0, histImage.rows, NORM_MINMAX, -1, Mat() );
	normalize(g_hist, g_hist, 0, histImage.rows, NORM_MINMAX, -1, Mat() );
	normalize(b_hist, b_hist, 0, histImage.rows, NORM_MINMAX, -1, Mat() );

	/// 在直方图画布上画出直方图
	for( int i = 1; i < histSize; i++ )
	{
		// 加入了三个if语句,为了比对直方图,不是例程程序所有
		if(i==180)
		{
			imshow("180",histImage);
		}
		if(i==200)
		{
			imshow("199",histImage);
		}
		if(i==210)
		{
			imshow("210",histImage);
		}

		line( histImage, <span style="color:#ff0000;">Point( bin_w*(i-1), hist_h - cvRound(r_hist.at<float>(i-1)) )</span> ,
			Point( bin_w*(i), hist_h - cvRound(r_hist.at<float>(i)) ),
			Scalar( 0, 0, 255), 2, 8, 0  );
		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), 2, 8, 0  );
		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), 2, 8, 0  );
	}

	/// 显示直方图
	namedWindow("calcHist Demo", CV_WINDOW_AUTOSIZE );
	imshow("calcHist Demo", histImage );

	waitKey(0);

	return 0;

}


例程中所设直方图灰度级为255,直方图画布cols为400, int bin_w = cvRound( (double) hist_w/histSize )计算出来值为2;

这里会有问题:当计算200灰度级下的直方图时,Point( bin_w*(i-1), hist_h - cvRound(r_hist.at<float>(i-1)))所在的点已经超出了直方图画布的宽度400,意味着200~255灰度级的直方图被截取舍弃了,直方图并不完整。

以下是180,199和210灰度下直方图对比:

   

   

可以看到,199灰阶下已经绘制到了画布的边缘,其后的200~255灰阶超出了画布范围,没有再绘制出来,直方图也不再有变化;


把200~255灰阶补充完整的直方图如下:


可以明显看到绿色线代表的直方图分布,在200~255灰阶下有一个下降的过程曲线,做了补充绘制。


原文地址:https://www.cnblogs.com/mtcnn/p/9412003.html