OpenCV学习(二十二) :反向投影:calcHist(),minMaxLoc(),compareHist()
参考博客:
反向投影backproject的直观理解opencv 反向投影颜色直方图的计算、显示、处理、对比及反向投影
一、概述
1、官方解释:反向投影是一种记录给定图像中的像素点如何适应直方图模型像素分布的方式。简单的讲,
1)就是首先计算某一特征的直方图模型;(特征可以为色调+饱和度、灰度值等等)
2)然后使用模型去寻找图像中存在的该特征。例如,你有一个肤色直方图(Hue-Saturation直方图),你可以用它(色调和饱和度)来寻找图像中的肤色区域:
2、反向投影图就是图像对应位置像素的数量统计,也可以看做是密度统计。 反向投影图在某一位置(点)的值是原图对应位置(点)的像素值所在原图区间(bins)的总数目。(所以,一个区间点越多,在反向投影矩阵中就越亮。)
3、反向投影中的“反向”指的是从直方图值到反向投影矩阵映射的过程。
4、通过反向投影,原始的图像被简化了,而这个简化的过程实际上就是提取出图像的某个特征。所以我们就可以用这个特征来对比两幅图,如果两幅图的反向投影矩阵相似或相同,那么我们就可以判定这两幅图这个特征是相同的。
5、反向投影的作用:
反向投影用于在输入图像(通常较大)中查找**特定图像(通常较小或者仅1个像素,以下将其称为模板图像)**最匹配的点或者区域,也就是定位模板图像出现在输入图像的位置。如图:
第一个图为源图像,中间的那个小图像是产生用于反向投影的直方图的图像,最后的用直方图均衡化后的结果图像,可以看到,苹果的像素位置几被找到了。
二、calcBackProject()函数
calcBackProject 的基本过程是:
1)拿到 特征图像 (或模板图像)
2)得到 特征图像的直方图
3)拿到测试图像,依据测试图像的每个像素的值,在特征图像的直方图中找到对应的值,然后将直方图的值赋给新的图像,backproject算法就完成了。
void cv::calcBackProject(
const Mat* images // 输入图像,图像深度必须位CV_8U,CV_16U或CV_32F中的一种,尺寸相同,每一幅图像都可以有任意的通道数
int nimages // 输入图像的数量
const int* channels // 用于计算反向投影的通道列表,通道数必须与直方图维度相匹配,第一个数组的通道是从0到image[0].channels()-1,
InputArray hist // 输入的直方图,直方图的bin可以是密集(dense)或稀疏(sparse)
OutputArray backProject // 目标反向投影输出图像,是一个单通道图像,与原图像有相同的尺寸和深度
const float ranges** // 直方图中每个维度bin的取值范围
double scale=1: // 可选输出反向投影的比例因子
bool uniform=true: // 直方图是否均匀分布(uniform)的标识符,有默认值true
)
三、CalcBackProjectPatch()函数
对于calcBackProjectPatch,整个是基于块的形式,利用直方图做匹配,类似于模板匹配,只不过这些模板转换为直方图,而原图中以某点为基准,抠出来作对比的部分也转换为直方图,两个直方图作匹配,匹配的结果作为此点的值。
结果会是一张概率图,概率越大的地方,代表此区域与模板的相似度越高。而且,当模板小于检测的目标时,得到的结果图也能反映出检测区域的形状。这个结果与模板匹配的结果很相似,但利用直方图的方式,就能去除光照变化、边缘遮挡,旋转等因素的影响。另外
基于块的反向投影。这种方法速度很慢,模版图像别弄的太大了。
例如:
1)当模板图像小与目标的时候,作为区域检测器,测试如下:可以找到手区域
2)当模板等于目标的时候,测试如下:输出图像,较亮的部分就是人的头部大致位置
详情参考:
opencv 直方图反向投影
函数原型:
void cvCalcBackProjectPatch(
IplImage** image, // 输入图像:是一个单通道图像数组,而非实际图像
CvArr* dst, // 输出结果:是一个单通道32位浮点图像,它的宽度为W-w+1,高度为H-h+1,这里的W和H是输入图像的宽度和高度,w和h是模板图像的宽度和高度
CvSize patch_size, // 模板图像的大小:宽度和高度
CvHistogram* hist, // 模板图像的直方图:直方图的维数和输入图像的个数相同,并且次序要一致;例如:输入图像包含色调和饱和度,那么直方图的第0维是色调,第1维是饱和度
int method, // 对比方式:跟直方图对比中的方式类似,可以是:CORREL(相关)、CHISQR(卡方)、INTERSECT(相交)、BHATTACHARYYA
float factor // 归一化因子,一般都设置成1,否则很可能会出错;中文、英文以及各路转载的文档都错了,这个参数的实际类型是double,而非float,我看了源代码才搞定这个地方
)
四、mixChannels()函数
mixChannels()函数用于将输入数组的指定通道复制到输出数组的指定通道。
其实我们接触到的,split()和merge(),以及cvtColor的某些形式,都只是mixChannels()的一部分。
参考:
opencv3/C++ mixChannels()详解:4通道图像分割、HSV通道获取
void mixChannels(
const Mat* src, //输入数组或向量矩阵,所有矩阵的大小和深度必须相同。
size_t nsrcs, //第一个参数src矩阵的数量
Mat* dst, //输出数组或矩阵向量,大小和深度必须与src[0]相同
size_t ndsts, //第三个参数ndsts矩阵的数量
const int* fromTo, //指定被复制通道与要复制到的位置组成的索引对
size_t npairs //fromTo中索引对的数目
);
示例:
#include<opencv2/opencv.hpp>
using namespace cv;
int main()
{
Mat bgra( 500, 500, CV_8UC4, Scalar(255,255,0,255) );
Mat bgr( bgra.rows, bgra.cols, CV_8UC3 );
Mat alpha( bgra.rows, bgra.cols, CV_8UC1 );
Mat out[] = { rgb, alpha };
int from_to[] = { 0, 2, 1, 1, 2, 0, 3, 3 };
// 输入1一个矩阵,输出2个举证,4个索引对
mixChannels( &bgra, 1, out, 2, from_to, 4 );
imshow("bgra", bgra); // 青色
imshow("bgr", bgr); // 黄色(通道值改变,色彩空间不变)
waitKey(0);
return 0;
}
五、示例:
使用模型直方图(代表手掌的皮肤色调)来检测测试图像中 的皮肤区域。
1)对测试图像中的每个像素(p(i,j),获取色调数据并找到该色调(h(i,j),s(i,j))在直方图中的bin的位置;
2)查询模型直方图中对应的bin-(hi,j,si,j)并读取该bin的数值;
3)将此数值存储在新的图像中(BackProjection)。你也可以先归一化模型直方图,这样测试图像的输出就可以在屏幕显示了;
4)通过对测试图像中的每个像素采用以上步骤,我们得到了下面的BackProjection结果图:
5)使用统计学的语言,BackProjection中存储的数值代表了测试图像中该像素属于皮肤区域的概率。比如上图为例,亮起的区域是皮肤区域的概率更大,而更暗的区域则表示更低的概率(注意手掌内部和边缘的阴影影响了检测的精度)。
#include <opencv2/opencv.hpp>
using namespace cv;
using namespace std;
Mat g_srcImage,g_hsvImage,g_hueImage;
int g_bins = 30;//直方图组距
int main()
{
// 1、载入源图,转化为HSV颜色模型
g_srcImage = imread("F:/C++/2. OPENCV 3.1.0/TEST/shou.jpg", 1);
if(!g_srcImage.data ) { printf("读取图片错误,请确定目录下是否有imread函数指定图片存在~! \n"); return false; }
cvtColor( g_srcImage, g_hsvImage, CV_BGR2HSV ); // 转换颜色空间
// 2、分离 Hue 色调通道
g_hueImage.create( g_hsvImage.size(), g_hsvImage.depth() ); // 创建同尺寸、深度的单通道图
int ch[ ] = { 0, 0 };
mixChannels( &g_hsvImage, 1, &g_hueImage, 1, ch, 1 );
imshow("g_hueImage", g_hueImage);
// 3、创建 Trackbar 来输入bin的数目
namedWindow( "反向投影图" , CV_WINDOW_AUTOSIZE );
createTrackbar("色调组距 ", "反向投影图" , &g_bins, 180, on_BinChange );
on_BinChange(0, 0);//进行一次初始化
// 4、显示效果图
imshow( "【原始图】" , g_srcImage );
waitKey(0);
return 0;
}
直方图计算 bins(特征空间子区段的数目)回调函数:
void on_BinChange(int, void* )
{
// 1、参数准备
MatND hist;
int histSize = MAX( g_bins, 2 ); // 组距 最小为2
float hue_range[] = { 0, 180 };
const float* ranges = { hue_range };
// 2、计算直方图并归一化
// 将handhue取值替换为g_hueImage图像的中心部分,作为特征图像
Mat handhue_feature= g_hueImage(Rect(g_hueImage.rows/2-25, g_hueImage.cols/2-25, 50, 50)).clone();// g_hueImage
imshow("handhue_feature", handhue_feature); // 显示特征
// 将handhue_feature的取值 替换为g_hueImage图像的中心部分,作为特征图像
calcHist( &handhue_feature, 1, 0, Mat(), hist, 1, &histSize, &ranges, true, false );
normalize( hist, hist, 0, 255, NORM_MINMAX, -1, Mat() ); // 归一化直方图数值范围为0~255之间
// 3、计算 获取反向投影
MatND backproj;
calcBackProject( &g_hueImage, 1, 0, hist, backproj, &ranges, 1, true );
// 4、显示反向投影
imshow( "反向投影图", backproj );
// 5、绘制直方图的参数准备
int w = 400; int h = 400;
int bin_w = cvRound( (double) w / histSize );
Mat histImg = Mat::zeros( w, h, CV_8UC3 );
// 6、绘制直方图
for( int i = 0; i < g_bins; i ++ )
{ rectangle( histImg, Point( i*bin_w, h ), Point( (i+1)*bin_w, h - cvRound( hist.at<float>(i)*h/255.0 ) ), Scalar( 100, 123, 255 ), -1 ); }
// 7、显示直方图窗口
imshow( "直方图", histImg );
}
源图+色调图
特征图: