openCV笔记——直方图反向投射(Back Projection)详解
备注:本人近期学习图像处理,一路遇到许多问题,多亏得到许多博主博客指点,感激不尽。现学习到直方图反向投射,花了半天时间终于搞懂了,故第一次写此博客记录,如有错误,请网友指正。
一、反向投影概念
反向投影是反应直方图模型在目标图像中的的分布情况,简单来说就是用直方图模型去目标图像中寻找相似的特征对象。
二、反向投影的作用
反向投影的作用是:在输入图像中寻找与模板图像最匹配的区域,也就是定位模板图像出现在输入图像的位置。
三、反向投影的原理
举例:
使用模型直方图(代表手掌的皮肤色调)来检测测试图像中的皮肤区域。以下是检测的步骤:
- 对测试图像中的每个像素P(i,j),获取色调数据并找到该色调h(i,j)在直方图中的bin的位置。
- 查询模型直方图中对应的bin-h(i,j)-并读取该bin的数值。
- 将此数值储存在新的图像中(BackProjection)。可以先归一化模型直方图,这样测试图像的输出就可以在屏慕显示了。
- 通过对测试图像中的每个像素采用以上步骤,就得到了BackProjection 结果图。
如:模板Hue像素为:
直方图的bin为:[0,3],[4,7],[8,11],[12,15]
模型直方图为:4 4 6 2
假设待测图片的Hue为:
(反向投影在某一位置的值就是原图对应位置像素值在在模型直方图中所属bin的值)
反向投影图为:
calcBackProject=
可看出与模型相同特征处较亮。
四、实现步骤与相关API
- 加载图片imread
- 将图像从RGB色彩空间转换到HSV色彩空间cvtColor
- 分割出hue通道mixChannels
- 计算直方图和归一化calcHist与normalize
- Mat与MatND其中Mat表示二维数组,MatND表示三维或者多维数组,此处均可以用Mat表示。
- 计算反向投影图像-calcBackProject
calceackProject(
const Matimages,//输入图像,图像深度必须位CV_8U,CV_16U或CV_32F中的一种,尺寸相同,每一幅图像都可以有任意的通道数
int nimages,//输入图片数量
const intchannels,//用于计算反向投影的通道列表,通道数必须与直方图维度相匹配
InputArray hist,//输入的直方图
OutputArray backProject,//目标反向投影输出图像,是一个单通道图像,与原图像有相同的尺寸和深度
const float **ranges,//直方图中每个维度bin的取值范围(二维数组)
double scale=1,//可选输出反向投影的比例因子
bool uniform =true, //直方图是否均匀分布(uniform)的标识符,有默认值true
)
mixchannels( //通道图像分割
const Matsrc,//输入数组或向量矩阵,所有矩阵的大小和深度必须相同。(指针形式)
size_t nsrcs,//矩阵的数量
Matdst,//输出数组或矩阵向量,大小和深度必须与src[0]相同(指针形式)
size_t ndsts,//输出矩阵的数量
const int*fromTo,//指定被复制通道与要复制到的位置组成的索引对
size_t npairs //fromTo中索引对的数目
)
五、代码演示
/*直方图反向投射
利用图片hand.jpg的hue通道的直方图hist作为模板,去图片hand1.jpg中的hue通道寻找相似特征
*/
#include <iostream>
#include <opencv2/opencv.hpp>
#include<math.h>
using namespace std;
using namespace cv;
char inputTitle[]="input demo";
char outputTitle[]="BackPro ject demo";
Mat model,test;
int bins=50;/区间数(级数)
void Hist_And_BackPro ject(int,void*);
int main(char argc,char**argv)
{
model=imread("D:\\OpenCV\\VS_opencv\\images\\hand.jpg");
if(!model.data)
{
cout<<"couldn't load image..."<<end1;
return-1;
}
imshow("model demo",model);
test=imread("D:\\ OpenCV\\ VS_opencv\\ images\\ hand1. jpg");
if (! test. data)
{
cout<<"couldn't load image..."<<end1;
return-1;
}
imshow("test demo", test);
namedWindow(outputTitle, WINDOW_AUTOSIZE);
createTrackbar("bins Value:", outputTitle,& bins,180, Hist_And_BackProject);
Hist_And_BackPro ject(0,0);
return 0;
}
void Hist_And_BackProject(int,void*)
{
//分离hue通道
Mat hsv,hue,hsv1,hue1;
//mode1:hand
cvtColor(model,hsv,COLOR_BGR2HSV);
hue.create(hsv.size),hsv.depth());
int nchannels[]={0,0};//索引对(hsv的0通道分离到hue的0通道)
mixchannels(&hsv,1,&hue,1,nchannels,1);
//test:hand1
cvtColor(test,hsvl,COLOR_BGR2HSV);
huel.create(hsvl.size(),hsv.depth));//索引对(hsv的0通道分离到hue的0通道)
mixChannels(&hsvl,1,&huel,1,nchannels,1);
//计算直方图
Mat hist;/输出直方图矩阵
int histsize=max(bins,2);//级数(至少2级)
float range[]={0,180};
const float*Range[]={range};//取值范围
calcHist(&hue,1,0,Mat(),hist,1,&histsize,Range,true,false);//直方图计算
normalize(hist,hist,0,255,NORM_MINMAX,-1,Mat());∥归一化
//反向投影
Mat BackProject;
calcBackPro ject(&hue1,1,0,hist,BackProject,Range,1,true);
imshow(outputTitle,BackProject);
//绘制直方图
int hist_w=512;
int hist h=512;
Mat hist_draw(hist_w,hist_h,CV_8UC3,Scalar(0,0,0));//绘制画布
int bin w=cvRound(hist w/bins);//每个区间的宽度
for(size_ti=0;i<bins;i++)
/*矩形
//Rect类:成员变量x、y、width、height,分别为左上角点的坐标和矩形的宽和高
//因为图片的(0,0)点在左上角,所以纵坐标要用总高度减去算出的高度
Rect rect=Rect(i*binw,hist_h-cvRound(hist.at<float>(i)*hist_h/255),bin_w,cvRound(hist.at<float>(i)*hist_h/255));
rectangle(hist_draw,rect,Scalar(255,0,0),2,LINE_AA);
*/
//填充(fi11Poly)
//因为图片的(0,0)点在左上角,所以纵坐标要用总高度减去算出的高度
Point pts[1][5];
pts[0][0]=Point(i*bin w,hist h-cvRound(hist.at<float>(i)*hist h/255));
pts[0][1]=Point((i+1)*bin_w,hist_h-cvRound(hist.at<float>(i)*hist_h/255));
pts[0][2]=Point((i+1)*bin w,hist_h);
pts[0][3]=Point(i*bin w,hist h);
pts[0][4]=Point(i*bin w,hist_h-cvRound(hist.at<float>(i)*hist_h/255));
const Point*ppts[]={pts[0]};
int npt[]={5};fil1Poly(hist_draw,ppts,npt,1,Scalar(0,0,255),LINE_AA);
imshow("Hist demo",hist_draw);
waitKey(0);
return;
}
结果
后记:学疏才浅,如有错误,忘指正!
感谢贾志刚老师的《opencv基础教程》视频教程。