【图像处理】OpenCV系列三十三--- 直方图与反向投影详解
一、直方图(calcHist)
1、函数原型
// 原型一
void calcHist(const Mat *images,
int nimages,
const int *channels,
InputArray mask,
OutputArray hist,
int dims,
const int *histSize,
const float **ranges,
bool uniform = true,
bool accumulate = false)
// 原型二
void calcHist(const Mat *images,
int nimages,
const int *channels,
InputArray mask,
SparseMat &hist,
int dims,
const int *histSize,
const float **ranges,
bool uniform = true,
bool accumulate = false)
// 原型三
void calcHist(InputArrayOfArrays images,
const std::vector<int> &channels,
InputArray mask,
OutputArray hist,
const std::vector<int> &histSize,
const std::vector<float> &ranges,
bool accumulate = false)
2、函数功能
计算一幅或多幅图像的直方图,在元组中增量一个直方图的时候,就是从输入图像组中的原位置提取一幅图像,并计算出它的直方图,并添加到元组中。
当参数dims>1时,输出矩阵Hist是二维矩阵;
3、参数详解
-
第一个参数,const Mat *images, 源图像,它们有同样的深度,CV_8U或 CV_32F ,以及相同的尺寸;图像阵列中的每一个图像都可以有任意多个通道;
-
第二个参数,int nimages,源图像的个数;
-
第三个参数,const int *channels, 维度通道序列,第一幅图像的通道标号从0~image[0].channels( )-1。Image[0]表示图像数组中的第一幅图像,channels()表示该图像的通道数量。同理,图像阵列中的第二幅图像,通道标号从image[0].channerls( )开始,到image[1].channels( )-1为止;第三、四幅图像的通道标号顺序依此类推;也就是说图像阵列中的所有图像的通道根据图像排列顺序,排成一个通道队列;
-
第四个参数,InputArray mask,可选择的mask。如果该矩阵不空的话,它必须是一个8-bit的矩阵,与images[i]同尺寸。在图像中,只有被mask覆盖的区域的像素才参与直方图统计。如果这个参数想用默认值,输入Mat()就可以了;
-
第五个参数,OutputArray hist,输出直方图, 它是一个稠密或稀疏矩阵,具有dims个维度;
-
第六个参数,int dims, 直方图的维度,一定是正值;
-
第七个参数,const int *histSize,数组,即histSize[i]表示第i个维度上bin的个数;这里的维度可以理解为通道;
-
第八个参数,const float **ranges,当uniform=true时,ranges是多个二元数组组成的数组;当uniform=false时,ranges是多元数组组成的数组。当在每个维度(或通道)上每个直方条等宽时,即uniform=true时,灰度值的有效统计范围的下界用L0表示,上界用UhistSize[i]-1表示,角标中的i表示第i个维度(或通道),上下界值可以表示为hrange[i]={ L0, UhistSize[i]-1}, 在统计时, L0和UhistSize[i]-1不在统计范围内。而ranges={ hrange[0], hrange[1], …… , hrange[dims]}。ranges的元素个数由参数dims决定;
-
第九个参数,bool uniform = true,标识,用于说明直方条bin是否是均匀等宽的;
-
第十个参数,bool accumulate = false,累积标识。如果该项设置,当直方图重新分配时,直方图在开始清零。这个特征可以使你通过几幅图像,累积计算一个简单的直方图,或者及时更新直方图;
二、归一化处理(normalize)
1、函数原型
// 原型一
void normalize(InputArray src,
InputOutputArray dst,
double alpha = 1,
double beta = 0,
int norm_type = NORM_L2,
int dtype = -1,
InputArray mask = noArray())
// 原型二
void normalize(const SparseMat &src,
SparseMat &dst,
double alpha,
int normType)
2、函数功能
归一化就是要把需要处理的数据经过处理后(通过某种算法)限制在你需要的一定范围内;
3、参数详解
-
第一个参数,InputArray src,输入数组;
-
第二个参数,InputOutputArray dst,与src大小相同的输出数组;
-
第三个参数,double alpha = 1,在范围归一化的情况下规范到或较低的范围边界的范数;
-
第四个参数,double beta = 0,范围归一化时的上限边界;不用于归一化;
-
第五个参数,int norm_type = NORM_L2,归一化的类型;
可以有以下的取值:
(1) NORM_MINMAX:数组的数值被平移或缩放到一个指定的范围,线性归一化,一般较常用;
(2) NORM_INF:此类型的定义没有查到,根据OpenCV 1的对应项,可能是归一化数组的C-范数(绝对值的最大值) ;
(3) NORM_L1 : 归一化数组的L1-范数(绝对值的和);
具体公式(4) NORM_L2: 归一化数组的(欧几里德)L2-范数;
具体公式(5) NORM_L2SQR
具体公式-
第六个参数,int dtype = -1,dtype为负数时,输出数组的type与输入数组的type相同;否则,输出数组与输入数组只是通道数相同,而tpye=CV_MAT_DEPTH(dtype);
-
第七个参数,InputArray mask = noArray(),操作掩膜,用于指示函数是否仅仅对指定的元素进行操作;
三、方向投影(calcBackProject)
1、反向投影
反向投影是一种记录给定图像中的像素点如何适应直方图模型像素分布的方式;
简单的讲, 所谓反向投影就是首先计算某一特征的直方图模型,然后使用模型去寻找图像中存在的该特征;
反向投影在某一位置的值就是原图对应位置像素值在原图像中的总数目;
例如, 你有一个肤色直方图 ,你可以用它来寻找图像中的肤色区域;
2、反向投影原理
(1) 例如一幅灰度图像如下:
Image=
0 1 2 3
4 5 6 7
8 9 10 11
8 9 14 15
(2) 该灰度图的直方图为(bin指定的区间为[0,3),[4,7),[8,11),[12,16))
Histogram = 4 4 6 2
(3)反向投影图
Back_Projection=
4 4 4 4
4 4 4 4
6 6 6 6
6 6 2 2
例如位置(0,0)上的像素值为0,对应的bin为[0,3),所以反向投影在该位置上的值为直方图上对应区域的值为4;
3、函数原型
// 原型一
void calcBackProject(const Mat *images,
int nimages,
const int *channels,
InputArray hist,
OutputArray backProject,
const float **ranges,
double scale = 1,
bool uniform = true )
// 原型二
void calcBackProject(const Mat *images,
int nimages,
const int *channels,
const SparseMat &hist,
OutputArray backProject,
const float **ranges,
double scale = 1,
bool uniform = true)
// 原型三
void calcBackProject(InputArrayOfArrays images,
const std::vector<int> &channels,
InputArray hist,
OutputArray dst,
const std::vector<float> &ranges,
double scale)
4、函数功能
计算直方图的反向投影;与calculHist函数类似,在每个(x,y)位置,函数从输入图像中的选定通道收集值,并找到相应的直方图bin;但是,函数不是递增它,而是读取bin值,按比例缩放,并存储在backProject(x,y)中;
在统计方面,该函数根据直方图所表示的经验概率分布计算每个元素值的概率;
例如,请参见如何在场景中查找和跟踪亮度好的对象;
(1) 对测试图像中的每个像素 p(i,j),获取色调数据并找到该色调在直方图中的bin位置 ;
(2) 查询模型直方图中对应的bin,并读取该bin的数值;
(3) 将此数值存储在新的图像中(BackProjection);也可以先归一化模型直方图,这样测试图像的输出就可以在屏幕上显示了;
(4) 使用统计学的语言,BackProjection中存储的数值代表了测试图像中该像素属于对象的概率;
(5) 亮的区域是对象的可能性更大,而暗的区域则表示更低的可能性;
5、参数详解
-
第一个参数,const Mat *images,输入图像,图像深度必须位CV_8U,CV_16U或CV_32F中的一种,尺寸相同,每一幅图像都可以有任意的通道数 ;
-
第二个参数,int nimages,输入图像的数量 ;
-
第三个参数,const int *channels,用于计算反向投影的通道列表,通道数必须与直方图维度相匹配,第一个数组的通道是从0到image[0].channels()-1,第二个数组通道从图像image[0].channels()到image[0].channels()+image[1].channels()-1计数 ;
-
第四个参数,InputArray hist,输入的直方图,直方图的bin可以是密集的或稀疏的;
-
第五个参数,OutputArray backProject,目标反向投影输出图像,是一个单通道图像,与原图像有相同的尺寸和深度;
-
第六个参数,const float **ranges,直方图中每个维度bin的取值范围;
-
第七个参数,double scale = 1,可选输出反向投影的比例因子;
-
第八个参数,bool uniform = true,直方图是否均匀分布的标识符,有默认值true;
三、综合实例
1、实验实例
#include <iostream>
#include <opencv2/core.hpp>
#include <opencv2/highgui.hpp>
#include <opencv2/imgproc.hpp>
using namespace std;
using namespace cv;
//定义全局变量
Mat srcImage, hsvImage,hueImage;
const int hueBinMaxValue = 180;
int hueBinValue=25;
// 声明回调函数
void Hist_and_Backprojection(int, void*);
int main()
{
// 载入图像
srcImage=imread("Back_Projection.jpg");
//判断图像是否加载成功
if(srcImage.empty())
{
cout << "图像加载失败" << endl;
return -1;
}
else
cout << "图像加载成功..." << endl << endl;
//将图像转化为HSV图像
cvtColor(srcImage, hsvImage, CV_BGR2HSV);
//只使用图像的H参数
hueImage.create(hsvImage.size(), hsvImage.depth());
int ch[]={0,0};
mixChannels(&hsvImage, 1, &hueImage, 1, ch, 1);
//轨迹条参数设置
char trackBarName[20];
sprintf(trackBarName,"Hue bin:%d",hueBinMaxValue);
namedWindow("SourceImage",WINDOW_AUTOSIZE);
//创建轨迹条并调用回调函数
createTrackbar(trackBarName, "SourceImage", &hueBinValue, hueBinMaxValue, Hist_and_Backprojection);
// 直方图与反向投影回调函数
Hist_and_Backprojection(hueBinValue, 0);
// 显示原图像
imshow("SourceImage", srcImage);
waitKey(0);
return 0;
}
void Hist_and_Backprojection(int, void*)
{
// 直方图
MatND hist;
int histsize=MAX(hueBinValue, 2);
// 直方图的范围
float hue_range[]={0,180};
const float* ranges={hue_range};
// 计算图像直方图并归一化处理
calcHist(&hueImage, 1, 0, Mat(), hist, 1, &histsize, &ranges, true, false);
// 对计算的直方图进行归一化处理
normalize(hist, hist, 0, 255, NORM_MINMAX, -1, Mat());
// 获取反向投影
MatND backProjection;
// 计算反向投影
calcBackProject(&hueImage, 1, 0, hist, backProjection, &ranges, 1, true);
// 显示反向投影图像
imshow("BackProjection", backProjection);
//绘制图像直方图
int w=400;
int h=400;
// 直方图分为多少段
int bin_w = cvRound((double)w/histsize);
// 创建直方图图像400 x 400
Mat histImage = Mat::zeros(w, h, CV_8UC3);
// 绘制直方图
for(int i=0; i < hueBinValue; i++)
{
rectangle(histImage, Point(i*bin_w, h), Point((i+1)*bin_w, h-cvRound(hist.at<float>(i)*h/255.0)), Scalar(0,0,255), -1);
}
// 显示直方图图像
imshow("HistImage", histImage);
}
2、实验结果
原图 直方图 反向投影我是奕双,现在已经毕业将近两年了,从大学开始学编程,期间学习了C语言编程,C++语言编程,Win32编程,MFC编程,毕业之后进入一家图像处理相关领域的公司,掌握了用OpenCV对图像进行处理,如果大家对相关领域感兴趣的话,可以关注我,我这边会为大家进行解答哦!如果大家需要相关学习资料的话,可以私聊我哦!