阅读(3133) (9)

OpenCV直方图计算

2017-09-19 10:24:40 更新

目标

在本教程中,您将学习如何:

注意
在上一个教程(直方图均衡)中,我们讨论了一种称为“ 图像直方图”的特定类型的直方图。现在我们将在其更一般的概念中思考。

什么是直方图?

  • 直方图是将数据组织成一组预定义仓的收集计数
  • 当我们说数据时,我们并不把它限制为强度值(正如我们在前面的教程中看到的)。收集的数据可以是您发现有用的描述您的图像的任何功能。
  • 我们来看一个例子。假设矩阵包含图像的信息(即范围内的强度):0−255

OpenCV直方图计算

  • 如果我们想以有组织的方式计算这些数据会怎么样?由于我们知道这种情况下的信息值范围是256个值,所以我们可以在子部分(称为bins)中划分我们的范围,如:

OpenCV直方图计算

我们可以保持落在每个范围内的像素数量。将其应用于上述示例,我们得到下面的图像(x轴表示bins和轴y表示每个像素的数目)。

OpenCV直方图计算

  • 这只是一个简单的直方图的例子,为什么它是有用的。直方图不仅可以保持颜色强度的计数,还可以保持我们想要测量的任何图像特征(即梯度,方向等)。
  • 我们来确定直方图的一些部分:
  1. dims:要收集数据的参数数。在我们的示例中,dims = 1,因为我们只计算每个像素的强度值(在灰度图像中)。
  2. bins:每个暗淡的细分数。在我们的例子中,bin = 16
  3. range:要测量的值的限制。在这种情况下:range = [0,255]

如果你想计算两个功能怎么办?在这种情况下,您生成的直方图将是一个3D图(其中x和y将为和  ,而z将每个组合的计数。这同样适用于更多的功能(当然会变得更棘手)。

OpenCV为您提供什么

为了简单起见,OpenCV实现了函数cv :: calcHist,它计算一组数组(通常是图像或图像平面)的直方图。它最多可以操作32个维度。我们将在下面的代码中看到它!

Code

  • 这个程序是做什么的?加载图像使用函数cv :: split将图像分解为R,G和B平面通过调用函数cv :: calcHist计算每个1通道平面的直方图在窗口中绘制三个直方图
  • 可下载的代码:点击这里
  • 代码一览:
#include "opencv2/highgui.hpp"
#include "opencv2/imgcodecs.hpp"
#include "opencv2/imgproc.hpp"
#include <iostream>
using namespace std;
using namespace cv;
int main(int argc, char** argv)
{
  Mat src, dst;
  String imageName( "../data/lena.jpg" ); // by default
  if (argc > 1)
  {
      imageName = argv[1];
  }
  src = imread( imageName, IMREAD_COLOR );
  if( src.empty() )
    { return -1; }
  vector<Mat> bgr_planes;
  split( src, bgr_planes );
  int histSize = 256;
  float range[] = { 0, 256 } ;
  const float* histRange = { range };
  bool uniform = true; bool accumulate = false;
  Mat b_hist, g_hist, r_hist;
  calcHist( &bgr_planes[0], 1, 0, Mat(), b_hist, 1, &histSize, &histRange, uniform, accumulate );
  calcHist( &bgr_planes[1], 1, 0, Mat(), g_hist, 1, &histSize, &histRange, uniform, accumulate );
  calcHist( &bgr_planes[2], 1, 0, Mat(), r_hist, 1, &histSize, &histRange, uniform, accumulate );
  // Draw the histograms for B, G and R
  int hist_w = 512; int hist_h = 400;
  int bin_w = cvRound( (double) hist_w/histSize );
  Mat histImage( hist_h, hist_w, CV_8UC3, Scalar( 0,0,0) );
  normalize(b_hist, b_hist, 0, histImage.rows, NORM_MINMAX, -1, Mat() );
  normalize(g_hist, g_hist, 0, histImage.rows, NORM_MINMAX, -1, Mat() );
  normalize(r_hist, r_hist, 0, histImage.rows, NORM_MINMAX, -1, Mat() );
  for( int i = 1; i < histSize; i++ )
  {
      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  );
      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(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  );
  }
  namedWindow("calcHist Demo", WINDOW_AUTOSIZE );
  imshow("calcHist Demo", histImage );
  waitKey(0);
  return 0;
}

