分析一下上次的行人跟踪程序关键点,达到的目的:看完这篇博客,即可凭借代码片段和设计思路自行写出。
二.关键函数
1.直方图计算函数,cv::calcHist(),在opencv的源码中查到有三种重载,如下:
(1)void cv::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
)
(2)void cv::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
)
(3)void cv::calcHist (
InputArrayOfArrays images,
const vector< int > & channels,
InputArray mask,
OutputArray hist,
const vector< int > & histSize,
const vector< float > & ranges,
bool accumulate = false
)
参数讲解:
参数1表示需要用来计算直方图的源图像序列,因此可以允许有多张大小一样,数据类型相同的图像被用来统计其直方图特征。
参数2表示的就是使用多少张图像序列中的图像用于计算直方图。
参数3的功能是指定哪些通道的图像被用来计算,用数组来表示哪张图片的哪个通道被用到,这里虽然我们输入的图像序列images中有很多图片,但是并不是每一张图片的 每一个通道都会被用来计算。
参数4是mask掩膜操作,即指定每张图片的哪些像素被用于计算直方图,这个掩膜矩阵不能够针对特定图像设定特定的掩膜,因此在这里是一视同仁对待参数5是保存计算的直方图结果的矩阵,有可能是多维矩阵。
参数6是需要计算的直方图的维数。
参数7是所需计算直方图的每一维的大小,即每一维bin的个数。
参数8是所需计算直方图的每一维的范围,如果参数9的uniform为true,这此时的参数8的大小为2,里面的元素值表示的是每一维的上下限这两个数字;如果参数9的uniform 为false,则此时的参数8的大小为bin的个数,即参数7的值,参数8里面的元素值需要人为的指定,即每一维的坐标值不一定是均匀的,需要人为指定。
参数9如果为true的话,则说明所需计算的直方图的每一维按照它的范围和尺寸大小均匀取值;如果为false的话,说明直方图的每一维不是均匀分布取值的,参考参数8的解 释。
参数10如果为false,则表示直方图输出矩阵hist在使用该函数的时候被清0了,如果为true,则表示hist在使用calcHist()函数时没有被清0,计算的结果会累加到前一次保存 的值中。
使用该函数的时候需要注意,如果在默认参数的情况下uniform = true,则此时的ranges大小必须是histSize大小的两倍,并且channels的大小必须等于dims维数。(我没验 证),从上面可以理解,channels里的值已经指定了使用哪些单通道的图像来计算目标直方图,因此一旦channels的尺寸确定,则对应的直方图的维数也就确定了,所以我们 不能使用多张图像来计算一个一维的直方图。
这是截取的一段代码,别人写的,但是都是经典思路,很常用,几乎所有有关直方图程序都有该片段。这是老版本opencv的函数,不推荐,但思路一样。
int hist_size = 256; //直方图尺寸
int hist_height = 256;
float range[] = {0,255}; /灰度级的范围
float* ranges[]={range}; //创建一维直方图,统计图像在[0 255]像素的均匀分布
CvHistogram* gray_hist = cvCreateHist(1,&hist_size,CV_HIST_ARRAY,ranges,1); //计算灰度图像的一维直方图
cvCalcHist(&gray_plane,gray_hist,0,0); //归一化直方图
cvNormalizeHist(gray_hist,1.0);
int scale = 2; //创建一张一维直方图的“图”,横坐标为灰度级,纵坐标为像素个数
IplImage* hist_image = cvCreateImage(cvSize(hist_size*scale,hist_height),8,3);
cvZero(hist_image); //统计直方图中的最大直方块
float max_value = 0;
cvGetMinMaxHistValue(gray_hist, 0,&max_value,0,0); //分别将每个直方块的值绘制到图中
for(int i=0;i<hist_size;i++)
{
float bin_val = cvQueryHistValue_1D(gray_hist,i); //像素i的概率
int intensity = cvRound(bin_val*hist_height/max_value); //要绘制的高度
cvRectangle(hist_image, cvPoint(i*scale,hist_height-1), cvPoint((i+1)*scale - 1, hist_height - intensity), CV_RGB(255,255,255));
}
2. cv::calcBackProject(),计算反射投影,在opencv中有三种重载方式。
(1)void cv::calcBackProject(
const Mat * images,int nimages,const int * channels,InputArray hist,OutputArray backProject,const float **ranges,double scale = 1,bool uniform = true )
(2)void cv::calcBackProject(
const Mat * images,int nimages,const int * channels,const SparseMat &hist,OutputArray backProject,const float ** ranges,doublescale = 1,bool uniform = true )
(3)void cv::calcBackProject(
InputArrayOfArrays images,const vector< int > & channels,InputArray hist,OutputArray dst,const vector< float > & ranges,doublescale )
图像的反向投影图是用输入图像的某一位置上像素值(多维或灰度)对应在直方图的一个bin上的值来代替该像素值,所以得到的反向投影图是单通的。用统计学术 语,输出图像象素点的值是观测数组在某个分布(直方图)下的概率。
这是截取的一段代码,别人写的,但是都是经典思路,很常用,几乎所有有关直方图程序都有该片段。
extern cv::Mat src, hsv, hue;
extern int bins;
void hist_and_backprojection(int, void* )
{
cv::MatND hist; //直方图bin的数目大小
int histSize = cv::max(bins, 2);
float hue_range[] = {0, 180};
const float *ranges = { hue_range };
cv::calcHist(&hue,
1, //图像数量
0, //通道数
cv::Mat(), //不使用掩膜
hist,
1, //直方图维度
&histSize, //每一维直方图bin的数目
&ranges, //每一维直方图的范围
true,
false
);
cv::normalize(hist, hist, 0, 255, cv::NORM_MINMAX, -1, cv::Mat()); //将直方图bin的数值归一化到0-255,可方便直接显示反向投影图
//计算反向投影
cv::MatND backproj;
cv::calcBackProject(&hue,
1, //源图像的数目
0, //用于计算反向投影值的通道列表
hist, //输入直方图
backproj, //单通道反向投影图像
&ranges, //每一维直方图bin的范围
1,
true);
//显示反向投影图
cv::namedWindow("BackProjection");
cv::imshow("BackProjection", backproj);
//显示直方图
int w = 400, h = 400;
int bin_w = cvRound((double)w / histSize);
cv::Mat histImg = cv::Mat::zeros( w, h, CV_8UC3 );
for(int i = 0; i < bins; i++)
{
cv::rectangle(histImg,
cv::Point(i*bin_w, h),
cv::Point((i+1)*bin_w, h - cvRound(hist.at<float>(i) * h / 255.0) ),
cv::Scalar(0, 0, 255),
-1);
}
cv::namedWindow("Histogram");
cv::imshow("Histogram", histImg);
}
3.RotatedRect cv::CamShift(
InputArray probImage,
CV_OUT CV_IN_OUT Rect & window,
TermCriteria criteria
)
CvTermCriteria
迭代算法的终止准则
#define CV_TERMCRIT_ITER 1
#define CV_TERMCRIT_NUMBER CV_TERMCRIT_ITER
#define CV_TERMCRIT_EPS 2
typedef struct CvTermCriteria
{
int type; /* CV_TERMCRIT_ITER 和CV_TERMCRIT_EPS二值之一,或者二者的组合 */
int max_iter; /* 最大迭代次数 */
double epsilon; /* 结果的精确性 */
}
CvTermCriteria;
/* 构造函数 */
inline CvTermCriteria cvTermCriteria( int type, int max_iter, double epsilon );
/* 在满足max_iter和epsilon的条件下检查终止准则并将其转换使得type=CV_TERMCRIT_ITER+CV_TERMCRIT_EPS */
CvTermCriteria cvCheckTermCriteria( CvTermCriteria criteria,
double default_eps,
int default_max_iters );
第三个参数CvTermCriteria criteria是说明迭代到多少次算完,这里网上别人写,设置为TermCriteria(CV_TERMCRIT_ITER|CV_TERMCRIT_EPS, 1000, 0.1) ,其中那个1000指迭代次数,0.1指的是迭代条件,但我写了TermCriteria( TermCriteria::EPS | TermCriteria::COUNT, 10, 1 ),考虑是版本变动。
calcBackProject(& ,
1,
0,
hist,
backproj,
&phranges
);
backproj &= mask;
RotatedRect trackBox = CamShift(backproj,
trackWindow,
TermCriteria( TermCriteria::EPS | TermCriteria::COUNT, 10, 1 )
);
三、总结
我分析这三个主要函数,是本项目中最重要的函数,使用时参照opencv官方源码和其他大神的程序,发现这部分编写思路较固定,思路经典,但是要注意函数重载和参数之间的关系。