camshift利用目标的颜色直方图模型将图像转换为颜色概率分布图,初始化一个搜索窗的大小和位置,并根据上一帧得到的结果自适应调整搜索窗口的位置和大小,从而定位出当前图像中目标的中心位置。
分为三个部分:
1--色彩投影图(反向投影):
(1).RGB颜色空间对光照亮度变化较为敏感,为了减少此变化对跟踪效果的影响,首先将图像从RGB空间转换到HSV空间。(2).然后对其中的H分量作直方图,在直方图中代表了不同H分量值出现的概率或者像素个数,就是说可以查找出H分量大小为h的概率或者像素个数,即得到了颜色概率查找表。(3).将图像中每个像素的值用其颜色出现的概率对替换,就得到了颜色概率分布图。这个过程就叫反向投影,颜色概率分布图是一个灰度图像。
例子说明:
Image=
0 1 2 3
4 5 6 7
8 9 10 11
8 9 14 15
Histogram=
4 4 6 2(3)反向投影图
Back_Projection=
4 4 4 4
4 4 4 4
6 6 6 6
6 6 2 2
2--meanshift
meanshift 算法 是一种密度函数梯度估计的非参数方法,通过迭代寻优找到概率分布的极值来定位目标。
算法过程为:
(1).在颜色概率分布图中选取搜索窗W
(2).计算零阶距:
计算一阶距:
计算搜索窗的质心:
3--camshift
将meanshift算法扩展到连续图像序列,就是camshift算法。它将视频的所有帧做meanshift运算,并将上一帧的结果,即搜索窗的大小和中心,作为下一帧meanshift算法搜索窗的初始值。如此迭代下去,就可以实现对目标的跟踪。
算法过程为:
(1).初始化搜索窗
(2).计算搜索窗的颜色概率分布(反向投影)
(3).运行meanshift算法,获得搜索窗新的大小和位置。
(4).在下一帧视频图像中用(3)中的值重新初始化搜索窗的大小和位置,再跳转到(2)继续进行。
camshift能有效解决目标变形和遮挡的问题,对系统资源要求不高,时间复杂度低,在简单背景下能够取得良好的跟踪效果。但当背景较为复杂,或者有许多与目标颜色相似像素干扰的情况下,会导致跟踪失败。因为它单纯的考虑颜色直方图,忽略了目标的空间分布特性,所以这种情况下需加入对跟踪目标的预测算法。
1 MeanShift原理
如下图所示:矩形窗口中的红色点代表特征数据点,矩形中的圆圈代表选取窗口。meanshift算法的目的是找到含有最多特征的窗口区域,即使圆心与概率密度函数的局部极值点重合,亦即使圆心与特征数据点最密集的地方中心尽量重合到一块。算法实现是通过向特征数据点密度函数上升梯度方向逐步迭代偏移至上升梯度值近似为零(到达最密集的地方)。
即在不改变选取局部窗口的情况下,通过给窗口一个向着特征数据点更密集的方向一个偏移向量,然后将偏移后的选取窗口作为当前选取窗口,根据选取窗口数据特征点密集情况给出一个一个向着特征数据点更密集的方向一个偏移向量……迭代偏移过程中,直到偏移向量的模值近似为零即可。
我们来分析一下上述过程:当选取窗口(圆形)由远靠近最密集点,再远离最密集点的过程中,圆形窗口中特征数据点的数量变化:理想情况下应是选取窗口包含的特征数据点越来越多,再到越来越少。(这个过程对应meanshift算法的基本形式,没有添加核函数时)
2 为什么要用概率密度函数的上升梯度呢?
概率密度函数可以表示大小不变的选取窗口中特征数据点的密度,我们要找的是概率密度最大的选取窗口,这样,我们只要使下一个选取窗口的概率密度函数值减去当前选取窗口的概率密度函数值大于0,就可以越来越靠近取窗口的概率密度函数的最大值,当下一个选取窗口与当前选取窗口非常接近时,就可以用概率密度梯度表示两窗口概率密度函数对应值的差,也就是说只要使选取窗口向概率密度函数梯度上升的方向偏移就可以在上升梯度值近似为零时取得概率密度函数的近似最大值。所以使用概率,是因为特征数据点本身是概率事件或说是标定与标准特征匹配程度的特征数据。
上面的分析的前提是默认选取窗口中的各个特征数据点的权值是相等的,但实际中各个特征数据点对最终判定是否为目标的影响是不一样的,这可以通过加权值函数(即meanshift扩展方法中的核函数)来实现。
3 添加的权值函数应满足什么条件呢?
对某一选取窗口的概率密度函数值=各个特征数据点的总和相当量除以窗口面积的相当量。
当选取窗口为全局窗口时,目标一定出现,即此范围内权值函数的各个值的总和归一化后一定为1。
为了便于比较和求概率密度函数的梯度,加权函数亦即核函数在自变量取值范围内的积分为1.
所以核函数应满足的条件:自变量范围内积分,值为1。
4 meanshift方法的适用范围及其优缺点
meanshift方法适合概率密度函数有极值且在某一局部区域内唯一,即选择的特征数据点能够较为明显的判定目标,亦即显著特征点。显然此方法,meanshift的基本形式不适合等概率特征点(即特征点是均匀分布)的情况。
1) meanshift算法,受初始值的影响很大,和经验相关。
2) 算法收敛的速度和程度很大程度上与选取的窗口有关,选取恰当的窗口非常重要。
3) 窗口选取的是否恰当很大程度上取决于目标(特征数据点的分布状况),这就是说此方法在处理一类目标时,还是很有效的,最起码跟踪同一目标,在窗口经过恰当的线性变换后,跟踪效果应该还是不错的。即概率密度函数的极值在自变量区域压缩或扩大的过程中,极值仍存在,仍可跟踪到目标。
4) meanshift算法若用于图割,则适用于:
已经建立标准的特征数据点集,且通过恰当的概率密度函数和核函数可以唯一的确定目标时,可以将目标从批量图片中分割,挑选出来,但是取出的结果显示为选取窗口中所有的内容,也就是说可能会有目标物之外的图像或缺失部分目标物。若选取窗口为目标物的轮廓,那将非常不错,也就意味着窗口模板要更新或目标轮廓是不变的(实际中不变几乎是不可能的),更新以为误差与误差的积累,也就是说进行批量分割时,效果是有限的。
5 meanshift 编程实现(opencv)
具体编程实现的时候meanshift算法思想其实很简单——利用概率密度的梯度爬升来寻找局部最优。它要做的就是输入一个在图像的范围,然后一直迭代(朝着重心迭代)直到满足你的要求为止。
例如在opencv中,实现过程:输入一张图像(imgProb),再输入一个开始迭代的方框(windowIn)和一个迭代条件(criteria),输出的是迭代完成的位置(comp )。
这是函数原型:
int cvMeanShift( const void* imgProb,CvRect windowIn,
CvTermCriteria criteria, CvConnectedComp* comp )
但是当它用于跟踪时,这张输入的图像就必须是反向投影图了。反向投影图实际上是一张概率密度图。经过反向投影时的输入是一个目标图像的直方图(也可以认为是目标图像),还一个输入是当前图像就是你要跟踪的全图,输出大小与全图一样大,它上像素点表征着一种概率,就是全图上这个点是目标图像一部分的概率。如果这个点越亮,就说明这个点属于物体的概率越大。
1)半自动跟踪思路:
输入视频,用画笔圈出要跟踪的目标,然后对物体跟踪。(用过opencv的都知道,这其实是camshiftdemo的工作过程)
第一步:选中物体,记录你输入的方框和物体。
第二步:求出视频中有关物体的反向投影图。
第三步:根据反向投影图和输入的方框进行meanshift迭代,由于它是向重心移动,即向反向投影图中概率大的地方移动,所以始终会移动到目标上。
第四步:然后下一帧图像时用上一帧输出的方框来迭代即可。
2)全自动跟踪思路:
输入视频,对运动物体进行跟踪。
第一步:运用运动检测算法将运动的物体与背景分割开来。
第二步:提取运动物体的轮廓,并从原图中获取运动图像的信息。
第三步:对这个信息进行反向投影,获取反向投影图。
第四步:根据反向投影图和物体的轮廓(也就是输入的方框)进行meanshift迭代,由于它是向重心移动,即向反向投影图中概率大的地方移动,所以始终会移动到物体上。
第五步:然后下一帧图像时用上一帧输出的方框来迭代即可。
(半自动和全自动的区别就是多了物体和背景的分离,特征提取部分)
附:后面的目标的跟踪例子是半自动的,即需要人手工选定一个目标。我正在努力尝试全自动的目标跟踪,希望可以和大家能在这方面与大家交流。
下面是自己的调试代码注释和一些见解:
#if 1
//对运动物体的跟踪:
//如果背景固定,可用帧差法 然后在计算下连通域 将面积小的去掉即可
//如果背景单一,即你要跟踪的物体颜色和背景色有较大区别 可用基于颜色的跟踪 如CAMSHIFT 鲁棒性都是较好的
//如果背景复杂,如背景中有和前景一样的颜色 就需要用到一些具有预测性的算法 如卡尔曼滤波等 可以和CAMSHIFT结合
#ifdef _CH_
#pragma package <opencv>
#endif
#ifndef _EiC
#include "cv.h"
#include "highgui.h"
#include <stdio.h>
#include <ctype.h>
#endif
#include "opencv2/opencv.hpp"
using namespace cv;
IplImage *image = 0, *hsv = 0, *hue = 0, *sta = 0, *light = 0,*camshift_test, *mask = 0, *backproject = 0, *histimg = 0;
//用HSV中的Hue分量进行跟踪
CvHistogram *hist = 0;
//直方图类
int backproject_mode = 0;
int select_object = 0;
int track_object = 0;
int show_hist = 1;
CvPoint origin;
CvRect selection;
CvRect track_window;
CvBox2D track_box;
//Meanshift跟踪算法返回的Box类
//typedef struct CvBox2D{
//CvPoint2D32f center; /* 盒子的中心 */
//CvSize2D32f size; /* 盒子的长和宽 */
//float angle; /* 水平轴与第一个边的夹角,用弧度表示*/
//}CvBox2D;
CvConnectedComp track_comp;
//连接部件
//typedef struct CvConnectedComp{
//double area; /* 连通域的面积 */
//float value; /* 分割域的灰度缩放值 */
//CvRect rect; /* 分割域的 ROI */
//} CvConnectedComp;
int hdims = 16;
//划分直方图bins的个数,越多越精确
float hranges_arr[] = { 0, 180 };
//像素值的范围
float* hranges = hranges_arr;
//用于初始化CvHistogram类
int vmin = 10, vmax = 256, smin = 30;
//用于设置滑动条
void on_mouse(int event, int x, int y, int flags, void* param)
//鼠标回调函数,该函数用鼠标进行跟踪目标的选择
{
if (!image)
return;
if (image->origin)
y = image->height - y;
//如果图像原点坐标在左下,则将其改为左上
if (select_object)
//select_object为1,表示在用鼠标进行目标选择
//此时对矩形类selection用当前的鼠标位置进行设置
{
selection.x = MIN(x, origin.x);
selection.y = MIN(y, origin.y);
selection.width = selection.x + CV_IABS(x - origin.x);
selection.height = selection.y + CV_IABS(y - origin.y);
selection.x = MAX(selection.x, 0);
selection.y = MAX(selection.y, 0);
selection.width = MIN(selection.width, image->width);
selection.height = MIN(selection.height, image->height);
selection.width -= selection.x;
selection.height -= selection.y;
}
switch (event)
{
case CV_EVENT_LBUTTONDOWN:
//鼠标按下,开始点击选择跟踪物体
origin = cvPoint(x, y);
selection = cvRect(x, y, 0, 0);
select_object = 1;
break;
case CV_EVENT_LBUTTONUP:
//鼠标松开,完成选择跟踪物体
select_object = 0;
if (selection.width > 0 && selection.height > 0)
//如果选择物体有效,则打开跟踪功能
track_object = -1;
break;
}
}
CvScalar hsv2rgb(float hue)
//用于将Hue量转换成RGB量
{
int rgb[3], p, sector;
static const int sector_data[][3] =
{ { 0, 2, 1 }, { 1, 2, 0 }, { 1, 0, 2 }, { 2, 0, 1 }, { 2, 1, 0 }, { 0, 1, 2 } };
hue *= 0.033333333333333333333333333333333f;
sector = cvFloor(hue);
p = cvRound(255 * (hue - sector));
p ^= sector & 1 ? 255 : 0;
rgb[sector_data[sector][0]] = 255;
rgb[sector_data[sector][1]] = 0;
rgb[sector_data[sector][2]] = p;
return cvScalar(rgb[2], rgb[1], rgb[0], 0);
}
int cvCamShift1(const void* imgProb, CvRect windowIn,
CvTermCriteria criteria,
CvConnectedComp* _comp,
CvBox2D* box)
{
const int TOLERANCE = 3; //这里是为了不断修正长轴的,使得出来的跟踪区域是同一片区域。见下面的解释想象
CvMoments moments;
double m00 = 0, m10, m01, mu20, mu11, mu02, inv_m00;
double a, b, c, xc, yc;
double rotate_a, rotate_c;
double theta = 0, square;
double cs, sn;
double length = 0, width = 0;
int itersUsed = 0;
CvConnectedComp comp;
CvMat cur_win, stub, *mat = (CvMat*)imgProb;
CV_FUNCNAME("cvCamShift");
comp.rect = windowIn;//初始化comp
__BEGIN__;
CV_CALL(mat = cvGetMat(mat, &stub));
CV_CALL(itersUsed = cvMeanShift(mat, windowIn, criteria, &comp));//调用meanshift计算质心
windowIn = comp.rect;//获得新的窗口的位置
//为了容错,窗口的四边都增大了TOLERANCE
windowIn.x -= TOLERANCE;
if (windowIn.x < 0)
windowIn.x = 0;
windowIn.y -= TOLERANCE;
if (windowIn.y < 0)
windowIn.y = 0;
windowIn.width += 2 * TOLERANCE;
if (windowIn.x + windowIn.width > mat->width)
windowIn.width = mat->width - windowIn.x;
windowIn.height += 2 * TOLERANCE;
if (windowIn.y + windowIn.height > mat->height)
windowIn.height = mat->height - windowIn.y;
CV_CALL(cvGetSubRect(mat, &cur_win, windowIn));//获得指向子窗口的数据指针
/* Calculating moments in new center mass */
CV_CALL(cvMoments(&cur_win, &moments));//重新计算窗口内的各种矩
m00 = moments.m00;
m10 = moments.m10;
m01 = moments.m01;
mu11 = moments.mu11;
mu20 = moments.mu20;
mu02 = moments.mu02;
if (fabs(m00) < DBL_EPSILON) //这是双浮点型精度的为零的时候的比较方法。因为不太编译器,其里面存零的时候值可能不是0。
EXIT;
inv_m00 = 1. / m00;
xc = cvRound(m10 * inv_m00 + windowIn.x);//新的中心坐标
yc = cvRound(m01 * inv_m00 + windowIn.y);
a = mu20 * inv_m00;
b = mu11 * inv_m00;
c = mu02 * inv_m00;
/* Calculating width & height */
square = sqrt(4 * b * b + (a - c) * (a - c));
/* Calculating orientation */
theta = atan2(2 * b, a - c + square); //长轴的方向角的计算公式
/* Calculating width & length of figure */
cs = cos(theta);
sn = sin(theta);
rotate_a = cs * cs * mu20 + 2 * cs * sn * mu11 + sn * sn * mu02;
rotate_c = sn * sn * mu20 - 2 * cs * sn * mu11 + cs * cs * mu02;
length = sqrt(rotate_a * inv_m00) * 4;//长与宽的计算
width = sqrt(rotate_c * inv_m00) * 4;
/* In case, when tetta is 0 or 1.57... the Length & Width may be exchanged */
if (length < width)
{
double t;
CV_SWAP(length, width, t);
CV_SWAP(cs, sn, t);
theta = CV_PI*0.5 - theta;
}
/* Saving results */
//由于有宽和高的重新计算,使得能自动调整窗口大小
if (_comp || box)
{
int t0, t1;
int _xc = cvRound(xc);//取整
int _yc = cvRound(yc);
t0 = cvRound(fabs(length * cs));
t1 = cvRound(fabs(width * sn));
t0 = MAX(t0, t1) + 2;//宽的重新计算
comp.rect.width = MIN(t0, (mat->width - _xc) * 2);//保证宽不超出范围
t0 = cvRound(fabs(length * sn));
t1 = cvRound(fabs(width * cs));
t0 = MAX(t0, t1) + 2;//高的重新计算
comp.rect.height = MIN(t0, (mat->height - _yc) * 2);//保证高不超出范围
comp.rect.x = MAX(0, _xc - comp.rect.width / 2);
comp.rect.y = MAX(0, _yc - comp.rect.height / 2);
comp.rect.width = MIN(mat->width - comp.rect.x, comp.rect.width);
comp.rect.height = MIN(mat->height - comp.rect.y, comp.rect.height);
comp.area = (float)m00;
}
__END__;
if (_comp)
*_comp = comp;
if (box)
{
box->size.height = (float)length;
box->size.width = (float)width;
//box->angle = (float)(theta*180. / CV_PI); //这个是差了九十度,上面计算出来的theta是差了90度的,要补回来
box->angle = (float)((CV_PI*0.5 + theta)*180. / CV_PI);
box->center = cvPoint2D32f(comp.rect.x + comp.rect.width*0.5f,
comp.rect.y + comp.rect.height*0.5f);
}
return itersUsed;
}
int main(int argc, char** argv)
{
CvCapture* capture = 0;
IplImage* imgGray, *img_inv, *copybackproject;
if (argc == 1 || (argc == 2 && strlen(argv[1]) == 1 && isdigit(argv[1][0])))
//打开摄像头
capture = cvCaptureFromCAM(argc == 2 ? argv[1][0] - '0' : 0);
else if (argc == 2)
//打开avi
capture = cvCaptureFromAVI(argv[1]);
if (!capture)
//打开视频流失败
{
fprintf(stderr, "Could not initialize capturing...\n");
return -1;
}
printf("Hot keys: \n"
"\tESC - quit the program\n"
"\tc - stop the tracking\n"
"\tb - switch to/from backprojection view\n"
"\th - show/hide object histogram\n"
"To initialize tracking, select the object with mouse\n");
//打印程序功能列表
cvNamedWindow("Histogram", 1);
//用于显示直方图
cvNamedWindow("CamShiftDemo", 1);
//用于显示视频
cvSetMouseCallback("CamShiftDemo", on_mouse, 0);
//设置鼠标回调函数
cvCreateTrackbar("Vmin", "CamShiftDemo", &vmin, 256, 0);
cvCreateTrackbar("Vmax", "CamShiftDemo", &vmax, 256, 0);
cvCreateTrackbar("Smin", "CamShiftDemo", &smin, 256, 0);
//设置滑动条
for (;;)
//进入视频帧处理主循环
{
IplImage* frame = 0;
int i, bin_w, c;
frame = cvQueryFrame(capture);
if (!frame)
break;
if (!image)
//image为0,表明刚开始还未对image操作过,先建立一些缓冲区,这里很好
{
image = cvCreateImage(cvGetSize(frame), 8, 3);
image->origin = frame->origin;
hsv = cvCreateImage(cvGetSize(frame), 8, 3);
hue = cvCreateImage(cvGetSize(frame), 8, 1);
sta = cvCreateImage(cvGetSize(frame), 8, 1);
light = cvCreateImage(cvGetSize(frame), 8, 1);
imgGray = cvCreateImage(cvGetSize(frame), 8, 1);
img_inv = cvCreateImage(cvGetSize(frame), 8, 1);
copybackproject = cvCreateImage(cvGetSize(frame), 8, 1);
mask = cvCreateImage(cvGetSize(frame), 8, 1);
camshift_test = cvCreateImage(cvGetSize(frame), 8, 3);
//分配掩膜图像空间
backproject = cvCreateImage(cvGetSize(frame), 8, 1);
//分配反向投影图空间,大小一样,单通道
hist = cvCreateHist(1, &hdims, CV_HIST_ARRAY, &hranges, 1);
//分配直方图空间
histimg = cvCreateImage(cvSize(320, 200), 8, 3);
//分配用于直方图显示的空间
cvZero(histimg);
//置背景为黑色
}
cvCopy(frame, image, 0);
cvCvtColor(image, hsv, CV_BGR2HSV);
cvCvtColor(image, imgGray,CV_BGR2GRAY );
cvCopy(frame, camshift_test);
//把图像从RGB表色系转为HSV表色系
if (track_object)
//track_object非零,表示有需要跟踪的物体
{
int _vmin = vmin, _vmax = vmax;
cvInRangeS(hsv, cvScalar(0, smin, MIN(_vmin, _vmax), 0),
cvScalar(180, 256, MAX(_vmin, _vmax), 0), mask);
//制作掩膜板,只处理像素值为H:0~180,S:smin~256,V:vmin~vmax之间的部分
cvSplit(hsv, hue, sta, light, 0);
//分离H分量
Mat imgceshi(hue,0); //0-180,不同软件上的这三个分量值是不同的,但是理论值是没有变的,见博客hsv取值
Mat imgceshi2(sta, 0); //0-255
Mat imgceshi3(light,0); //0-255
Mat imgceshi4(imgGray,0); //灰度图跟图像的V分量light还是有差别的,其值不过也相差不多。
if (track_object < 0)
//如果需要跟踪的物体还没有进行属性提取,则进行选取框类的图像属性提取 ,这里掩模是为了去除更多的干绕区域
{
float max_val = 0.f;
cvSetImageROI(hue, selection);
//设置原选择框为ROI
cvSetImageROI(mask, selection);
//设置掩膜板选择框为ROI
cvCalcHist(&hue, hist, 0, mask);
//得到选择框内且满足掩膜板内的直方图
cvGetMinMaxHistValue(hist, 0, &max_val, 0, 0);
cvConvertScale(hist->bins, hist->bins, max_val ? 255. / max_val : 0., 0);
// 对直方图的数值转为0~255
cvResetImageROI(hue);
//去除ROI
cvResetImageROI(mask);
//去除ROI
track_window = selection;
track_object = 1;
//置track_object为1,表明属性提取完成
cvZero(histimg);
bin_w = histimg->width / hdims;
for (i = 0; i < hdims; i++)
//画直方图到图像空间
{
int val = cvRound(cvGetReal1D(hist->bins, i)*histimg->height / 255);
CvScalar color = hsv2rgb(i*180.f / hdims);
cvRectangle(histimg, cvPoint(i*bin_w, histimg->height),
cvPoint((i + 1)*bin_w, histimg->height - val),
color, -1, 8, 0);
}
}
cvCalcBackProject(&hue, backproject, hist);
cvShowImage("反向投影",backproject);
//计算hue的反向投影图
cvAnd(backproject, mask, backproject, 0);
cvCopy(backproject,copybackproject);
cvThreshold(backproject, img_inv,50,1,THRESH_BINARY);
//得到掩膜内的反向投影,这里的cvCamShft1是从opencv里复制出来的函数,其值改了容错值为3,。
cvCamShift1(backproject, track_window,
cvTermCriteria(CV_TERMCRIT_EPS | CV_TERMCRIT_ITER, 10, 1),
&track_comp, &track_box);
//使用MeanShift算法对backproject中的内容进行搜索,返回跟踪结果
track_window = track_comp.rect;
cvRectangle(image,cvPoint(track_window.x,track_window.y),cvPoint(track_window.x+track_window.width,track_window.y+track_window.height),cvScalar(255,0,0),1);
//得到跟踪结果的矩形框
if (backproject_mode)
cvCvtColor(backproject, image, CV_GRAY2BGR);
if (image->origin)
track_box.angle = -track_box.angle;
//cvEllipseBox(image, track_box, CV_RGB(255, 0, 0), 3, CV_AA, 0);
cvEllipseBox(image, track_box, CV_RGB(255, 0, 0), 3, CV_AA, 0);
//画出跟踪结果的位置
}
if (select_object && selection.width > 0 && selection.height > 0)
//如果正处于物体选择,画出选择框
{
cvSetImageROI(image, selection);
cvXorS(image, cvScalarAll(255), image, 0);
cvResetImageROI(image);
}
cvShowImage("CamShiftDemo", image);
cvShowImage("Histogram", histimg);
c = cvWaitKey(10);
if ((char)c == 27)
break;
switch ((char)c)
//按键切换功能
{
case 'b':
backproject_mode ^= 1;
break;
case 'c':
track_object = 0;
cvZero(histimg);
break;
case 'h':
show_hist ^= 1;
if (!show_hist)
cvDestroyWindow("Histogram");
else
cvNamedWindow("Histogram", 1);
break;
default:
;
}
}
cvReleaseCapture(&capture);
cvDestroyWindow("CamShiftDemo");
return 0;
}
#ifdef _EiC
main(1, "camshiftdemo.c");
#endif
#endif
a、几何区域的长轴的计算公式:
b、使用掩模是为了滤掉干扰:
图一:直接反向投影结果 图二:反向投影后使用调节后mask来进行相与操作
c、调节的容错值TOLERANCE,是为了每次计算的窗口能比原先的大点TOLERANCE,这样就可 不断的判断增大后的区域是否还是目标区域。如过其为零则可能会出现跟踪区域不完全,如下:
容错为0
容错为3.
这个容错不能太大,过大的话,则可能跟踪的把另一个区域里目标也跟踪进来了。
DBL_EPSILON和 FLT_EPSILON主要用于单精度和双精度的比较当中:
第一个比较正确,第二个可能正确也可能错误,b==0.5的结果取决于处理器、编译器的版本和设置。比如 Visual C++ 2010 编译器编译后运行b的值为0.49999999999999994
一种正确的比较方法应该是这样的:
以上的例子可以看出, EPSILON是最小误差。 是EPSILON+X不等于X的最小的正数。