const bool visi = false); //visualize process?//是否可视化过程 ... };
用来检测面部特征的块模型存储在矩阵P中。在这个类中两个基本的函数是:calc_response和train。calc_response函数用来计算每一个搜索区域im的整体替代部分的块模型响应。 train函数学习psize大小的块模型,平均而言,在训练集上产生的响应很接近于理想的响应。 var,lambda,mu_init和nsamples是训练过程的参数,可以随时调整到最佳性能。
这个类的功能将在这一部分详细介绍。我们通过讨论相关块和它的训练过程开始本部分,并将用来学习块模型。下一步,我们将描述patch_models类,该类搜集了每一个面部特征的块模型并且对全局转换进行了解释说明。train_patch_model.cpp和visualize_patch_model.cpp程序单独地训练和可视化块模型。他们的使用将在面部特征检测器这一部分的结尾进行概述。
基于相关的块模型
在学习检测器中,有两个主要的竞争范例:生成的和区别的。生成的方法学习一个基础的图像块的表示,这可以采用它所有的表现最好地产生目标外观。另一方面,区分的方法学习一个表示法,它可以最好的区分目标的实体和其他目标,当配置上述情况时,该方法经常被用到。
生成的方法有这样一个优点:结果模型针对目标属性进行编码,视觉检查中允许有异常的实体。一个流行的方法,该方法属于生成方法的范例,是著名的Eigenfaces方法。区别的方法的有这样一个优点:满容量的模型直接很方便的适用于问题(个人理解,满容量的模型就是有足够多的样本训练出来的模型)。区分目标实体和其他实体。可能所有区分方法中最有名的是支持向量机。虽然这两个范例在很多情况下工作很好,但是我们将看到当模型化人脸特征作为图像块时,区分的范例将更好。
注释:
注意,eigenfaces和支持向量机方法最初产生是用来分类,而不是用来目标检测或者图像对齐。然而他们根本的数学上的概念表明了可以被应用与人脸跟踪领域。
学习区分的块模型
给一个标记数据集,特征检测器可以独自的学习他们。一个区分的块模型学习的目的是为了构造图像块,当该图像块和含有人脸特征的图像区域交叉相关时,将在特征位置产生强的响应,并且在其他地方产生弱的相应。数学上,这可以表示如下:
这里,P表示块模型,I表示第i个训练图像。I(a:b,c:d)表示矩形区域,它的左上角和右下角分别位于(a,c)和(b,d)处。句号表示内积操作,并且R表示理想响应。这个等式的解决方案是一个块模型,从均值上说,当使用最小二乘标准衡量时,它产生响应接近于理想的响应。对于理想响应,一个明显的选择:R是一个除中心点外其他地方均为零的矩阵。(假定训练图像块居于面部感兴趣的特征中心)。在实践中,因为图像是手工标记的,经常有标记错误。为了详细说明这个,一般将R描述为从中心距离衰减函数。一个好的选择是2维高斯分布,这等价于假定标记错误是高斯分布。下面的图像是针对左眼外眼角的可视化:
像前面写的一样,学习的目的是用一个一般称为线性最小二乘的形式。同样地,它提供了一个闭合形式的解。然而,这个问题的自由度,也就是说,解决变量变化方式数量的问题,等价于块中像素的数量。因此,解决最优化块模型的计算花费和内存需求可能过高,即使对于适当大小的块或者例如,一个大小为40*40的块模型有1600个自由度)。
一个解决学习问题的有效的替代方法是看作一个等价的线性系统,这个方法叫做随机梯度下降。通过可视化学习目标作为块模型自由度 上的一个误差区域,随机梯度下降迭代的对该区域做一个大约梯度方向的估计并且向反方向移动一小步。对于我们的问题,可以通过仅考虑单个学习目标或者随机选择图像集中的一个图像目标的梯度来近似计算梯度。
在patch_model类中,这个学习过程在train函数中实现:
void
patch_model::train(
const vector
const float lambda,//规则化参数 const int nsampes,//随机样本的数量 const bool visi//可视化训练过程 {
int N=image.size(),n=psize.width*psize.height; int dx=wsize.width-psize.widht;//响应的中心 int dy=wsize.height-psize.height;//.. Mat F(dy,dx,CV_32F);//理想的相应 for(int y=0;y float vx=(dx-1)/2-x; F.fl(y,x)=exp(-0.5*(vx*vx+vy*vy)/var);//高斯 } } normalize(F,F,0,1,NORM_MINMAX)//标准化到0和1之间 //分配内存 Mat I(wsize.height,wsize.width,CV_32f); Mat dp(psize.height,psize.width,CV_32F); Mat O=Mat::ones(psize.height,psize.width,CV_32F)/n; P=Mat::zeros(psize.height,psize.width,CV_32F); //使用随机梯度下降使最优 RNG rn(getTickCount());//随机数产生器 double mu=mu_init, step=pow(1e-8/mu_init,1.0/nsamples); for(int samples,sample int i=rn.uniform(0,N);//随机样本图像索引 I=this->convert_image(images[i]); dP=0.0; for(int y=0;y for(int x-0;x Mat Wi=I(Rect(x,y,psize.width,psize.height)).clone(); Wi-=Wi.dot(O);normalize(Wi,Wi);//标准化 dP+=(F.fl(y,x)-P.dot(Wi))*Wi; } } P+=mu*(dP-lambda*P);//移动一小步 mu*=step;//减小步长 } } 在先前的代码中,首先要强调的代码片段是关于理想响应的计算。因为图像居中在感兴趣的面部特征,对于所有的样本响应是相同的。第二个要强调的代码片段是衰减率,step,步长大小的经过nsmpales迭代之后决定,将会衰减到一个接近于0的值。第三个要强调的代码片段是随机梯度方向的计算和用于更新块模型。这里有两个注意的地方,首先,在训练中使用的图像传递给patch_model::convert_image函数,该函数转换图像到一个单通道图像(如果是彩色图像)并且对图像的像素强度应用自然对数: 因为0的对数没有定义,所有在应用对数之前,对每一个像素增加一个1的偏值。 在训练的图像上进行这个预处理的理由是对数尺度图像对于对比度的差异和光照的变换有更高的鲁棒性。下面的图形展示了在面部区域带有不同光照的两个人脸图像。与原始图像相比,对数尺度化图像中两个图像的差异变的不显著了。 第二点要注意的是更新的方程式是从更新方向上减去lambda*p。这可以有效的避免增长过大。一个过程通常应用于机器学习算法用来促进未知数据的一般化。尺度因子lambda是用户定义的,通常依赖于具体问题。然而,典型地,对于人脸特征检测的学习块模型,一个小的值工作的较好。 生成块模型VS区分块模型 尽管像先前描述的那样,块模型学习起来轻松。值得考虑的是,生成模型和与之相对应的训练体制是否足够的简单来完成相似的结果。生成的相对于相关块模型的部分是平均块。这个模型学习的目的是,当用最小二乘准则进行衡量时,它构造一个单一的图像块,该块尽可能的接近所有样本的面部特征。 这个的问题的解决方法是所有特征中心化的训练图像块的平均。因此,在某种程度上,这个解决方法是更简单的。 在下面的图形中,给出了采用一个样本图像,通过交叉相关的均值模型和相关的块模型的得到的响应图进行的比较。各个的平均和相关块模型也显示出来了。为了可视化,这里的像素值进行了标准化。尽管两个块的类型表现的有点相似,但是他们产生的响应实质上不同。相关块模型产生的响应在特征位置有一个高的峰值,与此同时,平均块模型产生的响应过度的平滑并且在特征位置和其周边位置没有明显的区别。观察块模型的外表,相关块模型主要是灰色的,在未标准化像素范围内它相对于0,在面部特征突出的区域周围带有强烈的正值和负值。因此,它仅保存训练块中用来区分排列不整齐结构的成分,这将导致更高峰值的响应。与此相反,平均块模型编码了未知的不整齐的数据。因此,它不适应用于面部特征的定位,面部特征定位的任务是从它的局部偏移版本中区分出一个对齐的图像块。 详细说明全局几何变换 当目前为止,我们假定训练图像居中在面部特征并且全局尺度和旋转进行了标准化。在实践中,在图像中人脸跟踪时,人脸可以任何尺度和旋转角度出现。因此,必须设计一个机制用来详细说明训练和测试环境之间的差异。一种方法是以期望在应用中碰到的角度和尺度范围来扰乱训练图像。然而,这类过分简单的形式的检测器,像相关块模型,对于那类的数据经常缺乏能力来产生有用的响应。另一方面,相关块模型对于尺度和旋转上小范围的扰乱,表现出一定程度的鲁棒性。由于在视频序列中连续的帧之间的运动相对较小,我们可以利用前一帧的对面部全局变换的估计来对于当前有尺度和旋转变换的图像进行标准化。为了实现这个过程,我们要做的是选择一个参考帧,相关块模型通过该帧进行学习。 patch_model对每一个面部特征存储着相关块模型,和这些模块在参考帧中训练的一样。正是patch_models类,而不是patch_model类,带有直接的人脸跟踪器代码接口,来获得特征的检测。下面的代码片段是对这个类基本功能的阐述: class patch_models { public: Mat reference;//参考形状[x1,y1;...;xn;yn] vector train(ft_data &data,//白年纪图像和形状数据 const vector const Size ssize,// 训练搜索窗口的大小 const bool mirror=false //使用镜像的训练数据