OpenCV 直方图计算函数 calcHist源码深度剖析

OpenCV中的直方图计算函数calcHist函数可以计算给定的若干幅图像的指定的通道的统计直方图!

calcHist函数原型为

     //!计算给定图像集合的联合密度直方图 (joint dense histogram)

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

该函数内部的实现方式是怎样的呢?

首先调用了函数 histPrepareImages(....)准备一些中间变量,

//该函数主要初始化了三个中间临时变量  ptrs,deltas,uniranges
static void histPrepareImages( const Mat* images, int nimages, const int* channels,
                               const Mat& mask, int dims, const int* histSize, const float** ranges, bool uniform,
                               vector<uchar*>& ptrs, vector<int>& deltas, Size& imsize, vector<double>& uniranges )

接着根据输入图像的位深度调用不同的函数实现

针对8位深度的图像,调用了函数calcHist_8u(.....)

static void
calcHist_8u( vector<uchar*>& _ptrs, const vector<int>& _deltas, Size imsize, Mat& hist, int dims, const float**                                _ranges, const double* _uniranges, bool uniform )

针对16位和32位深度图像,调用了模板类函数

template<typename T> static void
calcHist_( vector<uchar*>& _ptrs, const vector<int>& _deltas,  Size imsize, Mat& hist, int dims, const float**                                 _ranges, const double* _uniranges, bool uniform )

调用方式如下:

  int depth = images[0].depth();//输入图像的深度
//根据输入图像的位深度调用不同的直方图计算函数
    if( depth == CV_8U ) //8位深度
        calcHist_8u(ptrs, deltas, imsize, ihist, dims, ranges, _uniranges, uniform );
    else if( depth == CV_16U )//16位深度
        calcHist_<ushort>(ptrs, deltas, imsize, ihist, dims, ranges, _uniranges, uniform );
    else if( depth == CV_32F ) //32位深度
        calcHist_<float>(ptrs, deltas, imsize, ihist, dims, ranges, _uniranges, uniform );
    else
        CV_Error(CV_StsUnsupportedFormat, "");

为了研究源码,我把位于...\OpenCV245\modules\imgproc\src\文件夹下的histogram.cpp文件下的相关函数加到自定义命名空间中,这么做就可以调试运行每一行代码啦 

 
 
在命名空间MyHistFunc中定义了一系列与直方图的计算为主要目的之函数,所有在该Histogram.cpp文件中
被定义为 static 类型的函数,在头文件Histogram.h中都没有进行声明,所以这些静态函数都只能在该文件中
被该文件的定义在他之后的其他函数调用;而只有函数
calcHist( const Mat* images, int nimages, const int* channels,
InputArray _mask, OutputArray _hist, int dims, const int* histSize,
const float** ranges, bool uniform, bool accumulate )
在头文件中进行了声明,所以该函数是唯一暴露在外边,可以被其他文件中的函数(如Main()函数)调用
的函数。 如果你想在别的文件的函数中调用该文件中的static类型的函数,那么只需要把该函数的声明
复制到头文件Histogram.h中,就像calcHist(....)函数那样。

histogram.h 头文件的内容(这个头文件是在新建的工程中自己写的):

#include "opencv2/imgproc/imgproc.hpp"
#include "opencv2/imgproc/imgproc_c.h"
#include "opencv2/core/internal.hpp"
#include 
   
   
    
    
#include 
    
    
     
     
#include 
     
     
      
      
#include 
      
      
       
       
#include 
       
       
         #include 
        
          #include 
         
           using namespace std; using namespace cv; //自己定义了一个MyHistFunc的命名空间,然后把cv命名空间下的对应函数拷贝出来, //以免编译器不知道该使用哪个命名空间中的calcHist(。。。)函数,在主函数调用时 //要加上命名空间的前缀,用法: MyHistFunc:: calcHist(....) namespace MyHistFunc { //! 计算给定的一系列图像的联合密度分布直方图(joint dense histogram) void calcHist( const Mat* images, int nimages, const int* channels, InputArray _mask, OutputArray _hist, int dims, const int* histSize, const float** ranges, bool uniform, bool accumulate ); }; 
          
         
       
      
      
     
     
    
    
   
   

histogram.cpp 直方图函数实现文件的内容(这个头文件是在OpenCV的原文件上改动了命名空间的名字写成的):

#include "stdafx.h"
#include "Histogram.h"

