反向投影(BackProjection) - 阳光守望者 - 博客园 (cnblogs.com)
Goal
在本教程中,您将学习:
什么是反投影以及它为什么有用
如何使用 OpenCV 函数 cv::calcBackProject 计算反投影
如何使用 OpenCV 函数 cv::mixChannels 混合图像的不同通道
Theory
What is Back Projection?
反向投影Back Projection是一种记录给定图像的像素与直方图模型中像素分布的匹配程度的方法。
为了使其更简单:对于反向投影,您计算特征的直方图模型,然后使用它在图像中找到该特征。
应用示例:如果您有一个肤色直方图(例如,色相饱和度直方图),那么您可以使用它来查找图像中的肤色区域:
How does it work?
我们通过使用皮肤示例来解释这一点:
假设您已经根据下图获得了皮肤直方图(色相饱和度)。 此外,直方图将成为我们的模型直方图(我们知道它代表皮肤色调的样本)。 您应用了一些mask来仅捕获皮肤区域的直方图:
T0
T1
现在,让我们假设您得到另一张手部图像(测试图像),如下图所示:(及其各自的直方图):
T2
T3
我们想要做的是使用我们的model histogram模型直方图(我们知道它代表皮肤色调)来检测测试图像中的皮肤区域。 以下是步骤
1.在我们的测试图像的每个像素(即 p(i,j) )中,收集数据并找到该像素对应的 bin 位置(即 (hi,j,si,j) )。
2.在对应的 bin - (hi,j,si,j) 中查找模型直方图 - 并读取 bin 值。
3.将此 bin 值存储在新图像中 (BackProjection)。 此外,您可以考虑首先对模型直方图进行归一化,以便您可以看到测试图像的输出。
4.应用上述步骤,我们为我们的测试图像获得以下 BackProjection 图像:
5. 在统计方面,BackProjection 中存储的值代表了测试图像中的像素属于皮肤区域的概率,基于我们使用的model histogram模型直方图。 例如,在我们的测试图像中,较亮的区域更有可能是皮肤区域(实际上是),而较暗的区域则概率较小(请注意,这些“暗”区域属于有一些阴影的表面,这 反过来影响检测)。
Code
1.这个程序做了什么?
加载图像
将原始格式转换为 HSV 格式并仅分离用于直方图的 Hue 通道(使用 OpenCV 函数 cv::mixChannels )
让用户输入用于计算直方图的 bin 数量。
计算直方图(如果 bin 发生变化,则更新它)和同一图像的反投影。
在 windows 中显示反投影和直方图
2.可下载代码:
单击此处opencv/calcBackProject_Demo1.cpp at 4.x · opencv/opencv · GitHub 获取基本版本(在本教程中进行了说明)。
对于稍微花哨的东西(使用 H-S 直方图和 FloodFill 为皮肤区域定义蒙版),您可以查看改进的演示opencv/calcBackProject_Demo2.cpp at 4.x · opencv/opencv · GitHub
...或者您可以随时查看示例中的经典 camshiftdemo opencv/camshiftdemo.cpp at 4.x · opencv/opencv (github.com) 。
3. 代码一目了然
/**
* @file BackProject_Demo1.cpp
* @brief 反向投影 Sample code for backproject function usage
* @author OpenCV team
*/
#include "opencv2/imgproc.hpp"
#include "opencv2/imgcodecs.hpp"
#include "opencv2/highgui.hpp"
#include <iostream>
using namespace cv;
using namespace std;
/// 全局变量
Mat hue; //色调图
int bins = 25;//25分
/// 函数头 直方图和反向投影
void Hist_and_Backproj(int, void* );
/**
* @function main
*/
int main( int argc, char* argv[] )
{
//! [读取图像]
CommandLineParser parser( argc, argv, "{@input | | input image}" );
Mat src = imread( parser.get<String>( "@input" ) );
if( src.empty() )
{
cout << "无法打开或找不到图像!\n" << endl;
cout << "Usage: " << argv[0] << " <Input image>" << endl;
return -1;
}
//! [Read the image]
//! [将其转换为 HSV]
Mat hsv;
cvtColor( src, hsv, COLOR_BGR2HSV );
//! [Transform it to HSV]
//! [仅使用色调值]
hue.create(hsv.size(), hsv.depth());//hue图与hsv图尺寸一致
int ch[] = { 0, 0 };
mixChannels( &hsv, 1, &hue, 1, ch, 1 );//提取hue图
//! [Use only the Hue value]
//! [创建 Trackbar 以输入 bin 数量]
const char* window_image = "Source image";
namedWindow( window_image );
createTrackbar("* Hue bins: ", window_image, &bins, 180, Hist_and_Backproj );
Hist_and_Backproj(0, 0);//滑动条回调,设置bin数量,计算直方图和反向投影
//! [Create Trackbar to enter the number of bins]
//! [显示图像]
imshow( window_image, src );
// 等到用户退出程序
waitKey();
//! [Show the image]
return 0;
}
/**
* @function Hist_and_Backproj
* @brief Callback to Trackbar
*/
void Hist_and_Backproj(int, void* )
{
//! [初始化]
int histSize = MAX( bins, 2 );//bin数量
float hue_range[] = { 0, 180 };//hue色调变化范围
const float* ranges = { hue_range };
//! [initialize]
//! [获取直方图并将其标准化]
Mat hist;
calcHist( &hue, 1, 0, Mat(), hist, 1, &histSize, &ranges, true, false );
normalize( hist, hist, 0, 255, NORM_MINMAX, -1, Mat() );//标准化 0-255
//! [Get the Histogram and normalize it]
//! [Get Backprojection]
Mat backproj;//反向投影
calcBackProject( &hue, 1, 0, hist, backproj, &ranges, 1, true );//hist反向投影到hue得到backproj
//! [Get Backprojection]
//! [绘制反向投影]
imshow( "BackProj", backproj );
//! [Draw the backproj]
//! [绘制直方图]
int w = 400, h = 400;//图像尺寸
int bin_w = cvRound( (double) w / histSize );//bin宽度
Mat histImg = Mat::zeros( h, w, CV_8UC3 );//初始化黑色图片
for (int i = 0; i < bins; i++)
{
rectangle( histImg, Point( i*bin_w, h ), Point( (i+1)*bin_w, h - cvRound( hist.at<float>(i)*h/255.0 ) ),
Scalar( 0, 0, 255 ), FILLED );//矩形 直方图
}
imshow( "Histogram", histImg );//显示
//! [Draw the histogram]
}
Explanation
- 读取输入图像:
CommandLineParser parser( argc, argv, "{@input | | input image}" );
Mat src = imread( parser.get<String>( "@input" ) );
if( src.empty() )
{
cout << "Could not open or find the image!\n" << endl;
cout << "Usage: " << argv[0] << " <Input image>" << endl;
return -1;
}
- 将其转换为 HSV 格式:
Mat hsv;
cvtColor( src, hsv, COLOR_BGR2HSV );
- 在本教程中,我们将只使用一维直方图的色调值(如果您想使用更标准的 H-S 直方图,请查看上面链接中更高级的代码,这会产生更好的结果):
hue.create(hsv.size(), hsv.depth());
int ch[] = { 0, 0 };
mixChannels( &hsv, 1, &hue, 1, ch, 1 );
如您所见,我们使用函数 cv::mixChannels 从 hsv 图像中仅获取通道 0(色调)。 它获取以下参数:
&hsv:将从中复制通道的源数组
1:源数组的数量
&hue: 复制通道的目标数组
1:目标数组的数量
ch[] = {0,0}:指示如何复制通道的索引对数组。 在这种情况下,&hsv 的 Hue(0) 通道被复制到 &hue (1-channel) 的 0 通道
1:索引对数
- 为用户创建一个 Trackbar 以输入 bin 值。 Trackbar 上的任何更改都意味着调用 Hist_and_Backproj 回调函数。
const char* window_image = "Source image";
namedWindow( window_image );
createTrackbar("* Hue bins: ", window_image, &bins, 180, Hist_and_Backproj );
Hist_and_Backproj(0, 0);
- 显示图像并等待用户退出程序:
imshow( window_image, src );
// Wait until user exits the program
waitKey();
- Hist_and_Backproj 函数:初始化 cv::calcHist 所需的参数。 bin 的数量来自 Trackbar:
int histSize = MAX( bins, 2 );
float hue_range[] = { 0, 180 };
const float* ranges[] = { hue_range };
- 计算直方图并将其归一化到范围 [0,255]
Mat hist;
calcHist( &hue, 1, 0, Mat(), hist, 1, &histSize, ranges, true, false );
normalize( hist, hist, 0, 255, NORM_MINMAX, -1, Mat() );
- 通过调用函数 cv::calcBackProject 获取同一图像的 Backprojection
Mat backproj;
calcBackProject( &hue, 1, 0, hist, backproj, ranges, 1, true );
- 所有参数都是已知的(与用于计算直方图的相同),只是我们添加了 backproj 矩阵,它将存储源图像的反投影 (&hue)
- 显示反向投影
imshow( "BackProj", backproj );
- 绘制图像的一维色调直方图:
int w = 400, h = 400;
int bin_w = cvRound( (double) w / histSize );
Mat histImg = Mat::zeros( h, w, CV_8UC3 );
for (int i = 0; i < bins; i++)
{
rectangle( histImg, Point( i*bin_w, h ), Point( (i+1)*bin_w, h - cvRound( hist.at<float>(i)*h/255.0 ) ),
Scalar( 0, 0, 255 ), FILLED );
}
imshow( "Histogram", histImg );
Results
这是使用示例图像的输出(猜猜是什么?另一只手)。 您可以使用 bin 值,您将观察它如何影响结果:
R0
R1
R2