反向投影是计算像素和直方图模型中像素吻合度的一种方法。
例如有肤色的直方图,可以使用反向投影在图像中寻找肤色区域,用来做这种寻找操作的函数有两种形式,一个是为稠密数组而设计的,另一个为稀疏数组设计的。
和cv::calcHist()类似,反向投影从输入图像的指定通道中计算出一个向量。不同之处在于cv::calcHist()在向直方图中记录累计值,反向投影从输入的直方图中读取当前像素对应的计数值作为结果。
从统计学的视角来看,如果将输入的直方图视为某个物体上特定的向量的一个概率分布,那么反向投影就是计算图片上某个特定部分来自该先验分布的概率。
函数声明:
void cv::calcBackProject(
const cv::Mat* images, // C-Style of images ,8U or 32F
int nimages, // number of images in 'images' array
const int* channels, // C-style list,ints indentifying channels
cv::InputArray hist, // input histogram array
cv::OutputArray bacProject, // output single channel array
const float** ranges, // C-style array, 'dims' pairs of bin sizes
double scale = 1, // optional scale factor for output
bool uniform = true // true for uniform binning
);
void cv::calcBackProject(
const cv::Mat* images, // C-style array of images, 8U or 32F
int nimages, // number of images in 'images' array
const int* channels, // C-style list, ints indentifyinf channels
const cv::SparseMat& hist, // input(sparse) histogram array
cv::OutputArray backProject, // output single channel array
const float** ranges, // C-style array, 'dims' pairs of bin sizes
double scale = 1, // optional scale factor for output
bool uniform = true // true for uniform binning
);
void cv::calcBackProject(
cv::InputArrayOfArrays images, // STL-vector of images,8U or 32F
const vector<int>& channels, // STL-vector,channels indices
cv::InputArray hist, // input histogram array
cv::OutputArray backProject, // output single channel array
const vector<float>& ranges, // STL-style vector range boundaries
double scale = 1, // optional scale factor for output
bool uniform = true // true for uniform binning
);
可以看到有三种版本的cv::calcBackProject(),前两个使用C风格数组作为输入,分别支持稠密直方图和稀疏直方图,第三个使用基于模板的输入。
不管是哪种情况,都应以单通道或是多通道数组集合的形式提供图像,同时以cv::calcHist()输出的直方图的格式传入直方图。
单通道数组集合恰和调用cv::calcHist()时所传入的第一个参数具有相同的形式,只是此时表示要和直方图做比较的图片。如果参数images是一个C风格数组,需要传递nimages指明images数组中元素的个数。
参数channel是通道的列表,该参数的形式和调用cv::calcHist()时对用的参数形式相同。虽然channels元素的个数等于直方图hist的维数,但该数组并不要求等于images中的数组数(或者说images中通道的总数)。
反向计算的结果保存在backProject中,大小类型和images[0]相同,只有一个通道。
参数ranges与cv::calcHist()计算直方图时传入的区间信息ranges一致。
参数scale是一个可选的应用于backProject的缩放因子(需要结果可视化时有用)。
参数uniform表示输入的直方图是不是一个均匀的直方图,和cv::calcHist()中的意义相同。默认=true表示传入的直方图是均匀的,在传入的直方图是非均匀时需要设置为false。
该函数使用与cv::calcHist()极其类似,示例使用参考如下:
通过滑动条控制直方图的区间个数,读取另一张图4.jpg,计算出它的反向投影,并计算该反向投影的直方图,并与argv[1]图片的直方图比较。
#include <stdio.h>
#include <opencv.hpp>
#include <vector>
using namespace std;
using namespace cv;
#define WINDOW_NAME "原始图"
void on_trackbar(int pos,void *param);
int main(int argc, char* argv[])
{
int nPos = 30;
int nMaxPos = 180;
cv::Mat srcImg = cv::imread(argv[1],cv::IMREAD_COLOR);
if(!srcImg.data)
{
printf("Load image fail,%s\r\n",argv[1]);
return -1;
}
cv::Mat hsv;
cv::cvtColor(srcImg,hsv,cv::COLOR_BGR2HSV);
//calcTwoDims(hsv,argv);
int ch[] = {0,0};
cv::Mat hueImg, histImg;
hueImg.create(hsv.size(),hsv.depth());
cv::mixChannels(&hsv,1,&hueImg,1,ch,1);
cv::namedWindow(WINDOW_NAME);
cv::Mat imgs[] = {hueImg,histImg};
cv::createTrackbar("hue",WINDOW_NAME,&nPos,nMaxPos,on_trackbar,(void*)imgs);
cv::imshow(WINDOW_NAME,srcImg);
cv::waitKey(0);
return 0;
}
void on_trackbar(int pos,void *param)
{
cv::Mat imgs[3] = {*(cv::Mat*)param};
cv::Mat hueImg = imgs[0];
cv::Mat hist;
cv::Mat backPro = imgs[1];
// 计算直方图
int histSize = max(pos,2); // 区间个数
float h_range[] = {0,180};
const float *histRange[] = {h_range};
cv::calcHist(&hueImg,1,0,cv::noArray(),hist,1,&histSize,histRange);
cv::normalize(hist,hist,0,255,cv::NORM_MINMAX);
// 计算反向投影
cv::Mat srcImg = cv::imread("4.jpg",cv::IMREAD_COLOR);
if(!srcImg.data)
{
printf("Load image fail,%s\r\n","4.jpg");
return;
}
cv::Mat hsv;
cv::cvtColor(srcImg,hsv,cv::COLOR_BGR2HSV);
int ch[] = {0,0};
cv::Mat hueImg1;
hueImg1.create(hsv.size(),hsv.depth());
cv::mixChannels(&hsv,1,&hueImg1,1,ch,1);
cv::calcBackProject(&hueImg1,1,0,hist,backPro,histRange);
cv::Mat backHist;
cv::calcHist(&backPro,1,0,cv::noArray(),backHist,1,&histSize,histRange);
cv::normalize(backHist,backHist,0,255,cv::NORM_MINMAX);
printf("\nhue_%d:",pos);
for(int i=0;i<4;i++)
{
printf("method[%d] = %f\t",i,cv::compareHist(hist,backHist,i));
}
vector<cv::Vec3f> sigv;
for(int i=0;i<histSize;i++)
{
float hval = hist.at<float>(i);
if(hval != 0)
sigv.push_back(cv::Vec3f(hval,(float)i,0));
}
cv::Mat sig0 = cv::Mat(sigv).clone().reshape(1);
sigv.clear();
for(int i=0;i<histSize;i++)
{
float hval = backHist.at<float>(i);
if(hval != 0)
sigv.push_back(cv::Vec3f(hval,(float)i,0));
}
cv::Mat sig = cv::Mat(sigv).clone().reshape(1);
printf("EMD = %f",cv::EMD(sig0,sig,CV_DIST_L2));
// 绘制直方图
int h=400;
int w=400;
int bin_w = cvRound((double)w/histSize);
cv::Mat histImg = cv::Mat::zeros(h,w,CV_8UC3);
for(int i=1;i<histSize;i++)
{
cv::rectangle(
histImg,
cv::Point((i-1)*bin_w,h),
cv::Point(i*bin_w,h - cvRound(hist.at<float>(i)*h/255.0)),
cv::Scalar(100,133,255),
-1
);
}
cv::imshow("反射投影",backPro);
cv::imshow("直方图",histImg);
}
运行结果:改变直方图区间个数,可以看到结果也有明显变化