OpenCV 图像直方图计算calcHist()

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/keith_bb/article/details/56680997




图像直方图是对数据集合的一种统计方法,将统计结果分布于一系列预定义的bin中,bin是直方图中经常用到的一个概念,其数值是从数据中计算出的特征统计量,这些数据不仅仅指的灰度值,统计数据可能是任何能有效描述图像的特征包括梯度、方向、色彩或任何其他特征。直方图获得的是数据分布的统计图,通常直方图的维数要低于原始数据。

图像直方图是用以表示数字图像中亮度分布的直方图,标绘了图像中亮度值的像素数。可以借助观察该直方图了解需要如何调整亮度分布,这种直方图中横坐标的左侧为纯黑或较暗区域,右侧为较亮纯白的区域。因此一张较暗图片的图像直方图中数据多集中于左侧和中间部分,而整体明亮、只有少量阴影的图像则相反。计算机视觉领域长借助图像直方图来实现图像的二值化。直方图意义如下:

. 直方图是图像中像素强度分布的图形表达方式。

. 它统计了每个强度值所具有的像素个数。

假设有一个矩阵包含一张图像信息(灰度值0-255),如下:

这里写图片描述

如果按照某种方式去统计这些数字,已知数字的范围包含256个值,将这个范围分割成子区域就是bins,如:

这里写图片描述

然后再统计在每一个bin的像素数目。采用这一方法统计上面的数字矩阵我们可以得到下图(x轴表示bin, y轴表示各个bin中的像素个数)。
这里写图片描述

以上只是一个所名直方图如何工作以及它的用处的简单示例。直方图可以统计的不仅仅是颜色灰度,它可以统计任何图像特征(如梯度,方向等等).具体阐述以下直方图的具体细节:
a. dims: 需要统计的特征的数据,上面的例子中,dims=1因为我们仅仅统计了灰度值(灰度图像)
b. bins:每个特征空间子区段的数据,上面的例子中bins=16
c. range: 每个特征空间的取值范围,在上面的例子中range=[0,255]
如果想统计两个特征,直方图就由上面的二维扩展为三维,x轴和y轴分别代表一个特征,z轴是再bin区间的样本数据,同样的方法适用于更多多维度的情况。
在opencv中提供了calcHist()函数计算图像的直方图,计算完成后可以采用前面提到的opencv中的绘图函数如rectangle、line()等绘制显示出来.calcHist()函数原型如下:

void cv::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 
    )   

参数解释
. images: 输入的图像或数组,它们的深度必须为CV_8U, CV_16U或CV_32F中的一类,尺寸必须相同。
. nimages: 输入数组个数,也就是第一个参数中存放了几张图像,有几个原数组。
. channels: 需要统计的通道dim,第一个数组通道从0到image[0].channels()-1,第二个数组从image[0].channels()到images[0].channels()+images[1].channels()-1,以后的数组以此类推
. mask: 可选的操作掩码。如果此掩码不为空,那么它必须为8位并且尺寸要和输入图像images[i]一致。非零掩码用于标记出统计直方图的数组元素数据。
. hist: 输出的目标直方图,一个二维数组
. dims: 需要计算直方图的维度,必须是正数且并不大于CV_MAX_DIMS(在opencv中等于32)
. histSize: 每个维度的直方图尺寸的数组
. ranges: 每个维度中bin的取值范围
. uniform: 直方图是否均匀的标识符,有默认值true
. accumulate: 累积标识符,有默认值false,若为true,直方图再分配阶段不会清零。此功能主要是允许从多个阵列中计算单个直方图或者用于再特定的时间更新直方图.

此外calcHist()还有另外两种定义形式,可查询其classIndex进行查看。

在计算图像直方图的时候一般配合minMaxLoc()和normalize()函数,minMaxLoc()函数是用于寻找最值的函数,其定义如下:

void cv::minMaxLoc  (   InputArray      src,
        double *    minVal,
        double *    maxVal = 0,
        Point *     minLoc = 0,
        Point *     maxLoc = 0,
        InputArray      mask = noArray() 
    )   