/**
  在命名空间MyHistFunc中定义了一系列与直方图的计算为主要目的之函数,所有在该Histogram.cpp文件中
  被定义为 static 类型的函数,在头文件Histogram.h中都没有进行声明,所以这些静态函数都只能在该文件中
  被该文件的定义在他之后的其他函数调用;而只有函数
       calcHist( const Mat* images, int nimages, const int* channels,
                   InputArray _mask, OutputArray _hist, int dims, const int* histSize,
                   const float** ranges, bool uniform, bool accumulate )
   在头文件中进行了声明,所以该函数是唯一暴露在外边,可以被其他文件中的函数(如Main()函数)调用
   的函数。 如果你想在别的文件的函数中调用该文件中的static类型的函数,那么只需要把该函数的声明
   复制到头文件Histogram.h中,就像calcHist(....)函数那样。
*/

namespace MyHistFunc
{
	
//#define HAVE_TBB 1
	// Helper functions //

static const size_t OUT_OF_RANGE = (size_t)1 << (sizeof(size_t)*8 - 2);

//不管有多少通道,对于8位深度的图像,单一通道上的每个像素点的取值范围是从[0,256),
//所以,在计算8位深度的图像的直方图的时候,总是假定每一个维度上的取值范围都是[0,256)
//然后根据参数中给出的每一维度的范围制作查找表;在每一维上,查找表的长度都是256,
//查找表中第i维第j的位置上存放的就是灰度值等于j的像素点所对应的i维度上的bin的编号
static void
calcHistLookupTables_8u( const Mat& hist, const SparseMat& shist,
                         int dims, const float** ranges, const double* uniranges,
                         bool uniform, bool issparse, vector
   
   
    
    & _tab )
{
    const int low = 0, high = 256;//位深度为8位的图像,一个基础元素的范围都在[0,255)内
    int i, j;
	//为查找表开辟空间,查找表长度 = 直方图维数*256
	//每一个维度所占的存储位置都是256个
    _tab.resize((high-low)*dims);
    size_t* tab = &_tab[0];//指向查找表数组头的指针

    if( uniform )  //均匀直方图,也就是同一维度内部的各个bin的区间宽度是一样的 
    {			   //但是不同的维度上的bin的区间宽度不一定是一样的
        for( i = 0; i < dims; i++ )
        {
            double a = uniranges[i*2];//第i维上单位区间内bin的上限
            double b = uniranges[i*2+1];
            int sz = !issparse ? hist.size[i] : shist.size(i);//第i维上可以容纳的bin数目
			//step[i]是第i维的步长(跨度),多维矩阵每一维度的步长不一样的
			//对于单通道32位深度的浮点数矩阵,第0维的指针移动步长是sizeof(float)即==4字节长度
			//第1维的指针移动步长是 cols*sizeof(float) = 4*hist.cols
            size_t step = !issparse ? hist.step[i] : 1;

			//下面这一循环就是把第i维上的每一个灰度值(0,255)压缩到参数规定的
			//区间{ranges[i][low]到ranges[i][high]}内,然后在对应位置上保存bin索引值
            for( j = low; j < high; j++ )// 灰度值j 从0到255 循环
            {
				//灰度值j在输入参数规定的第i维灰度区间内的索引值
                int idx = cvFloor(j*a + b);//该索引值是数学上线性运算后得到的

                size_t written_idx; //该索引是写入直方图矩阵时的索引值,
				
				//由于查找表是单行矩阵,而直方图是多维矩阵,所以索引idx要乘以直方图
				//每一维对应的跨度(step)才能与查找表的具体位置对应
                if( (unsigned)idx < (unsigned)sz )
                    written_idx = idx*step;//索引idx要乘以直方图每一维对应的跨度(step)
                else
                    written_idx = OUT_OF_RANGE;//对直方图的索引项不能超出直方图的总长度

				//对查找表第i维第j个位置的索引是256*i+j,该位置保存的是当灰度值为j时,应该访问的
				//直方图的对应位置的索引
                tab[i*(high - low) + j - low] = written_idx; //相当于tab[i*256+j] = written_idx;
            }
        }
    }
    else //非均匀直方图的情形,同一维度内各个bin的区间宽度不一样,从最小值到最大值非均匀划分
    {
        for( i = 0; i < dims; i++ )
        {
            int limit = std::min(cvCeil(ranges[i][0]), high);
            int idx = -1, sz = !issparse ? hist.size[i] : shist.size(i);
            size_t written_idx = OUT_OF_RANGE;
            size_t step = !issparse ? hist.step[i] : 1;

            for(j = low;;)
            {
				//低于第i维的最小区间的下界的那些灰度值
                for( ; j < limit; j++ )
                    tab[i*(high - low) + j - low] = written_idx;

				//在第i维指定区间内的灰度-索引值查找表
                if( (unsigned)(++idx) < (unsigned)sz )
                {
                    limit = std::min(cvCeil(ranges[i][idx+1]), high);
                    written_idx = idx*step;
                }
                else
                {
					//超出了第i维度规定的区间中的最大上界的那些灰度值 j
                    for( ; j < high; j++ )
                        tab[i*(high - low) + j - low] = OUT_OF_RANGE;
                    break;
                }
            }
        }
    }
}

//该函数主要初始化了三个中间临时变量  ptrs,deltas,uniranges
static void histPrepareImages( const Mat* images, int nimages, const int* channels,
                               const Mat& mask, int dims, const int* histSize,
                               const float** ranges, bool uniform,
                               vector
    
    
     
     & ptrs, vector
     
     
      
      & deltas,
                               Size& imsize, vector
      
      
       
       & uniranges )
{
    int i, j, c;
    CV_Assert( channels != 0 || nimages == dims );

	 
    imsize = images[0].size();//images是个图像数组,所有图像的尺寸必须一样的
	//输入图像的位深度,elemSize1 表示的是矩阵中数据类型的大小,即 elemSize/channels的大小
    int depth = images[0].depth(), esz1 = (int)images[0].elemSize1();//对于CV_8UCx类的图像,==1
    bool isContinuous = true; //数据在内存中是否连续存放

	//dims是要构建的直方图的维数,+1是因为ptrs中最后一个位置要留给掩膜矩阵mask呀
    ptrs.resize(dims + 1);//指向对应的图像数组首地址,数组中最后一个指针指向mask
    deltas.resize((dims + 1)*2);// +1 是因为deltas中最后两个位置要留给mask呀

    for( i = 0; i < dims; i++ )
    {
        if(!channels) 
        {//channels为空的时候,该句将执行
            
            c = 0;
            CV_Assert( images[i].channels() == 1 );

			CV_Assert( images[i].size() == imsize && images[i].depth() == depth );
            if( !images[i].isContinuous() )
               isContinuous = false;
			ptrs[i] = images[i].data + c*esz1; //ptrs指针指向对应的图像数据数组首地址,这时c==0
			deltas[i*2] = images[i].channels();//第j个图像的通道数目,指针ptrs[i]在图像中同一行内移动的步长
			deltas[i*2+1] = (int)(images[i].step/esz1 - imsize.width*deltas[i*2]);
			 
        }
        else
        {
            c = channels[i];
            CV_Assert( c >= 0 );
            for( j = 0; j < nimages; c -= images[j].channels(), j++ )
                if( c < images[j].channels() )
                    break;
            CV_Assert( j < nimages );

			CV_Assert( images[j].size() == imsize && images[j].depth() == depth );
            if( !images[j].isContinuous() )
               isContinuous = false;
			ptrs[i] = images[j].data + c*esz1; //ptrs指针指向对应的图像矩阵c通道的首地址
			deltas[i*2] = images[j].channels();//第j个图像的通道数目
			deltas[i*2+1] = (int)(images[j].step/esz1 - imsize.width*deltas[i*2]);
        }
        
    }

	//如果mask矩阵不为空,则初始化指向mask矩阵的数据的指针和步长
    if( mask.data )
    {
        CV_Assert( mask.size() == imsize && mask.channels() == 1 );
        isContinuous = isContinuous && mask.isContinuous();
        ptrs[dims] = mask.data;
        deltas[dims*2] = 1;  //列与列之间的步长,掩膜矩阵mask是单通道的,所以 ==1
		//mask.step/mask.elemSize1()是mask矩阵一行的字节数,
        deltas[dims*2 + 1] = (int)(mask.step/mask.elemSize1());//指针在行与行之间的步长
    }

#ifndef HAVE_TBB
    if( isContinuous )
    {
        imsize.width *= imsize.height;
        imsize.height = 1;
    }
#endif
	//ranges[][]是个具有dims行2列的数组,其中第i行ranges[i]代表了直方图第i维的取值区间
	//该区间包括下界ranges[i][0]和上界ranges[i][1]

    if( !ranges )  //ranges数组为空的话,说明参数中没有指定每一维的取值区间,
    {				//则默认所有维度的取值区间是(0,255),而且默认把区间[0,255]
					//划分成histSize个宽度一样的 bin
        CV_Assert( depth == CV_8U );

        uniranges.resize( dims*2 );
        for( i = 0; i < dims; i++ )
        {/*第i维上单位区间内直方图bin的数目*/
            uniranges[i*2] = histSize[i]/256.;
            uniranges[i*2+1] = 0;
        }
    }
    else if( uniform )//如果uniform为真,则说明直方图的每一维上的区间是均匀划分的
    {				  //也就是直方图每一维的所有bin的宽度是一样的
        uniranges.resize( dims*2 );
        for( i = 0; i < dims; i++ )
        {
			//保证第i维的取值范围区间的合理性(a,b)满足a
       
        static void calcHist_( vector 
        
          & _ptrs, const vector 
         
           & _deltas, Size imsize, Mat& hist, int dims, const float** _ranges, const double* _uniranges, bool uniform ) { T** ptrs = (T**)&_ptrs[0]; const int* deltas = &_deltas[0]; uchar* H = hist.data; int i, x; const uchar* mask = _ptrs[dims]; int mstep = _deltas[dims*2 + 1]; int size[CV_MAX_DIM]; size_t hstep[CV_MAX_DIM]; for( i = 0; i < dims; i++ ) { size[i] = hist.size[i]; hstep[i] = hist.step[i]; } if( uniform ) { const double* uniranges = &_uniranges[0]; if( dims == 1 ) { #ifdef HAVE_TBB calcHist1D_Invoker 
          
            body(_ptrs, _deltas, hist, _uniranges, size[0], dims, imsize); parallel_for(BlockedRange(0, imsize.height), body); return; #endif double a = uniranges[0], b = uniranges[1]; int sz = size[0], d0 = deltas[0], step0 = deltas[1]; const T* p0 = (const T*)ptrs[0]; for( ; imsize.height--; p0 += step0, mask += mstep ) { if( !mask ) for( x = 0; x < imsize.width; x++, p0 += d0 ) { int idx = cvFloor(*p0*a + b); if( (unsigned)idx < (unsigned)sz ) ((int*)H)[idx]++; } else for( x = 0; x < imsize.width; x++, p0 += d0 ) if( mask[x] ) { int idx = cvFloor(*p0*a + b); if( (unsigned)idx < (unsigned)sz ) ((int*)H)[idx]++; } } } else if( dims == 2 ) { #ifdef HAVE_TBB calcHist2D_Invoker 
           
             body(_ptrs, _deltas, hist, _uniranges, size, dims, imsize, hstep); parallel_for(BlockedRange(0, imsize.height), body); return; #endif double a0 = uniranges[0], b0 = uniranges[1], a1 = uniranges[2], b1 = uniranges[3]; int sz0 = size[0], sz1 = size[1]; int d0 = deltas[0], step0 = deltas[1], d1 = deltas[2], step1 = deltas[3]; size_t hstep0 = hstep[0]; const T* p0 = (const T*)ptrs[0]; const T* p1 = (const T*)ptrs[1]; for( ; imsize.height--; p0 += step0, p1 += step1, mask += mstep ) { if( !mask ) for( x = 0; x < imsize.width; x++, p0 += d0, p1 += d1 ) { int idx0 = cvFloor(*p0*a0 + b0); int idx1 = cvFloor(*p1*a1 + b1); if( (unsigned)idx0 < (unsigned)sz0 && (unsigned)idx1 < (unsigned)sz1 ) ((int*)(H + hstep0*idx0))[idx1]++; } else for( x = 0; x < imsize.width; x++, p0 += d0, p1 += d1 ) if( mask[x] ) { int idx0 = cvFloor(*p0*a0 + b0); int idx1 = cvFloor(*p1*a1 + b1); if( (unsigned)idx0 < (unsigned)sz0 && (unsigned)idx1 < (unsigned)sz1 ) ((int*)(H + hstep0*idx0))[idx1]++; } } } else if( dims == 3 ) { #ifdef HAVE_TBB if( calcHist3D_Invoker 
            
              ::isFit(hist, imsize) ) { calcHist3D_Invoker 
             
               body(_ptrs, _deltas, imsize, hist, uniranges, dims, hstep, size); parallel_for(BlockedRange(0, imsize.height), body); return; } #endif double a0 = uniranges[0], b0 = uniranges[1], a1 = uniranges[2], b1 = uniranges[3], a2 = uniranges[4], b2 = uniranges[5]; int sz0 = size[0], sz1 = size[1], sz2 = size[2]; int d0 = deltas[0], step0 = deltas[1], d1 = deltas[2], step1 = deltas[3], d2 = deltas[4], step2 = deltas[5]; size_t hstep0 = hstep[0], hstep1 = hstep[1]; const T* p0 = (const T*)ptrs[0]; const T* p1 = (const T*)ptrs[1]; const T* p2 = (const T*)ptrs[2]; for( ; imsize.height--; p0 += step0, p1 += step1, p2 += step2, mask += mstep ) { if( !mask ) for( x = 0; x < imsize.width; x++, p0 += d0, p1 += d1, p2 += d2 ) { int idx0 = cvFloor(*p0*a0 + b0); int idx1 = cvFloor(*p1*a1 + b1); int idx2 = cvFloor(*p2*a2 + b2); if( (unsigned)idx0 < (unsigned)sz0 && (unsigned)idx1 < (unsigned)sz1 && (unsigned)idx2 < (unsigned)sz2 ) ((int*)(H + hstep0*idx0 + hstep1*idx1))[idx2]++; } else for( x = 0; x < imsize.width; x++, p0 += d0, p1 += d1, p2 += d2 ) if( mask[x] ) { int idx0 = cvFloor(*p0*a0 + b0); int idx1 = cvFloor(*p1*a1 + b1); int idx2 = cvFloor(*p2*a2 + b2); if( (unsigned)idx0 < (unsigned)sz0 && (unsigned)idx1 < (unsigned)sz1 && (unsigned)idx2 < (unsigned)sz2 ) ((int*)(H + hstep0*idx0 + hstep1*idx1))[idx2]++; } } } else { for( ; imsize.height--; mask += mstep ) { if( !mask ) for( x = 0; x < imsize.width; x++ ) { uchar* Hptr = H; for( i = 0; i < dims; i++ ) { int idx = cvFloor(*ptrs[i]*uniranges[i*2] + uniranges[i*2+1]); if( (unsigned)idx >= (unsigned)size[i] ) break; ptrs[i] += deltas[i*2]; Hptr += idx*hstep[i]; } if( i == dims ) ++*((int*)Hptr); else for( ; i < dims; i++ ) ptrs[i] += deltas[i*2]; } else for( x = 0; x < imsize.width; x++ ) { uchar* Hptr = H; i = 0; if( mask[x] ) for( ; i < dims; i++ ) { int idx = cvFloor(*ptrs[i]*uniranges[i*2] + uniranges[i*2+1]); if( (unsigned)idx >= (unsigned)size[i] ) break; ptrs[i] += deltas[i*2]; Hptr += idx*hstep[i]; } if( i == dims ) ++*((int*)Hptr); else for( ; i < dims; i++ ) ptrs[i] += deltas[i*2]; } for( i = 0; i < dims; i++ ) ptrs[i] += deltas[i*2 + 1]; } } } else { // non-uniform histogram const float* ranges[CV_MAX_DIM]; for( i = 0; i < dims; i++ ) ranges[i] = &_ranges[i][0]; for( ; imsize.height--; mask += mstep ) { for( x = 0; x < imsize.width; x++ ) { uchar* Hptr = H; i = 0; if( !mask || mask[x] ) for( ; i < dims; i++ ) { float v = (float)*ptrs[i]; const float* R = ranges[i]; int idx = -1, sz = size[i]; while( v >= R[idx+1] && ++idx < sz ) ; // nop if( (unsigned)idx >= (unsigned)sz ) break; ptrs[i] += deltas[i*2]; Hptr += idx*hstep[i]; } if( i == dims ) ++*((int*)Hptr); else for( ; i < dims; i++ ) ptrs[i] += deltas[i*2]; } for( i = 0; i < dims; i++ ) ptrs[i] += deltas[i*2 + 1]; } } } static void calcHist_8u( vector 
              
                & _ptrs, const vector 
               
                 & _deltas, Size imsize, Mat& hist, int dims, const float** _ranges, const double* _uniranges, bool uniform ) { uchar** ptrs = &_ptrs[0]; const int* deltas = &_deltas[0]; uchar* H = hist.data; //指向直方图数据的指针 int x; const uchar* mask = _ptrs[dims];//指向掩膜矩阵的指针 int mstep = _deltas[dims*2 + 1];//mask step ,掩膜矩阵行与行之间的步长 vector 
                
                  _tab; //计算直方图时的查找表 //计算直方图查找表 calcHistLookupTables_8u( hist, SparseMat(), dims, _ranges, _uniranges, uniform, false, _tab ); const size_t* tab = &_tab[0]; //指向直方图查找表数组头的指针 if( dims == 1 )//一维直方图的情形 { #ifdef HAVE_TBB if( CalcHist1D_8uInvoker::isFit(hist, imsize) ) { int treadsNumber = tbb::task_scheduler_init::default_num_threads(); int grainSize = imsize.height/treadsNumber; tbb::mutex histogramWriteLock; CalcHist1D_8uInvoker body(_ptrs, _deltas, imsize, hist, dims, _tab, &histogramWriteLock); parallel_for(BlockedRange(0, imsize.height, grainSize), body); return; } #endif //deltas[]应该是指针每次移动的步子 //deltas[0],d0 是指针横向移动的步长,在一行内每次移动的步长等于通道数 //deltas[1],step0 是纵向移动的步长,在行与行之间移动 int d0 = deltas[0] ; //其实就是输入图像的通道数 int step0 = deltas[1]; int matH[256] = { 0, };//该数组用于统计每一个亮度值出现的次数 const uchar* p0 = (const uchar*)ptrs[0]; //指向图像数据的指针 //外循环按行从上往下扫描,同时分别移动指向图像数据和掩膜矩阵的指针 for( ; imsize.height--; p0 += step0, mask += mstep ) { if( !mask ) //掩膜矩阵的行数和列数与输入图像矩阵的行和列一样 { //如果指向对应行的掩膜矩阵指针为空,则执行下面的语句 if( d0 == 1 )//对应于单通道图像 { for( x = 0; x <= imsize.width - 4; x += 4 ) { int t0 = p0[x], t1 = p0[x+1]; matH[t0]++; matH[t1]++; t0 = p0[x+2]; t1 = p0[x+3]; matH[t0]++; matH[t1]++; } p0 += x; } else //对应于多通道图像 { for( x = 0; x <= imsize.width - 4; x += 4 ) { int t0 = p0[0], t1 = p0[d0]; matH[t0]++; matH[t1]++; p0 += d0*2; t0 = p0[0]; t1 = p0[d0]; matH[t0]++; matH[t1]++; p0 += d0*2; } } //上面的if-else语句执行之后,x的值已经==imsize.width了啊 //所以下面的循环好像不会被执行的 for( ; x < imsize.width; x++, p0 += d0 ) { matH[*p0]++; } } else //如果该行掩膜矩阵的指针mask不为空,则表示该行内有被禁止统计的 { //像素位置,则该行内的每一个位置在统计前要判断是否被掩盖了,即 //mask[x]等于0的位置不予统计 for( x = 0; x < imsize.width; x++, p0 += d0 ) if( mask[x] ) matH[*p0]++; } } //将matH中的统计信息根据查找表放入直方图矩阵中 for(int i = 0; i < 256; i++ ) { //从查找表取出灰度值为i的像素点对应的直方图存储位置索引 size_t hidx = tab[i];//一维直方图的情形,所以查找表中有256项,有可能多个i对应于一个hidx if( hidx < OUT_OF_RANGE ) *(int*)(H + hidx) += matH[i];//(int*)(H + hidx)定位到直方图的第hidx个bin处 } } else if( dims == 2 ) { #ifdef HAVE_TBB if( CalcHist2D_8uInvoker::isFit(hist, imsize) ) { callCalcHist2D_8u(_ptrs, _deltas, imsize, hist, dims, _tab); return; } #endif int d0 = deltas[0], step0 = deltas[1], d1 = deltas[2], step1 = deltas[3]; const uchar* p0 = (const uchar*)ptrs[0]; const uchar* p1 = (const uchar*)ptrs[1]; for( ; imsize.height--; p0 += step0, p1 += step1, mask += mstep ) { if( !mask ) { //没有掩膜矩阵的情况 for( x = 0; x < imsize.width; x++, p0 += d0, p1 += d1 ) { size_t idx = tab[*p0] + tab[*p1 + 256]; if( idx < OUT_OF_RANGE ) ++*(int*)(H + idx); //二维直方图没有用matH做统计,而是在原来的H上直接累加 } } else { //有掩膜矩阵的情况 for( x = 0; x < imsize.width; x++, p0 += d0, p1 += d1 ) { if(mask[x]) //mask[x]等于0的位置不予统计 { //从查找表取出*p0和*p1对应的直方图存储位置索引 size_t idx = tab[*p0] + tab[*p1 + 256] ;//p0是第一维,p1第二维 if( idx < OUT_OF_RANGE ) ++*(int*)(H + idx); //在对应位置上累加直方图 } } } } } else if( dims == 3 ) { #ifdef HAVE_TBB if( CalcHist3D_8uInvoker::isFit(hist, imsize) ) { callCalcHist3D_8u(_ptrs, _deltas, imsize, hist, dims, _tab); return; } #endif int d0 = deltas[0], step0 = deltas[1], d1 = deltas[2], step1 = deltas[3], d2 = deltas[4], step2 = deltas[5]; const uchar* p0 = (const uchar*)ptrs[0]; const uchar* p1 = (const uchar*)ptrs[1]; const uchar* p2 = (const uchar*)ptrs[2]; for( ; imsize.height--; p0 += step0, p1 += step1, p2 += step2, mask += mstep ) { if( !mask ) for( x = 0; x < imsize.width; x++, p0 += d0, p1 += d1, p2 += d2 ) { size_t idx = tab[*p0] + tab[*p1 + 256] + tab[*p2 + 512]; if( idx < OUT_OF_RANGE ) ++*(int*)(H + idx); } else for( x = 0; x < imsize.width; x++, p0 += d0, p1 += d1, p2 += d2 ) { size_t idx; if( mask[x] && (idx = tab[*p0] + tab[*p1 + 256] + tab[*p2 + 512]) < OUT_OF_RANGE ) ++*(int*)(H + idx); } } } else { for( ; imsize.height--; mask += mstep ) { if( !mask ) for( x = 0; x < imsize.width; x++ ) { uchar* Hptr = H; int i = 0; for( ; i < dims; i++ ) { size_t idx = tab[*ptrs[i] + i*256]; if( idx >= OUT_OF_RANGE ) break; Hptr += idx; ptrs[i] += deltas[i*2]; } if( i == dims ) ++*((int*)Hptr); else for( ; i < dims; i++ ) ptrs[i] += deltas[i*2]; } else for( x = 0; x < imsize.width; x++ ) { uchar* Hptr = H; int i = 0; if( mask[x] ) for( ; i < dims; i++ ) { size_t idx = tab[*ptrs[i] + i*256]; if( idx >= OUT_OF_RANGE ) break; Hptr += idx; ptrs[i] += deltas[i*2]; } if( i == dims ) ++*((int*)Hptr); else for( ; i < dims; i++ ) ptrs[i] += deltas[i*2]; } for(int i = 0; i < dims; i++ ) ptrs[i] += deltas[i*2 + 1]; } } } } //! 计算给定的一系列图像的联合密度分布直方图(joint dense histogram) //该函数按照直方图的数据类型和直方图的维数分情况计算直方图 void MyHistFunc::calcHist( const Mat* images, int nimages, const int* channels, InputArray _mask, OutputArray _hist, int dims, const int* histSize, const float** ranges, bool uniform, bool accumulate ) { Mat mask = _mask.getMat(); //把InputArray类型的参数转换为Mat类型 //CV_Assert(...)语句只有Debug模式下会执行,release模式下不执行 CV_Assert(dims > 0 && histSize);//参数检验, //参数_hist是输出参数,保存着计算好的直方图 uchar* histdata = _hist.getMat().data; //指向直方图的数据部分的指针 _hist.create(dims, histSize, CV_32F); //为输出直方图开辟空间 Mat hist = _hist.getMat(), ihist = hist; //把OutputArray类型的参数转换为Mat类型 ihist.flags = (ihist.flags & ~CV_MAT_TYPE_MASK)|CV_32S;//直方图矩阵的类型信息 //如果参数_hist中本来就有之前计算的直方图,判断是否重新计算或累加上去 if( !accumulate || histdata != hist.data ) hist = Scalar(0.); //清空原来的数据,当前直方图不累加到原来的直方图上去 else hist.convertTo(ihist, CV_32S);//保留原来的直方图,当前计算的结果累加到原来的直方图 //定义一些中间临时变量 vector 
                 
                   ptrs; //指向图像数据和掩膜矩阵数据的指针数组,有dims+1个指针 vector 
                  
                    deltas; //存放每一维上的指针移动步长 vector 
                   
                     uniranges;//直方图每一个维度上单位区间内的直方图数目 Size imsize; //图像尺寸 //检查掩膜矩阵的有效性,必须是8位单通道 CV_Assert( !mask.data || mask.type() == CV_8UC1 ); //前期准备工作,主要是填充前面声明的辅助变量ptrs, deltas, imsize, uniranges histPrepareImages( images, nimages, channels, mask, dims, hist.size, ranges, uniform, ptrs, deltas, imsize, uniranges ); //判断在每一维度上的划分是否刻度均匀, const double* _uniranges = uniform ? &uniranges[0] : 0; int depth = images[0].depth();//输入图像的深度 //根据输入图像的位深度调用不同的直方图计算函数 if( depth == CV_8U ) //8位深度 calcHist_8u(ptrs, deltas, imsize, ihist, dims, ranges, _uniranges, uniform ); else if( depth == CV_16U )//16位深度 calcHist_ 
                    
                      (ptrs, deltas, imsize, ihist, dims, ranges, _uniranges, uniform ); else if( depth == CV_32F ) //32位深度 calcHist_ 
                     
                       (ptrs, deltas, imsize, ihist, dims, ranges, _uniranges, uniform ); else CV_Error(CV_StsUnsupportedFormat, ""); ihist.convertTo(hist, CV_32F); } 
                      
                     
                    
                   
                  
                 
                
               
              
             
            
           
          
        
      
      
     
     
    
    
   
   
<="" ranges[i][1]="" );="" double="" low="ranges[i][0]," high="ranges[i][1];//第i维取值范围的的下界和上界" t="" -="" low);="" *第i维上单位区间内直方图bin的数目*="" uniranges[i*2]="t;" uniranges[i*2+1]="-t*low;" }="" else="" 非均匀划分直方图的情形,这时ranges[][]是一个dims行n列的数组="" {="" 每一行都是一串从小到大排列的数,即="" 第i维上第k个bin的区间是[="" ranges[i][k]="" ,="" ranges[i][k+1]="" ],每个区间的宽度可能不等="" for(="" i="" dims;="" i++="" )="" size_t="" n="histSize[i];" 第i维的bin的个数="" 保证第i维的每一个bin的取值区间的合理性,即区间(a,b)满足a

主函数的实现:(主程序中调用了命名空间MyHistFunc中的函数calcHist(....)

//

#include "stdafx.h"
#include 
    
     
      
     
#include 
     
      
       
      
#include "Histogram.h"

using namespace std;
using namespace cv;

Mat image;       //母图像
Mat roi_hist;   //兴趣区域图像块的直方图
Mat roi_patch;  //兴趣区域的图像块
bool selectObject = false; //selectObject的初始值为false,
int calculateHist = 0;  //calculateHist的初值是0
bool showHist = false;
Point origin;
Rect selection;
//用鼠标选择目标图像区域
static void onMouse( int event, int x, int y, int, void* );
//该函数用于计算给定矩形区域的图像块的直方图
static void CalculatePatchHistogram(Rect& patchRect);
//绘制直方图
void DrawHistogramImage();

//设定直方图中bin的数目
int histSize = 256;
//设定取值范围
float range[] = {0,256};//灰度特征的范围都是0到255
const float* histRange = {range};  
bool uniform = true; 
bool accumu = false;//均匀分布,无累加

int _tmain(int argc, _TCHAR* argv[])
{
	string filename = "E:\\素材\\flower1.jpg";
	string winname = "MainWindow";
	namedWindow(winname,1);
	//为 Histogram Demo窗口添加鼠标响应函数,用于选择图像块
	setMouseCallback( "MainWindow", onMouse, 0 );

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

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

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

	for(;;)
	{
		///循环显示读入的图像
		imshow(winname,image);
		if(calculateHist)
		{
			cout<<"开始计算所选区域直方图.... ... ..."<
      
       
        
        0 && selection.height > 0 )
            calculateHist = 1; //当在第一帧用鼠标选定了合适的目标跟踪窗口后,calculateHist的值置为 1
		selection = Rect(696,246,176,177);
		cout<<"选中的矩形区域为: "<
       
        
         
        <
        
         
           (i-1))),Scalar(0,0,255),1,8,0); */ //折线表示 line( histImage, Point( bin_w*(i-1), hist_h - cvRound(roi_hist.at 
          
            (i-1)) ) , Point( bin_w*(i), hist_h - cvRound(roi_hist.at 
           
             (i)) ), Scalar( 0, 0, 255), 1, 8, 0 ); } /// 显示直方图 namedWindow("ROI Hist",1); imshow("ROI Hist", histImage ); } 
            
           
         
       
        
      
       
     
      
    
     

