Skip to content

反向投影

本教程中你将学到:

  • 什么是反向投影(Back Projection),以及它为什么有用
  • 如何使用 OpenCV 函数 cv::calcBackProject 计算反向投影
  • 如何使用 OpenCV 函数 cv::mixChannels 混合图像的不同通道
  • 反向投影是一种记录给定图像的像素与某个直方图模型中像素分布匹配程度的方法。
  • 简单地说:对于反向投影,你先计算某个特征的直方图模型,然后用它在图像中寻找该特征。
  • 应用示例:如果你有一个肤色直方图(比如色调-饱和度直方图),那么你就可以用它在一幅图像中寻找肤色区域:
  • 我们用肤色的例子来说明:

  • 假设你已经基于下面的图像得到了一个肤色(色调-饱和度)直方图。旁边的直方图将是我们的模型直方图(我们知道它代表了一个肤色样本)。你对皮肤区域应用了某个掩膜,只采集皮肤区域的直方图:

  • 现在,假设你又得到另一幅手部图像(测试图像),如下图所示:(附带其对应的直方图):

  • 我们要做的是用我们的模型直方图(我们知道它代表一种肤色)在测试图像中检测皮肤区域。步骤如下:

    1. 在测试图像的每个像素(即 p(i,j)p(i,j))处,采集数据并找到该像素对应的 bin 位置(即 (hi,j,si,j)( h_{i,j}, s_{i,j} ))。
    2. 在模型直方图对应的 bin——(hi,j,si,j)( h_{i,j}, s_{i,j} )——中查找,并读取该 bin 的值。
    3. 将该 bin 值存入一幅新图像(BackProjection)。此外,你可以考虑先对模型直方图做归一化,这样测试图像的输出对你而言才可见。
    4. 应用上述步骤,我们得到测试图像如下的 BackProjection 图像:
1. 从统计意义上讲,*BackProjection* 中存储的值表示:基于我们所用的*模型直方图*,测试图像中的某个像素属于皮肤区域的*概率*。例如在我们的测试图像中,较亮的区域更有可能是皮肤区域(事实上也确实如此),而较暗的区域概率较低(注意这些“暗”区域属于带有阴影的表面,这反过来会影响检测)。
  • 这个程序做什么?

    • 载入一幅图像
    • 将原图转换为 HSV 格式,并仅分离出 Hue(色调)通道用于直方图(使用 OpenCV 函数 cv::mixChannels)
    • 让用户输入用于直方图计算的 bin 数量。
    • 计算直方图(若 bin 数变化则更新它)以及同一图像的反向投影。
    • 在窗口中显示反向投影与直方图。
  • 可下载代码:

    • 点击
[这里](https://github.com/opencv/opencv/tree/4.x/samples/cpp/tutorial_code/Histograms_Matching/calcBackProject_Demo1.cpp)

获取基础版本(本教程所讲解的版本)。

  • 想要更花哨一些的内容(使用 H-S 直方图并用 floodFill 为皮肤区域定义掩膜),可以查看这个改进版示例
  • ……或者你总可以在示例中查看经典的
[camshiftdemo](https://github.com/opencv/opencv/tree/4.x/samples/cpp/camshiftdemo.cpp)。
  • 代码一览: 代码见下方示例

  • 可下载代码:

    • 点击
[这里](https://github.com/opencv/opencv/tree/4.x/samples/java/tutorial_code/Histograms_Matching/back_projection/CalcBackProjectDemo1.java)

获取基础版本(本教程所讲解的版本)。

  • 想要更花哨一些的内容(使用 H-S 直方图并用 floodFill 为皮肤区域定义掩膜),可以查看这个改进版示例
  • ……或者你总可以在示例中查看经典的
[camshiftdemo](https://github.com/opencv/opencv/tree/4.x/samples/cpp/camshiftdemo.cpp)。
  • 代码一览: 代码见下方示例

  • 可下载代码:

    • 点击
[这里](https://github.com/opencv/opencv/tree/4.x/samples/python/tutorial_code/Histograms_Matching/back_projection/calcBackProject_Demo1.py)

获取基础版本(本教程所讲解的版本)。

  • 想要更花哨一些的内容(使用 H-S 直方图并用 floodFill 为皮肤区域定义掩膜),可以查看这个改进版示例
  • ……或者你总可以在示例中查看经典的
[camshiftdemo](https://github.com/opencv/opencv/tree/4.x/samples/cpp/camshiftdemo.cpp)。

Back_Projection_Theory0.jpg Back_Projection_Theory1.jpg Back_Projection_Theory2.jpg Back_Projection_Theory3.jpg Back_Projection_Theory4.jpg Back_Projection1_Source_Image.jpg Back_Projection1_Histogram.jpg Back_Projection1_BackProj.jpg

  • 代码一览: 代码见下方示例
  • 读取输入图像:

  • 将其转换为 HSV 格式:

  • 在本教程中,我们的一维直方图只使用 Hue 值(如果你想使用更标准的 H-S 直方图以获得更好的结果,请查看上面链接中更花哨的代码):

  • 如你所见,我们使用函数 cv::mixChannels 从 hsv 图像中只获取通道 0(Hue)。它接收以下参数:

    • &hsv: 从中复制通道的源数组
    • 1: 源数组的数量
    • &hue: 存放复制通道的目标数组
    • 1: 目标数组的数量
    • ch[] = {0,0}: 指示通道如何复制的索引对数组。本例中,&hsv 的 Hue(0) 通道被复制到 &hue(单通道)的 0 通道
    • 1: 索引对的数量
  • 创建一个 Trackbar,供用户输入 bin 值。Trackbar 的任何变化都会触发对 Hist_and_Backproj 回调函数的调用。

  • 显示图像并等待用户退出程序:

  • Hist_and_Backproj 函数: 初始化 cv::calcHist 所需的参数。bin 数量来自 Trackbar:

  • 计算直方图并将其归一化到 [0,255][0,255] 范围

  • 通过调用函数 cv::calcBackProject 获取同一图像的反向投影

  • 所有参数都是已知的(与计算直方图时所用的相同),只是我们增加了 backproj 矩阵,它将存放源图像(&hue)的反向投影

  • 显示 backproj:

  • 绘制图像的一维 Hue 直方图:

下面是使用一幅示例图像(猜猜看?又是一只手)得到的输出。你可以调整 bin 值并观察它对结果的影响: