小知识:
HSV中的H分量,则大概对光线的变化会不敏感。如果选择的是V分量,当然光线的变量会影响结果了。如果选择的是图像的梯度,那就是检查纹路的了。等等。
工作原理:
简单的讲, 所谓反向投影就是首先计算某一特征的直方图模型,然后使用模型去寻找图像中存在的该特征。
我们使用肤色直方图为例来解释反向投影的工作原理:
假设你已经通过下图得到一个肤色直方图(Hue-Saturation), 旁边的直方图就是模型直方图 (代表手掌的皮肤色调)。你可以通过掩码操作来抓取手掌所在区域的直方图:
下图是另一张手掌图(测试图像) 以及对应的整张图像的直方图:
我们要做的就是使用模型直方图 (代表手掌的皮肤色调) 来检测测试图像中的皮肤区域。以下是检测的步骤:
a.对测试图像中的每个像素 ( p(i,j) ),获取色调数据并找到该色调 在直方图中的bin的位置。
b.查询模型直方图中对应的bin - - 并读取该bin的数值。
c.将此数值储存在新的图像中(BackProjection)。 你也可以先归一化模型直方图,这样测试图像的输出就可以在屏幕显示了。
d.通过对测试图像中的每个像素采用以上步骤, 我们得到了下面的 BackProjection 结果图:
e.使用统计学的语言, BackProjection 中储存的数值代表了测试图像中该像素属于皮肤区域的 概率 。比如以上图为例, 亮起的区域是皮肤区域的概率更大(事实确实如此),而更暗的区域则表示更低的概率(注意手掌内部和边缘的阴影影响了检测的精度)。
backproject的基本过程是:
1. 拿到 特征图像 (或模板图像)
2. 得到 特征图像的直方图
3. 拿到源图像,依据源图像的每个像素的值,在特征图像的直方图中找到对应的值,然后将直方图的值赋给新的图像,backproject算法就完成了。
对于calcBackProjectPatch,整个是基于块的形式,利用直方图做匹配,类似于模板匹配,只不过这些模板转换为直方图,而原图中以某点为基准,抠出来作对比的部分也转换为直方图,两个直方图作匹配,匹配的结果作为此点的值。 结果会是一张概率图,概率越大的地方,代表此区域与模板的相似度越高。而且,当模板小于检测的目标时,得到的结果图也能反映出检测区域的形状。利用直方图的方式,就能去除光照变化、边缘遮挡,旋转等因素的影响。
反向投影用于在输入图像(通常较大)中查找特定图像(通常较小或者仅1个像素,以下将其称为模板图像)最匹配的点或者区域,也就是定位模板图像出现在输入图像的位置。
工作原理2:
反向投影如何查找(工作)?
查找的方式就是不断的在输入图像中切割跟模板图像大小一致的图像块,并用直方图对比的方式与模板图像进行比较。
假设我们有一张100x100的输入图像,有一张10x10的模板图像,查找的过程是这样的:
(1)从输入图像的左上角(0,0)开始,切割一块(0,0)至(10,10)的临时图像;
(2)生成临时图像的直方图;
(3)用临时图像的直方图和模板图像的直方图对比,对比结果记为c;
(4)直方图对比结果c,就是结果图像(0,0)处的像素值;
(5)切割输入图像从(0,1)至(10,11)的临时图像,对比直方图,并记录到结果图像;
(6)重复(1)~(5)步直到输入图像的右下角。
特殊情况怎么样?
如果输入图像和模板图像一样大,那么反向投影相当于直方图对比。如果输入图像比模板图像还小,直接罢工。
cvCalcBackProject()函数
作用:计算反向投影
格式:
void cvCalcBackProject(
IplImage** image,
CvArr* back_project,
const CvHistogram* hist
);
参数:
image
单通道输入图像 (也可以传递 CvMat** ).
back_project
单通道反向投影图像,与输入图像具有同样类型.
hist
直方图——I think应当是模板图像的直方图
函数 cvCalcBackProject 计算直方图的反向投影。 对于所有输入的单通道图像同一位置的像素数组,该函数根据相应的像素数组,放置其对应的直方块的值到输出图像中。用统计学术语,输出图像像素点的值是观测数组在某个分布(直方图)下的概率。 例如,为了发现图像中的红色目标,可以这么做:
1 对红色物体计算色调直方图(I think就是H通道,hsv的h就是色调),假设图像仅仅包含该物体。则直方图有可能有极值,对应着红颜色。
2 对将要搜索目标的输入图像,使用直方图计算其色调平面的反向投影,然后对图像做阈值操作。
3 在产生的图像中发现连通部分,然后使用某种附加准则选择正确的部分,比如最大的连通部分。
反向投影程序
#include <cv.h>
#include <highgui.h>
int main()
{
IplImage* src = cvLoadImage("h1.jpg");
if (src != NULL)
{
IplImage* hsv = cvCreateImage(cvGetSize(src), IPL_DEPTH_8U, 3);
//将bgr图像转化为hsv图像
cvCvtColor(src, hsv, CV_BGR2HSV);
//创建3个单通道的图像,用于将hsv图像分离为3个通道
IplImage* h = cvCreateImage(cvGetSize(src), IPL_DEPTH_8U, 1);
IplImage* s = cvCreateImage(cvGetSize(src), IPL_DEPTH_8U, 1);
IplImage* v = cvCreateImage(cvGetSize(src), IPL_DEPTH_8U, 1);
IplImage* planes[] = {h, s};
//将hsv图像分离
cvCvtPixToPlane(hsv, h, s, v, 0);//相当于cvSplit
//在h通道上设定30个划分度,在s通道上设定32个划分度
int h_bins(30), s_bins(32);
CvHistogram* hist;
{
int hist_size[] = {h_bins, s_bins};
float h_ranges[] = {0, 180};
float s_ranges[] = {0, 255};
//将h通道上的范围,即[0,180],以及s通道上的范围,即[0,255]组织成函数cvCreateHist所需的形式
float* ranges[] = {h_ranges, s_ranges};
//创建一个2维的、h维上划分度为30、s维上划分度为32的均分直方图
hist = cvCreateHist(2, hist_size, CV_HIST_ARRAY, ranges, 1);
}
//通过源图像上的h与s通道上图像数据统计出直方图
cvCalcHist(planes, hist, 0, 0);
//获取得到的直方图的所有数据的和
CvScalar sc = cvSum(hist->bins);
float sum = sc.val[0] + sc.val[1] + sc.val[2] + sc.val[3];
//归一化直方图
cvNormalizeHist(hist, 1.0);
//scale:每一个划分在用于显示的图像上所占的像素点数
int scale = 10;
IplImage* img = cvCreateImage(cvSize(h_bins * scale, s_bins * scale), IPL_DEPTH_8U, 3);
cvZero(img);
//为什么要求最大值呢?由于归一化之后的图像中的像素点的值都不会大于1,那么在人眼看来都是黑色的一片,没法通过图像来直观的感受直方图了,所以在下边会通过将直方图的值和直方图中的最大值做除法,再乘以255,使人眼能看出直方图中数值的变化
float max_value = 0;
cvGetMinMaxHistValue(hist, NULL, &max_value, 0, 0);
for (int h = 0; h < h_bins; ++h)
{
for (int s = 0; s < s_bins; ++s)
{
//访问直方图在关于h与s所确定的颜色的数值
float bin_val = cvQueryHistValue_2D(hist, h, s);
int intensity = cvRound(bin_val * 255 / max_value);
cvRectangle(img,
cvPoint(h * scale, s * scale),
cvPoint((h + 1) * scale - 1, (s + 1) * scale - 1),
CV_RGB(intensity, intensity, intensity),CV_FILLED);
}
}
cvNamedWindow("src");
cvShowImage("src", src);
cvNamedWindow("img");
cvShowImage("img", img);
IplImage* img2 = cvLoadImage("hand2.jpg");
IplImage* hsv2 = cvCreateImage(cvGetSize(img2), IPL_DEPTH_8U, 3);
cvCvtColor(img2, hsv2, CV_BGR2HSV);
IplImage* h2 = cvCreateImage(cvGetSize(img2), IPL_DEPTH_8U, 1);
IplImage* s2 = cvCreateImage(cvGetSize(img2), IPL_DEPTH_8U, 1);
IplImage* v2 = cvCreateImage(cvGetSize(img2), IPL_DEPTH_8U, 1);
IplImage* planes2[] = {h2, s2};
cvCvtPixToPlane(hsv2, h2, s2, v2, 0);
IplImage* backProject = cvCreateImage(cvGetSize(img2), IPL_DEPTH_8U, 1);
cvSetZero(backProject);
cvNormalizeHist(hist, sum);
cvCalcBackProject(planes2, backProject, hist);
cvNamedWindow("img2");
cvShowImage("img2", img2);
cvNamedWindow("back project");
cvShowImage("back project", backProject);
//释放资源
cvWaitKey(0);
cvDestroyAllWindows();
cvReleaseImage(&src);
cvReleaseImage(&img);
cvReleaseImage(&backProject);
cvReleaseImage(&img2);
cvReleaseHist(&hist);
}
return 0;
}
程序理解:
用的是
http://www.cnblogs.com/slysky/archive/2011/10/13/2210745.html
里面的代码——删掉了一些,个人感觉其中有一部分是建立一个我没看懂的直方图,删掉了。
这个程序的思路是先建立一个H-S二维直方图,过程和实例2:建立一个二维直方图(书上的例子)是一毛一样的。接着,将待测图片的h和s通道分离出来,然后反向投影,就可以了。
代码很有用,标准图片和待测图片之间没有大小关系。
特别注意:
使用这个代码,有一点要注意。其中有几句很关键:
//获取得到的直方图的所有数据的和
CvScalar sc = cvSum(hist->bins);
float sum = sc.val[0] + sc.val[1] + sc.val[2] + sc.val[3];
然后,在 cvCalcBackProject(planes2, backProject, hist); 之前,先要有:
cvNormalizeHist(hist, sum);
——很关键,如果删掉,最终结果就是一片漆黑。
为什么会这样呢?
我想了想是这样的,如果归一化了,直方图所有数据之和为1,这个直方图相当于所有数据被压缩了。那么按照这个直方图反向投影,所有的像素都不会超过1——不是真实的情况。
但是如果获取得到的直方图的所有数据的和,然后cvNormalizeHist(hist, sum);就可以反映真实情况了,所以图像可以准确地显示出来。
那么又有一个问题,为什么前面还有一个:
//归一化直方图
cvNormalizeHist(hist, 1.0);
因为这个语句是为了画出H-S二维直方图,所以会有这个。
所用的图片
h1
hand2