参数解释
. src: 输入的单通道数组
. minVal: double类型指针,用于返回最小值的指针,如果不需要返回则设置为NULL
. maxVal: double类型的指针,用于返回最大值指针,如果不需要返回则设置为NULL
. minLoc: 返回最小值位置指针(2D的情况下),如果不需要则设置为NULL
. maxLoc: 返回最大位置指针(2D情况下),如果不需要则设置为NULL
. mask: 可选掩模板。

normalize()函数的作用是将一个数组的值归一化到指定的范围

void cv::normalize  (   InputArray      src,
        InputOutputArray    dst,
        double      alpha = 1,
        double      beta = 0,
        int     norm_type = NORM_L2,
        int     dtype = -1,
        InputArray      mask = noArray() 
    )   

参数解释
. src: 输入数组
. dst: 输出数组,与src有相同的尺寸
. alpha: 将数组归一化范围的最大值,有默认值1
. beta: 归一化的最小值,有默认值0
. norm_type: 归一化方式,可以查看NormTypes()函数查看详细信息,有默认值NORM_L2
. dtype: 当该值取负数时,输出数组与src有相同类型,否则,与src有相同的通道并且深度为CV_MAT_DEPTH(dtype)
. mask: 可选的掩膜版

示例代码

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

using namespace std;
using namespace cv;

