Skip to content

直方图计算

本教程中你将学到如何:

  • 使用 OpenCV 函数 cv::split 将一幅图像划分到其对应的各平面。
  • 使用 OpenCV 函数 cv::calcHist 计算图像数组的直方图
  • 使用函数 cv::normalize 对数组进行归一化

注意: 在上一个教程(tutorial_histogram_equalization)中,我们讨论了一种称为图像直方图(Image histogram)的特殊直方图。现在我们将从更一般的概念来考察它。继续往下读!

  • 直方图是将数据组织到一组预定义bin(区间)中后收集到的计数。

  • 当我们说数据时,并不局限于强度值(正如我们在上一个教程 tutorial_histogram_equalization 中所见)。所采集的数据可以是任何你认为对描述图像有用的特征。

  • 来看一个例子。假设一个矩阵包含某幅图像的信息(例如取值范围为 0−2550-255 的强度):

  • 如果我们想以一种有条理的方式对这些数据进行计数,会怎样?由于我们知道本例中信息值的范围是 256 个值,我们可以将范围划分为若干子部分(称为 bin),例如:

    [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 轴表示 bin,y 轴表示每个 bin 中的像素数)。
  • 这只是一个说明直方图如何工作以及为何有用的简单示例。直方图不仅可以统计颜色强度,还可以统计任何我们想测量的图像特征(例如梯度、方向等)。

  • 我们来认识一下直方图的几个组成部分:

    1. dims:你想要采集数据的参数个数。在我们的例子中,dims = 1,因为我们只统计每个像素的强度值(在灰度图中)。
    2. bins:每个维度上的细分数量。在我们的例子中,bins = 16
    3. range:要测量的值的范围界限。本例中:range = [0,255]
  • 如果你想统计两个特征呢?此时得到的直方图将是一个三维图(其中 x 与 y 分别是每个特征的 binxbin_{x} 与 binybin_{y},z 是每个 (binx,biny)(bin_{x}, bin_{y}) 组合的计数)。对更多特征也同理适用(当然会变得更棘手)。

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

  • 这个程序做什么?

    • 载入一幅图像
    • 使用函数 cv::split 将图像划分到其 R、G、B 平面
    • 通过调用函数 cv::calcHist 计算每个单通道平面的直方图
    • 在一个窗口中绘制三个直方图
  • 可下载代码:点击 这里

  • 代码一览: 代码见下方示例

  • 可下载代码:点击 这里

  • 代码一览: 代码见下方示例

  • 可下载代码:点击 这里

  • 代码一览: 代码见下方示例

  • 载入源图像

  • 将源图像分离为其 R、G、B 三个平面。为此我们使用 OpenCV 函数 cv::split:

    我们的输入是要被划分的图像(本例中为三通道),输出是一个 Mat 向量)

  • 现在我们准备好为每个平面配置直方图了。由于我们处理的是 B、G、R 平面,我们知道取值将落在区间 [0,255][0,255] 内

  • 设定 bin 数量(5、10……):

  • 设置取值范围(如前所述,介于 0 到 255 之间)

  • 我们希望各 bin 大小相同(uniform),并在开始时清空直方图,因此:

  • 我们使用 OpenCV 函数 cv::calcHist 来计算直方图:

  • 其中参数为(C++ 代码):

    • &bgr_planes[0]: 源数组
    • 1: 源数组的数量(本例中为 1。这里也可以传入一个数组列表)
    • 0: 要测量的通道(dim)。本例中仅为强度(每个数组都是单通道),所以写 0。
    • Mat(): 用于源数组的掩膜(零表示要忽略的像素)。若未定义则不使用
    • b_hist: 存放直方图的 Mat 对象
    • 1: 直方图的维数。
    • histSize: 每个使用维度上的 bin 数
    • histRange: 每个维度上要测量的取值范围
    • uniform 与 accumulate: 各 bin 大小相同,且直方图在开始时被清空。
  • 创建一幅用于显示直方图的图像:

  • 注意,在绘制之前,我们先对直方图进行 cv::normalize,使其值落入由所输入参数指定的范围内:

  • 该函数接收以下参数(C++ 代码):

    • b_hist: 输入数组
    • b_hist: 归一化后的输出数组(可以与输入相同)
    • 0 与 histImage.rows:本例中,它们是对 r_hist 值进行归一化的下限与上限
    • NORM_MINMAX: 指示归一化类型的参数(如前所述,它将值调整到先前设定的两个界限之间)
    • -1: 表示归一化后的输出数组与输入类型相同
    • Mat(): 可选的掩膜
  • 注意,要访问 bin(本例中为一维直方图的 bin):

    我们使用如下表达式(C++ 代码):

b_hist.at<float>(i)

Histogram_Calculation_Theory_Hist0.jpg Histogram_Calculation_Theory_Hist1.jpg

其中 $i$ 表示维度。如果是二维直方图,我们会用类似这样的写法:
b_hist.at<float>( i, j )

Histogram_Calculation_Original_Image.jpg Histogram_Calculation_Result.jpg

  • 最后我们显示直方图并等待用户退出:
  1. 以下图所示的图像作为输入:

  2. 得到如下直方图: