自己编写的计算图像直方图的函数

由于OpenCV中的直方图统计函数的实现过于复杂,所以本人就动手重写了直方图统计函数,与各位看官们分享交流一下!当然了,代码还是基于OpenCV实现的。本人只实现了计算CV_8U类图像的直方图统计,从一维,二维,到三维。

关于OpenCV的calcHist函数的详细分析,请查看我的博客点击打开链接

我把自己写的calcHist(...)函数放在了自定义的命名空间中,这样避免和OpenCV中的calcHist(...)函数发生混乱


闲话少说,请看代码:(代码猪似的很详细)


#include "stdafx.h"
#include "myHistogram.h"

 
 
/**
	计算给定图像的一维直方图,图像类型必须是CV_8UCx的,x = 1,or 2, or 3 or ....,
	channels规定了通道索引顺序,对于1D直方图,channels数组只能有一个值,且必须 < x
*/
void MyHist::myCalcHist1D( const Mat& image, vector
   
   
    
    & channels,OutputArray _hist, 
						  int dims, const int* histSize,const float** ranges)
{
	int calc_channels = channels.size();//image图像中指定要被统计计算的通道的个数
	int img_channel = image.channels(); //image图像的总的通道个数
	if(img_channel < channels[calc_channels - 1] + 1 )
	{
		printf("channels中的最大通道索引数不能超过images的通道数\n");
		getchar();
	}
	if(image.depth() != CV_8U)
	{
		printf("该函数仅支持CV_8U类的图像\n");
		getchar();
	}
	if(dims != calc_channels)
	{
		printf("被索引的通道数目与直方图的维数不相等\n");
		getchar();
	}
	int img_width = image.cols; //图像宽度
	int img_height = image.rows;//图像高度
	 

	//参数_hist是输出参数,保存着计算好的直方图,一维直方图是histSize[0]行1列的矩阵
	_hist.create(dims, histSize, CV_32F); //为输出直方图开辟空间
    Mat hist = _hist.getMat();	
    hist.flags = (hist.flags & ~CV_MAT_TYPE_MASK)|CV_32S;//直方图矩阵的类型信息 
	hist = Scalar(0.f); //清空原来的数据,当前直方图不累加到原来的直方图上去
 
    int ch = channels[dims-1];//要统计的通道编号 ==: 0 or 1 or 2 
	float low_range = ranges[0][0] ,high_range = ranges[0][1];//要计算的直方图区间的上下限
	float a = histSize[0]/(high_range - low_range);/* 单位区间内直方图bin的数目*/
	float b = -a*low_range;

	//外循环按行从上往下扫描  
	for(int y=0; y < img_height ; y++ )
    {
		//指向图像第y行的指针
		const uchar* ptr_y = image.ptr
    
    
     
     (y);
		
		//内循环从左往右扫描
		for(int x=0; x < img_width ; x++)
		{
			//取出输入图像的第y行第x列第ch通道的像素值
			uchar val = ptr_y[x*img_channel + ch];
			 
			//灰度值val在输入参数规定的灰度区间内的bin序号的索引值
			int idx = cvFloor(val*a + b);//该索引值是数学上线性运算后得到的
			float* hist_ptr = hist.ptr
     
     
      
      (idx); //指向输出直方图的第idx个bin
			 (*hist_ptr)++; //累计idx对应的直方图bin上的数量
		}
    }
	  
	return;
}

