文档结构  
翻译进度:已翻译     翻译赏金:0 元 (?)    ¥ 我要打赏

目标

在这篇教程中你将学到:

  • 使用OpenCV 函数 split 来将你的图像分割到相应的通道。.
  • 通过OpenCV 函数 calcHist来计算图像数组形式的直方图。
  • 使用函数 normalize来归一化一个数组。

注意

在上一篇教程中(Histogram Equalization) 我们讨论了一种特殊形式的直方图叫做图像直方图。现在我们从更一般的概念层面上来考虑它。搞起!

什么是直方图?

  • 对一些数据我们可以将其划分为一些预定义的容器,每个容器内对应相应的数据的统计计数,我们称之为直方图。

  • 当说到数据的时候我们不仅限于灰度值 (在之前的教程中是这样的). 被统计的数据可以是任何你觉得可用于描述你图像的特征。

  • 让我们举个例子。想象一个包含图像信息的矩阵(也就是在 0-255范围内的灰度值):

    ../../../../../_images/Histogram_Calculation_Theory_Hist0.jpg
  • 假如我们想有组织的对这些数据计数,应该怎么做?由于我们知道在此例中灰度值的范围是0-255,有256种取值, 我们可以将这个范围分为一些小块 (称为容器) 就像这样:

    \begin{array}{l} [0, 255] = { [0, 15] \cup [16, 31] \cup ....\cup [240,255] } \\ range={ bin_{1} \cup bin_{2} \cup ....\cup bin_{n = 15} } \end{array}

    我们对落入每个bin_{i}范围内的像素个数计数. 在上面的例子中我们能得到下图( x坐标代表容器,y坐标代表相应容器内的像素个数).

    ../../../../../_images/Histogram_Calculation_Theory_Hist1.jpg
  • 这只是个简单的用来表明直方图是怎么工作和为啥有用的一个例子. 一个直方图不仅能够统计颜色亮度值, 而且可用于任何你想测量的图像特征 (诸如梯度,方向等等).

  • 让我们来认识一下直方图的一些重要特征:

    1. 维数: 你想要统计的数据的参数个数。在我们的例子中, dims = 1 因为我们只统计了每个像素的灰度值 (在一个灰度图中).
    2. 容器数: 它是对每个维度的细分数目。在我们的例子当中, bins = 16
    3. 范围: 被测量值的一个限度范围.在本例中: range = [0,255]
  • 如果我们想要统计两种特征该怎么办? 在本例中你的直方图结果是一个3D图像 (图像中的 x 和y 分别表示容器bin_{x} 和容器bin_{y},各自对应一种特征,z是落入容器 (bin_{x}, bin_{y})中的统计计数值. 应用到多特征中则是同理 (当然它会变得更加复杂)).

第 1 段(可获 2 积分)

OpenCV能做什么

OpenCV提供了一个简单的计算数组集(通常是图像或分割后的通道)的直方图函数 calcHist。 支持高达 32 维的直方图。下面的代码演示了如何使用该函数计算直方图!

代码

  • 本程序做什么?

    • 装载一张图像
    • 使用函数 split 将载入的图像分割成 R, G, B 单通道图像
    • 调用函数 calcHist 计算各单通道图像的直方图
    • 在一个窗口叠加显示3张直方图
  • 下载代码: 点击 这里

  • 代码一瞥:

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

using namespace std;
using namespace cv;

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

  /// Load image
  src = imread( argv[1], 1 );

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

  /// Separate the image in 3 places ( B, G and R )
  vector<Mat> bgr_planes;
  split( src, bgr_planes );

  /// Establish the number of bins
  int histSize = 256;

  /// Set the ranges ( for B,G,R) )
  float range[] = { 0, 256 } ;
  const float* histRange = { range };

  bool uniform = true; bool accumulate = false;

  Mat b_hist, g_hist, r_hist;

  /// Compute the histograms:
  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 the result to [ 0, histImage.rows ]
  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() );

  /// Draw for each channel
  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  );
  }

  /// Display
  namedWindow("calcHist Demo", CV_WINDOW_AUTOSIZE );
  imshow("calcHist Demo", histImage );

  waitKey(0);

  return 0;
}
第 2 段(可获 2 积分)

解释

  1. 创建矩阵:

    Mat src, dst;
    
  2. 装载原图像

    src = imread( argv[1], 1 );
    
    if( !src.data )
      { return -1; }
    
  3. 使用OpenCV函数 split 将图像分割成3个单通道图像:

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

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

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

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

      int histSize = 256; //from 0 to 255
      
    2. 设定像素值范围 (前面已经提到,在 0 到 255之间 )

      /// Set the ranges ( for B,G,R) )
      float range[] = { 0, 256 } ; //the upper boundary is exclusive
      const float* histRange = { range };
      
    3. 我们要把bin范围设定成同样大小(均一)以及开始统计前先清除直方图中的痕迹:

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

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

      /// Compute the histograms:
      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 );
      

      参数说明如下:

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

    // 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) );
    
  6. 在画直方图之前,先使用 normalize 归一化直方图,这样直方图bin中的值就被缩放到指定范围:

    /// Normalize the result to [ 0, histImage.rows ]
    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() );
    

    该函数接受下列参数:

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

    /// Draw for each channel
    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 )
    
  8. 最后显示直方图并等待用户退出程序:

    namedWindow("calcHist Demo", CV_WINDOW_AUTOSIZE );
    imshow("calcHist Demo", histImage );
    
    waitKey(0);
    
    return 0;
    
第 3 段(可获 2 积分)

结果

  1. 使用下图作为输入图像:../../../../../_images/Histogram_Calculation_Original_Image.jpg

  2. 产生如下直方图:

    ../../../../../_images/Histogram_Calculation_Result.jpg
第 4 段(可获 2 积分)

文章评论