int main()
{
    Mat srcImage;
    srcImage = imread("lena.jpg");

    //判断图像是否读取成功
    if(srcImage.empty())
    {
        cout << "图像加载失败!" << endl;
        return -1;
    }
    else
        cout << "图像加载成功!" << endl << endl;

    //分割成三通道图像
    vector<Mat> channels;
    split(srcImage, channels);

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

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

    bool uniform = true;
    bool accumulate = false;

    //声明三个通道的hist数组
    Mat red_hist, green_hist, blue_hist;

    //计算直方图
    calcHist(&channels[0], 1, 0, Mat(), red_hist, 1, &histBinNum, &histRange, uniform, accumulate);
    calcHist(&channels[1], 1, 0, Mat(), green_hist, 1, &histBinNum, &histRange, uniform, accumulate);
    calcHist(&channels[2], 1, 0, Mat(), blue_hist, 1, &histBinNum, &histRange, uniform, accumulate);

    //创建直方图窗口
    int hist_w = 400;
    int hist_h = 400;
    int bin_w = cvRound((double)srcImage.cols/histBinNum);

    Mat histImage(srcImage.cols, srcImage.rows, CV_8UC3, Scalar(0, 0, 0));

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

    //循环绘制直方图
    for(int i = 1; i < histBinNum; i++)
    {
        line(histImage, Point(bin_w*(i-1), srcImage.rows - cvRound(red_hist.at<float>(i-1))),
            Point(bin_w*(i), srcImage.rows - cvRound(red_hist.at<float>(i))), Scalar(0, 0, 255), 2, 8, 0);
        line(histImage, Point(bin_w*(i-1), srcImage.rows - cvRound(green_hist.at<float>(i-1))),
            Point(bin_w*(i), srcImage.rows - cvRound(green_hist.at<float>(i))), Scalar(0, 255, 0), 2, 8, 0);
        line(histImage, Point(bin_w*(i-1), srcImage.rows - cvRound(blue_hist.at<float>(i-1))),
            Point(bin_w*(i), srcImage.rows - cvRound(blue_hist.at<float>(i))), Scalar(255, 0, 0), 2, 8, 0);
    }

    namedWindow("原图像", WINDOW_AUTOSIZE);
    imshow("原图像", srcImage);

    namedWindow("图像直方图", WINDOW_AUTOSIZE);
    imshow("图像直方图", histImage);

    waitKey(0);

    return 0;
}
  • 创建一些矩阵:

    Mat src, dst;
    
  • 装载原图像

    src = imread( argv[1], 1 );
    
  • if( !src.data )
    { return -1; }

  • 使用OpenCV函数 split 将图像分割成3个单通道图像:

    vector<Mat> rgb_planes;
    split( src, rgb_planes );
    

    输入的是要被分割的图像 (这里包含3个通道), 输出的则是Mat类型的的向量。

  • 现在对每个通道配置 直方图 设置, 既然我们用到了 R, G 和 B 通道, 我们知道像素值的范围是 [0 , 255]

  • 设定bins数目 (5, 10...):

    int histSize = 255;
    
  • 设定像素值范围 (前面已经提到,在 0 到 255之间 )

    /// 设定取值范围 ( R,G,B) )
    float range[] = { 0, 255 } ;
    const float* histRange = { range };
    
  • 我们要把bin范围设定成同样大小(均一)以及开始统计前先清除直方图中的痕迹:

    bool uniform = true; bool accumulate = false;
    
  • 最后创建储存直方图的矩阵:

    Mat r_hist, g_hist, b_hist;
    
  • 下面使用OpenCV函数 calcHist 计算直方图:

    /// 计算直方图:
    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 );
    

    参数说明如下:

    • &rgb_planes[0]: 输入数组(或数组集)
    • 1: 输入数组的个数 (这里我们使用了一个单通道图像,我们也可以输入数组集 )
    • 0: 需要统计的通道 (dim)索引 ,这里我们只是统计了灰度 (且每个数组都是单通道)所以只要写 0 就行了。
    • Mat(): 掩码( 0 表示忽略该像素), 如果未定义,则不使用掩码
    • r_hist: 储存直方图的矩阵
    • 1: 直方图维数
    • histSize: 每个维度的bin数目
    • histRange: 每个维度的取值范围
    • uniformaccumulate: bin大小相同,清楚直方图痕迹
  • 创建显示直方图的画布:

    // 创建直方图画布
    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) );

  • 在画直方图之前,先使用 normalize 归一化直方图,这样直方图bin中的值就被缩放到指定范围:

    /// 将直方图归一化到范围 [ 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() );
    

    该函数接受下列参数:

    • r_hist: 输入数组
    • r_hist: 归一化后的输出数组(支持原地计算)
    • 0histImage.rows: 这里,它们是归一化 r_hist 之后的取值极限
    • NORM_MINMAX: 归一化方法 (例中指定的方法将数值缩放到以上指定范围)
    • -1: 指示归一化后的输出数组与输入数组同类型
    • Mat(): 可选的掩码
  • 请注意这里如何读取直方图bin中的数据 (此处是一个1维直方图):

      /// 在直方图画布上画出直方图
      for( int i = 1; i < histSize; i++ )
        {
          line( histImage, Point( bin_w*(i-1), hist_h - cvRound(r_hist.at<float>(i-1)) ) ,
                              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&lt;float&gt;(i-1)) ) ,
                          Point( bin_w*(i), hist_h - cvRound(g_hist.at&lt;float&gt;(i)) ),
                          Scalar( 0, 255, 0), 2, 8, 0  );
    
      line( histImage, Point( bin_w*(i-1), hist_h - cvRound(b_hist.at&lt;float&gt;(i-1)) ) ,
                          Point( bin_w*(i), hist_h - cvRound(b_hist.at&lt;float&gt;(i)) ),
                          Scalar( 255, 0, 0), 2, 8, 0  );
    }
    
  • 使用了以下表达式:

    … code-block:: cpp

    r_hist.at<float>(i)

    :math:i 指示维度,假如我们要访问2维直方图,我们就要用到这样的表达式:

    … code-block:: cpp

    r_hist.at<float>( i, j )

  • 最后显示直方图并等待用户退出程序:

    namedWindow("calcHist Demo", CV_WINDOW_AUTOSIZE );
    imshow("calcHist Demo", histImage );
    
  • waitKey(0);

    return 0;

    程序运行结果
    这里写图片描述

    猜你喜欢

    转载自blog.csdn.net/zhuyong006/article/details/85992684