需要看客们注意的地方是:我并没有把TBB下的函数拷贝到自定义的命名空间钟来,因为那是并行运算的代码。

如下所示:

  
   
#ifdef HAVE_TBB
calcHist1D_Invoker < T > body ( _ptrs , _deltas , hist , _uniranges , size [ 0 ], dims , imsize );
parallel_for ( BlockedRange ( 0 , imsize . height ), body );
return ;
#endif
只要没有定义HAVE_TBB标志,自己的命名空间就可以正常运行了,否则会提示你上述类 calcHist1D_Invoker < T >没有定义喔





  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
OpenCV提供了calcHist()函数计算图像的直方图,它可以对整幅图像进行直方图处理,也可以对图像的某个区域进行直方图处理。 对图像的某个区域进行直方图处理,需要先定义一个ROI(Region of Interest)区域,然后将该区域作为参数传递给calcHist()函数。定义ROI区域的方法有两种: - 使用cv::Rect定义矩形区域 - 使用cv::Mat定义掩模区域 定义矩形区域的代码如下: ```cpp cv::Mat image = cv::imread("image.jpg"); cv::Rect roi_rect(100, 100, 200, 200); // (x, y, w, h) cv::Mat roi_image = image(roi_rect); cv::Mat hist; int channels[] = {0}; // 只处理图像的一个通道 int histSize[] = {256}; // 直方图的bin数量 float range[] = {0, 256}; // 像素值的范围 const float* ranges[] = {range}; cv::calcHist(&roi_image, 1, channels, cv::Mat(), hist, 1, histSize, ranges); ``` 定义掩模区域的代码如下: ```cpp cv::Mat image = cv::imread("image.jpg"); cv::Mat mask = cv::Mat::zeros(image.size(), CV_8UC1); cv::Rect roi_rect(100, 100, 200, 200); // (x, y, w, h) mask(roi_rect) = cv::Scalar(255); // 将ROI区域设置为白色 cv::Mat hist; int channels[] = {0}; // 只处理图像的一个通道 int histSize[] = {256}; // 直方图的bin数量 float range[] = {0, 256}; // 像素值的范围 const float* ranges[] = {range}; cv::calcHist(&image, 1, channels, mask, hist, 1, histSize, ranges); ``` 在上述代码中,cv::calcHist()函数的第4个参数是掩模区域,如果不需要使用掩模区域,可以传递cv::Mat()作为该参数。 处理完直方图之后,可以使用cv::normalize()函数直方图归一化到一定范围内,例如[0,1]或[0,255]。归一化后的直方图可以用于图像增强、颜色量化等应用。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值