/**
	计算给定图像的二维直方图,图像类型必须是CV_8UCx的,x = 2, or 3 or ....,
	channels规定了通道索引顺序,对于2D直方图,channels数组只能有2个值,且必须都 < x
*/
void MyHist::myCalcHist2D( const Mat& image, vector
      
      
       
       & channels,OutputArray _hist, 
						  int dims, const int* histSize,const float** ranges)
{
	int calc_channels = channels.size();//image图像中指定要被统计计算的通道的个数
	int img_channel = image.channels(); //image图像的总的通道个数
	if(img_channel < channels[calc_channels - 1] + 1 )
	{
		printf("channels中的最大通道索引数不能超过images的通道数\n");
		getchar();
	}
	if(image.depth() != CV_8U)
	{
		printf("该函数仅支持CV_8U类的图像\n");
		getchar();
	}
	if(dims != calc_channels)
	{
		printf("被索引的通道数目与直方图的维数不相等\n");
		getchar();
	}
	int img_width = image.cols; //图像宽度
	int img_height = image.rows;//图像高度
	 
	//参数_hist是输出参数,保存着计算好的直方图,二维直方图是histSize[0]行histSize[1]列的矩阵 
	_hist.create(dims, histSize, CV_32F); //为输出直方图开辟空间
    Mat hist = _hist.getMat();	
    hist.flags = (hist.flags & ~CV_MAT_TYPE_MASK)|CV_32S;//直方图矩阵的类型信息 
	hist = Scalar(0.f); //清空原来的数据,当前直方图不累加到原来的直方图上去
	 
	//被统计的通道索引,对应直方图的第一维和第二维
    int ch1 = channels[dims-2], ch2 = channels[dims-1];//要统计的通道编号 ==: 0 or 1 or 2
	//对应于直方图的第一维的区间范围,bin个数等
	float low_range1 = ranges[0][0] ,high_range1 = ranges[0][1];//要计算的直方图第一维区间的上下限
	float a1 = histSize[0]/(high_range1 - low_range1);/* 单位区间内直方图bin的数目*/
	float b1 = -a1*low_range1;
	//对应于直方图的第二维的区间范围,bin个数等
	float low_range2= ranges[1][0] ,high_range2 = ranges[1][1];//要计算的直方图第二维区间的上下限
	float a2 = histSize[1]/(high_range2 - low_range2);/* 单位区间内直方图bin的数目*/
	float b2 = -a2*low_range2;

	//外循环按行从上往下扫描  
	for(int y=0; y < img_height ; y++ )
    {
		//指向图像第y行的指针
		const uchar* ptr_y = image.ptr
       
       
         (y); //内循环从左往右扫描 for(int x=0; x < img_width ; x++) { //取出输入图像的第y行第x列第ch1通道和第ch2通道的像素值 uchar val1 = ptr_y[x*img_channel + ch1]; uchar val2 = ptr_y[x*img_channel + ch2]; //计算灰度值val在输入参数规定的灰度区间内的bin序号的索引值 int idx1 = cvFloor(val1*a1 + b1);//输出直方图第一维的bin索引号 int idx2 = cvFloor(val2*a2 + b2);//输出直方图第二维的bin索引号 //第一维idx1对应于直方图矩阵的行,第二维idx2对应于直方图矩阵的列 float* hist_ptr = hist.ptr 
        
          (idx1) + idx2; //指向输出直方图的第idx1行第idx2列的bin的指针 (*hist_ptr)++; //累计(idx1,idx2)位置处的对应的直方图bin上的数量 } } return; } /** 计算给定图像的三维直方图,图像类型必须是CV_8UCx的,x = 3 or 4,...., channels规定了通道索引顺序,对于3D直方图,channels数组只能有3个值,且必须都 < x */ void MyHist::myCalcHist3D( const Mat& image, vector 
         
           & channels,OutputArray _hist, int dims, const int* histSize,const float** ranges) { int calc_channels = channels.size();//image图像中指定要被统计计算的通道的个数 int img_channel = image.channels(); //image图像的总的通道个数 if(img_channel < channels[calc_channels - 1] + 1 ) { printf("channels中的最大通道索引数不能超过images的通道数\n"); getchar(); } if(image.depth() != CV_8U) { printf("该函数仅支持CV_8U类的图像\n"); getchar(); } if(dims != calc_channels) { printf("被索引的通道数目与直方图的维数不相等\n"); getchar(); } int img_width = image.cols; //图像宽度 int img_height = image.rows;//图像高度 //参数_hist是输出参数,保存着计算好的直方图 _hist.create(dims, histSize, CV_32F); //为输出直方图开辟空间 Mat hist = _hist.getMat(); hist.flags = (hist.flags & ~CV_MAT_TYPE_MASK)|CV_32S;//直方图矩阵的类型信息 hist = Scalar(0.f); //清空原来的数据,当前直方图不累加到原来的直方图上去 //被统计的通道索引,对应直方图的第一维和第二维,和第三维 int ch1 = channels[dims-3], ch2 = channels[dims-2],ch3 = channels[dims-1]; //对应于直方图的第一维的区间范围,bin个数等 float low_range1 = ranges[0][0] ,high_range1 = ranges[0][1];//要计算的直方图第一维区间的上下限 float a1 = histSize[0]/(high_range1 - low_range1);/* 单位区间内直方图bin的数目*/ float b1 = -a1*low_range1; //对应于直方图的第二维的区间范围,bin个数等 float low_range2= ranges[1][0] ,high_range2 = ranges[1][1];//要计算的直方图第二维区间的上下限 float a2 = histSize[1]/(high_range2 - low_range2);/* 单位区间内直方图bin的数目*/ float b2 = -a2*low_range2; //对应于直方图的第三维的区间范围,bin个数等 float low_range3 = ranges[2][0] ,high_range3 = ranges[2][1];//要计算的直方图第三维区间的上下限 float a3 = histSize[2]/(high_range3 - low_range3);/* 单位区间内直方图bin的数目*/ float b3 = -a3*low_range3; const uchar* hist_data = hist.data; //指向直方图矩阵的数据区的首指针 size_t hist_step0 = hist.step[0]; //直方图第一维的步长 size_t hist_step1 = hist.step[1]; //直方图第er维的步长 size_t hist_step2 = hist.step[2]; //直方图第san维的步长 Mat_ 
          
            Myx = image;//Mxy是一个uchar类型的三元组,用于取出image中位于(x,y)位置的像素 //外循环按行从上往下扫描 for(int y=0; y < img_height ; y++ ) { //内循环从左往右扫描 for(int x=0; x < img_width ; x++) { //取出输入图像的第y行第x列所有通道的像素值 Vec3b val = Myx(y,x);//val三元组中按照B,G,R的顺序存放着三个通道的uchar值 //计算像素值val在输入参数规定的灰度区间内的bin序号的索引值 int idx1 = cvFloor(val[ch1]*a1 + b1);//输出直方图第一维的bin索引号 int idx2 = cvFloor(val[ch2]*a2 + b2);//输出直方图第二维的bin索引号 int idx3 = cvFloor(val[ch3]*a3 + b3);//输出直方图第三维的bin索引号 //第一维idx1对应于直方图矩阵的面(层)索引,第二维idx2对应于行索引,第三维idx3为列索引 float* hist_ptr =(float*)(hist_data + idx1*hist_step0 + idx2*hist_step1+ idx3*hist_step2); (*hist_ptr)++; //累计(idx1,idx2,idx3)位置处的对应的直方图bin上的数量 } } return; } 
           
          
         
       
      
      
     
     
    
    
   
   

下面是测试代码,检查自己写的函数是不是与OpenCV的计算结果一样



#include "stdafx.h"
#include 
   
   
    
    
#include 
    
    
     
     
#include "myHistogram.h"

using namespace std;
using namespace cv;

//绘制直方图
void DrawHistogramImage(Mat& hist,int histSize);

Mat grayImg;

int _tmain(int argc, _TCHAR* argv[])
{

	string filename = "E:\\素材\\flower1.jpg";
	string winname = "MainWindow";
	namedWindow(winname,1);

	Mat src, dst;//声明原始图像和目标图像

	/// 装载图像
    src = imread( filename, 1 );
    if( !src.data ) //判断图像是否成功读取
    { return -1; }

	//src.copyTo(image);
	cvtColor(src,grayImg,CV_BGR2GRAY);

	Rect ShowRegion(50,50,450,450);//显示的图像块区域
	Mat ShowImg = src(ShowRegion);//显示的图像块

	imshow(winname,ShowImg);//显示图像块


printf("\n一维直方图测试/\n\n");


	//直方图参数
	vector
     
     
      
       channels;
	//统计#2通道即G通道的直方图 
	channels.push_back(2); //(#0:B;#1:G,#2:R)通道索引顺序
	//设定直方图中bin的数目
	int histSize = 256;
	//设定取值范围
	float range[] = {0,256};//灰度特征的范围都是[0,256)
	const float* histRanges = {range};  
	bool uniform = true; //均匀分布,
	bool accumu = false;//无累加
	Mat cvHist,myHist;
 
	//自己写的计算CV_8UCX(X=1,2,3)类的图像的一维直方图的函数
	MyHist::myCalcHist1D(ShowImg,channels,myHist,1,&histSize,&histRanges);
	//对应的OpenCV的实现
	int cvch[] = {channels[0]};
	calcHist(&ShowImg,1,cvch,noArray(),cvHist,1,&histSize,&histRanges,true,false);
 
	//结果对比
	int flag=1;
	for(int r = 0;r
      
      
       
       (r); 
		float* cvhist_ptr = cvHist.ptr
       
       
         (r); //printf("cvhist: %.1f , myHist: %.1f\n", *cvhist_ptr,*myhist_ptr); if((*cvhist_ptr - *myhist_ptr) >= DBL_EPSILON) { printf("cvhist: %.1f , myHist: %.1f\n", *cvhist_ptr,*myhist_ptr); flag = 0; } } if(flag) printf("一维直方图计算结果与OpenCV自带函数相等\n"); else printf("一维直方图计算结果与OpenCV自带函数不相同\n"); //绘制直方图 DrawHistogramImage(myHist,histSize); printf("\n/------- 二维直方图测试----------///\n\n"); //直方图参数 vector 
        
          channels2; //统计#0和#2通道即B与R通道的直方图 channels2.push_back(0); //(#0:B;#1:G,#2:R)通道索引顺序 channels2.push_back(2); //(#0:B;#1:G,#2:R)通道索引顺序 //设定直方图中每一维上bin的数目 int histSize2[] = {128,128}; //将来的直方图就是histSize2[0]行histSize2[1]列的矩阵 //设定取值范围 float range2[] = {0,256};//b,r通道的灰度特征的范围都是[0,256) const float* histRanges2[] = {range2,range2}; bool uniform2 = true; //均匀分布, bool accumu2 = false;//无累加 Mat cvHist2,myHist2; //自己写的计算CV_8UCX(X=2,3)类的图像的二维直方图的函数 MyHist::myCalcHist2D(ShowImg,channels2,myHist2,2,histSize2,histRanges2); //对应的OpenCV的实现 int cvch2[] = {channels2[0],channels2[1]}; calcHist(&ShowImg,1,cvch2,noArray(),cvHist2,2,histSize2,histRanges2,true,false); //结果对比 int flag2 = 1; for(int r = 0;r 
         
           (r)+c; //指向直方图矩阵的第r行第c列 float* cvhist2_ptr = cvHist2.ptr 
          
            (r)+c; //指向直方图矩阵的第r行第c列 //printf("cvhist: %.1f , myHist: %.1f\n", *cvhist_ptr,*myhist_ptr); if((*cvhist2_ptr - *myhist2_ptr) >= DBL_EPSILON) { printf("cvhist2: %.1f , myHist2: %.1f\n", *cvhist2_ptr,*myhist2_ptr); flag2 = 0;//如果对应位置不相等,则自己的算法和OpenCV不一样 } } } if(flag2) printf("二维直方图计算结果与OpenCV自带函数相等\n"); else printf("二维直方图计算结果与OpenCV自带函数不相同\n"); //归一化直方图 normalize(myHist2,myHist2,0,1,CV_MINMAX); printf("\n/------- 三维直方图测试----------///\n\n"); //直方图参数 vector 
           
             channels3; //3D直方图的第一维是blue,第二维是red,第三维是green channels3.push_back(0); //(#0:B;#1:G,#2:R)通道索引顺序 channels3.push_back(2); //(#0:B;#1:G,#2:R)通道索引顺序 channels3.push_back(1); //(#0:B;#1:G,#2:R)通道索引顺序 //设定直方图中每一维上bin的数目 int histSize3[] = {32,32,32}; //设定取值范围 float range3[] = {0,256};//b,g,r通道的灰度特征的范围都是[0,256) const float* histRanges3[] = {range3,range3,range3}; bool uniform3 = true; //均匀分布, bool accumu3 = false;//无累加 Mat cvHist3,myHist3; //自己写的计算CV_8UC3类的图像的三维直方图的函数 MyHist::myCalcHist3D(ShowImg,channels3,myHist3,3,histSize3,histRanges3); //对应的OpenCV的实现 int cvch3[] = {channels3[0],channels3[1],channels3[2]};//3D直方图通道索引顺序 calcHist(&ShowImg,1,cvch3,noArray(),cvHist3,3,histSize3,histRanges3,true,false); //结果对比,三维直方图总共有histSize3[0]层图像,每层图像有histSize3[1]行histSize3[2]列 int flag3 = 1; for(int idx0 = 0;idx0 < histSize3[0];idx0++) { for(int idx1 = 0;idx1 < histSize3[1];idx1++) { for(int idx2 = 0;idx2 < histSize3[2];idx2++) { float* myhist3_ptr = (float*)(myHist3.data+idx0*myHist3.step[0]+idx1*myHist3.step[1]+ idx2*myHist3.step[2]); float* cvhist3_ptr = (float*)(cvHist3.data+idx0*cvHist3.step[0]+idx1*cvHist3.step[1]+ idx2*cvHist3.step[2]); //printf("cvhist: %.1f , myHist: %.1f\n", *cvhist3_ptr,*myhist3_ptr); if((*cvhist3_ptr - *myhist3_ptr) >= DBL_EPSILON) { printf("cvhist3: %.1f , myHist3: %.1f\n", *cvhist3_ptr,*myhist3_ptr); flag3 = 0;//如果对应位置不相等,则自己的算法和OpenCV不一样 } } } } if(flag3) printf("三维直方图计算结果与OpenCV自带函数相等\n"); else printf("三维直方图计算结果与OpenCV自带函数不相同\n"); //normalize(myHist3,myHist3,0,1,CV_MINMAX);运行这句会报错,normalize函数不支持三维以及三维以上的矩阵 cv::waitKey(0); return 0; } //绘制直方图 void DrawHistogramImage(Mat& hist,int histSize) { // 创建直方图画布 int hist_w = 400; int hist_h = 400;//直方图图像的宽度和高度 int bin_w = cvRound( (double) hist_w/histSize );//直方图中一个矩形条纹的宽度 Mat histImage( hist_w, hist_h, CV_8UC3, Scalar( 0,0,0) );//创建画布图像 Mat draw_hist; /// 将直方图归一化到范围 [ 0, histImage.rows ] normalize(hist, draw_hist, 0, histImage.rows, CV_MINMAX, -1, Mat() ); /// 在直方图画布上画出直方图 for(int i=1;i<=histSize;i++) { //rectangle( histImage,Point((i-1)*bin_w,hist_h),Point(i*bin_w,hist_h-cvRound(draw_hist.at 
            
              (i-1))),Scalar(0,0,255),1,8,0); //折线表示 line( histImage, Point( bin_w*(i-1), hist_h - cvRound(draw_hist.at 
             
               (i-1)) ) , Point( bin_w*(i), hist_h - cvRound(draw_hist.at 
              
                (i)) ), Scalar( 0, 0, 255), 1, 8, 0 ); } /// 显示直方图 namedWindow("ROI Hist",1); imshow("ROI Hist", histImage ); } 
               
              
             
            
           
          
         
       
      
      
     
     
    
    
   
   


  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值