说明

  • 创建必要的矩阵:
Mat src,dst;
  • 加载源图像
src = imread( argv[1], 1 );
if( !src.data )
  { return -1; }
  • 在三个R,G和B平面中分离源图像。为此,我们使用OpenCV函数cv :: split
vector<Mat> bgr_planes;
split( src, bgr_planes );

我们的输入是要分割的图像(这种情况下有三个通道),输出是Mat的向量)

  • 现在我们准备开始为每个平面配置直方图。由于我们正在使用B,G和R planes,我们知道我们的值将在间隔范围内[0,255]

建立箱数(5,10 ...):

int histSize = 256; //from 0 to 255

设置值的范围(如我们所说,在0到255之间)

float range[] = { 0, 256 } ; //the upper boundary is exclusive
const float* histRange = { range };

我们希望我们的箱子具有相同的尺寸(均匀),并在开始时清除直方图,所以:

bool uniform = true ; bool accumulate = false ;

最后,我们创建Mat对象来保存直方图。创建3(每个planes一个):

Mat b_hist,g_hist,r_hist;

我们继续使用OpenCV函数cv :: calcHist计算直方图:

calcHist(&bgr_planes [0],1,0,Mat(),b_hist,1,&histSize,&histRange,uniform,accumulate);
calcHist(&bgr_planes [1],1,0,Mat(),g_hist,1,&histSize,&histRange,uniform,accumulate);
calcHist(&bgr_planes [2],1,0,Mat(),r_hist,1,&histSize,&histRange,uniform,accumulate);

其中的论点是:

  1. **&bgr_planes [0]:**源数组(s)
  2. 1:源数组的数量(在这种情况下,我们正在使用1.我们可以在这里输入一个数组列表)
  3. 0:要测量的通道(dim)。在这种情况下,它只是强度(每个阵列是单通道),所以我们只写0。
  4. Mat():在源数组上使用的掩码(指示要忽略的像素的零)。如果未定义,则不使用它
  5. b_hist:要存储直方图的Mat对象
  6. 1:直方图维度。
  7. histSize:每个使用的维数的数量
  8. histRange:每个维度要测量的值的范围
  9. uniformaccumulate:纸箱尺寸相同,直方图在开始时清除。
  • 创建一个图像以显示直方图:
// Draw the histograms for R, G and B
int hist_w = 512; int hist_h = 400;
int bin_w = cvRound( (double) hist_w/histSize );
Mat histImage( hist_h, hist_w, CV_8UC3, Scalar( 0,0,0) );
  • 请注意,在绘制之前,我们首先cv ::归一化直方图,使其值落在由输入的参数指示的范围内:
normalize(b_hist, b_hist, 0, histImage.rows, NORM_MINMAX, -1, Mat() );
normalize(g_hist, g_hist, 0, histImage.rows, NORM_MINMAX, -1, Mat() );
normalize(r_hist, r_hist, 0, histImage.rows, NORM_MINMAX, -1, Mat() );

此函数接收这些参数:

  1. b_hist:输入数组
  2. b_hist:输出归一化数组(可以相同)
  3. 0和** histImage.rows:对于这个例子,它们是对r_hist **的值进行归一化的下限和上限
  4. NORM_MINMAX:指示归一化类型的参数(如上所述,它调整之前设置的两个限制之间的值)
  5. ** - 1:**意味着输出归一化数组将与输入的类型相同
  6. Mat():可选掩码
  • 最后,观察到访问bin(在这种情况下在这个1D直方图中):
for( int i = 1; i < histSize; i++ )
{
    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  );
    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(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  );
}

我们使用表达式:

b_hist.at < float >(i)

其中 i 表示尺寸。如果它是一个二维直方图,我们将使用如下:

b_hist.at<float>( i, j )

最后我们显示直方图,等待用户退出:

namedWindow("calcHist Demo", WINDOW_AUTOSIZE );
imshow("calcHist Demo", histImage );
waitKey(0);
return 0;

结果

  • 作为输入参数使用如下所示的图像:

OpenCV直方图计算

  • 生成以下直方图:

OpenCV直方图计算