第一:反向投影
反向投影(back projection)是一种记录像素点(为cvCalcBackProjection)或像素块(为cvCalcBackProjecitPatch)如何适应直方图模型中分布的方式。
函数原型:void cvCalcBackProject(IplImage** image, CvArr* back_project, const CvHistogram* hist);
image:与输入图像的大小一样,而且也是单通道,8位或浮点图像。
back_project:设置为hist中的相关bin的值。如果直方图是归一化的,此值便与一个条件概率值相关(即图像中像素点为直方图hist所表征的某种成员的概率)。
第二:基于块的反向投影
我们可以用函数cvCalcBackProject计算一个像素是否是一个已知目标的一部分,也可以用函数cvCalcBackProjectPatch计算一块区域是否包含已知的目标。函数cvCalcBackProjectPatch在整个输入图像使用一个滑动窗口。在输入图像矩阵的每一个位置,块中所有的像素点都被设置为在目标图像中对应的块中心位置的像素点。这一点非常重要,因为图像的许多特性(如纹理)在单一的像素级别上无法确定,但可以从一组像素确定。cvCalcBackProjectPatch有两种用法:但采样窗口小于目标时,作为一个区域检测器,当采样窗口和目标一般大时,作为目标检测器。
函数原型:void cvCalcBackProjectPatch(IplImage** images, CvArr* dst, CvSize patch_size, CvHistogram* hist, int method, float factor);
images:源图像
dst:目标图像,单通道浮点型图像。
patch_size:目标大小为:images[0][0].width-patch_size.x+1,images[0][0].height-patch_size.y+1。
factor:归一化水平
代码:
#include <cv.h>
#include <highgui.h>
#include <iostream>
#include <opencv2/legacy/legacy.hpp>
using namespace cv;
using namespace std;
//全局变量-->--->-->--->-->--->-->--->输入的两张图片的大小要一样的/
const char * soutceFile_InDoor = "D:\\VC98\\C++项目\\opencv\\page245.6\\page245.6\\hand2.jpg";
IplImage *image_Source = cvLoadImage(soutceFile_InDoor, CV_LOAD_IMAGE_UNCHANGED);
const char * soutceFile_Dst = "D:\\VC98\\C++项目\\opencv\\page245.6\\page245.6\\hand3.jpg";
IplImage *image_Dst = cvLoadImage(soutceFile_Dst, CV_LOAD_IMAGE_UNCHANGED);
IplImage * image_mask = cvCreateImage(cvSize(image_Source->width+2,image_Source->height+2), IPL_DEPTH_8U, 1);//②注意mask图像的大小
const char * mask_windowName = "掩码图像窗口";
//<--<--<--<--<--<--<--<--<--全局变量/。
void DrawHistogram(IplImage ** image_hist, const CvHistogram * histogram, int scaleValue);
void onTrackbarSlide_low(int pos);
void onTrackbarSlide_up(int pos);
void GetMaskImage(int pos, bool isUpValue);
CvHistogram * CalcHSHistogramByMask(const IplImage* image_RGB, IplImage *mask);
void CalcHSBackProject(const IplImage* image_RGB, const CvHistogram *HSHist);
//<--<--<--<--<--<--<--<--<--函数声明//
int main()
{
const char * src_windowName = "原始图像窗口";
cvNamedWindow(src_windowName, 0);//CV_WINDOW_AUTOSIZE
int slider_position_low = 5;
int slider_position_up = 30;
cvCreateTrackbar("loDiff", src_windowName, &slider_position_low, 255, onTrackbarSlide_low);
cvCreateTrackbar("upDiff", src_windowName, &slider_position_up, 255, onTrackbarSlide_up);
cvShowImage(src_windowName, image_Source);
cvSaveImage("image_Source_result.jpg",image_Source);
//接下来在TrackBar的回调函数中完成后续的一系列操作
cvWaitKey();
cvReleaseImage(&image_Source);
cvReleaseImage(&image_Dst);
cvReleaseImage(&image_mask);
cvDestroyAllWindows();
return 0;
}
//目前只实现绘制二维直方图
void DrawHistogram(/*IplImage ** image_hist,*/ const CvHistogram * histogram, int scaleValue)
{
const char * draw_histWindow = "DrawHist";
cvNamedWindow(draw_histWindow,0);//CV_WINDOW_AUTOSIZE
//直方图:横坐标表示各个bin,纵坐标表示各个bin归一化后的值
int hist_dims = histogram->mat.dims;
int bin_size1, bin_size2/*, bin_size3*/;
if (hist_dims == 2)
{
bin_size1 = histogram->mat.dim[0].size;
bin_size2 = histogram->mat.dim[1].size;
//bin_size3 = histogram->mat.dim[2].size;
}
else
{
return;
}
int bin_count = bin_size1*bin_size2/**bin_size3*/;
float max_temp;
cvGetMinMaxHistValue(histogram, NULL, &max_temp);
int max_value = (int)(max_temp*scaleValue) + 1;
CvSize hist_imageSize = cvSize(bin_count, max_value);
IplImage*image_hist = cvCreateImage(hist_imageSize, IPL_DEPTH_8U, 1);
(image_hist)->origin = 1;
cvZero(image_hist);
int x;
int value;
for (int r = 0; r < bin_size1; ++r)
{
for (int g = 0; g < bin_size2; ++g)
{
//for (int b = 0; b < bin_size3; ++b)
{
//x = r*(bin_size1*bin_size2) + g*bin_size2 + b;
x = r* bin_size1 + g;
value = (int)(cvQueryHistValue_2D(histogram, r, g)*scaleValue);
//value = (int)(cvQueryHistValue_3D(histogram, r, g, b)*scaleValue);
/* if (value == 0)
{
value = 10;
}*/
cvRectangle(image_hist, cvPoint(x, 0), cvPoint(x, value), cvScalar(255));
}
}
}
cvShowImage(draw_histWindow, image_hist);
cvSaveImage("image_hist_result.jpg",image_hist);
}
void onTrackbarSlide_low(int pos)
{
GetMaskImage(pos, false);
}
void onTrackbarSlide_up(int pos)
{
GetMaskImage(pos, true);
}
void GetMaskImage(int pos ,bool isUpValue)
{
static int lowValue = 0;
static int upValue = 0;
CvPoint seedPoint = cvPoint(image_Source->width / 3 * 2, image_Source->height / 3 * 2);
if (isUpValue == true)
{
upValue = pos;
}
else
{
lowValue = pos;
}
if (lowValue > upValue)
{
lowValue ^= upValue ^= lowValue ^= upValue;//交换两个变量的值
}
int flags = 8
| CV_FLOODFILL_MASK_ONLY
| CV_FLOODFILL_FIXED_RANGE
| (255 << 8);
cvZero(image_mask);//①重要
cvNamedWindow(mask_windowName, 0);//CV_WINDOW_AUTOSIZE
//漫水填充找掩码
cvFloodFill(image_Source, seedPoint, cvScalar(255, 255, 255), cvScalar(lowValue, lowValue, lowValue), cvScalar(upValue, upValue, upValue), NULL, flags, image_mask);
//使用掩码计算直方图
CvRect rect_ROI = cvRect(1, 1, image_mask->width - 2, image_mask->height - 2);
cvSetImageROI(image_mask, rect_ROI); //③
cvShowImage(mask_windowName, image_mask);
cvSaveImage("image_mask_result.jpg",image_mask);
CvHistogram * hist_ImageSource = CalcHSHistogramByMask(image_Source, image_mask);
cvResetImageROI(image_mask);
//反向投影
CalcHSBackProject(image_Dst, hist_ImageSource);
cvNormalizeHist(hist_ImageSource, 1.0);//重要:注意归一化直方图与反向投影的顺序
DrawHistogram(hist_ImageSource, 3000);
cvReleaseHist(&hist_ImageSource);
}
CvHistogram * CalcHSHistogramByMask(const IplImage* image_RGB,IplImage *mask)
{
IplImage* image_HSV = cvCreateImage(cvGetSize(image_RGB), IPL_DEPTH_8U, 3);
cvCvtColor(image_RGB, image_HSV, CV_BGR2HSV);
IplImage* h_plane = cvCreateImage(cvGetSize(image_RGB), IPL_DEPTH_8U, 1);
IplImage* s_plane = cvCreateImage(cvGetSize(image_RGB), IPL_DEPTH_8U, 1);
IplImage* v_plane = cvCreateImage(cvGetSize(image_RGB), IPL_DEPTH_8U, 1);
IplImage* planes[] = { h_plane, s_plane };
cvCvtPixToPlane(image_HSV, h_plane, s_plane, v_plane, 0);
int dims = 2;
int h_bins = 30, s_bins = 32;
CvHistogram* hist;
int hist_size[] = { h_bins, s_bins };
float h_ranges[] = { 0, 180 }; // hue is [0,180]
float s_ranges[] = { 0, 255 };
float* ranges[] = { h_ranges, s_ranges };
hist = cvCreateHist(dims,hist_size,CV_HIST_ARRAY,ranges,1);
cvCalcHist(planes, hist, 0, mask);
cvReleaseImage(&h_plane);
cvReleaseImage(&s_plane);
cvReleaseImage(&v_plane);
cvReleaseImage(&image_HSV);
return hist;
}
void CalcHSBackProject(const IplImage* image_RGB, const CvHistogram *HSHist)
{
IplImage* image_HSV = cvCreateImage(cvGetSize(image_RGB), IPL_DEPTH_8U, 3);
cvCvtColor(image_RGB, image_HSV, CV_BGR2HSV);
IplImage* h_plane = cvCreateImage(cvGetSize(image_RGB), IPL_DEPTH_8U, 1);
IplImage* s_plane = cvCreateImage(cvGetSize(image_RGB), IPL_DEPTH_8U, 1);
IplImage* v_plane = cvCreateImage(cvGetSize(image_RGB), IPL_DEPTH_8U, 1);
IplImage* planes[] = { h_plane, s_plane };
cvCvtPixToPlane(image_HSV, h_plane, s_plane, v_plane, 0);
IplImage * image_backProject = cvCreateImage(cvGetSize(image_RGB), IPL_DEPTH_8U, 1);
cvZero(image_backProject);
cvCalcBackProject(planes, image_backProject, HSHist);
cvNamedWindow("反向投影", 0);//CV_WINDOW_AUTOSIZE
cvShowImage("反向投影", image_backProject);
cvSaveImage("image_backProject_result.jpg",image_backProject);
cvReleaseImage(&h_plane);
cvReleaseImage(&s_plane);
cvReleaseImage(&v_plane);
cvReleaseImage(&image_HSV);
}
输入两张原图:
结果:
掩码图像
反向投影
直方图
几个注意事项:
①可以通过掩码操作来抓取手掌所在区域的直方图,也可以使用类似Cognex拖拽矩形的方式选取兴趣区域,获得目标颜色直方图,利用本章内容,实现颜色识别
②注意:cvFloodFill不会覆盖mask的非零像素点,因此,如果不希望mask阻碍填充操作时,将其中元素设为0,即代码中的 cvZero(image_mask);//①重要 ,如果没有这行,会发现掩码图像是一张黑白相间的竖线组成的图片
③注意:cvFloodFill填充时,mask图像必须是一个单通道、8位、像素宽度和高度均比源图像大两个像素的图像。
④但对于cvCalcHist的掩码,又要求掩码图像必须与用于计算的各plane图像相同大小,所以,要对cvFloodFill得到的掩码图像设置兴趣区域,当然,如果mask是矩阵,可以利用OpenCV的Range函数直接得到
⑤注意169行和171行计算方向投影和归一化直方图的先后顺序,如果先归一化直方图,反向投影得不到理想图像,尽管不算错,《学习OpenCV》针对此有如下描述:如果直方图是归一化的吗,此值便与一个条件概率值相关(即图像中像素点为直方图hist所表征的某种成员的概率)
前几道题“肤色直方图”的计算不准确,应该用本题这种方式 ,在前面各篇中著注明