《Master Opencv…读书笔记》非刚性人脸跟踪 IV (终)

一、我们目前为止拥有什么

为了有一个连续完整的认识,在介绍最后一节前,先梳理下至今我们训练了哪些数据特征,并且训练它们的目的是什么。

1.ft_data:利用手工标注工具,获取最原始的样本训练数据,包括以下内容:

博文地址:

涉及主要技术:如何利用Opencv序列化存储“类”的结构数据

2.shape_model:由于人脸的高度结构化特征对局部形变产生了极大的约束,因此我们需要提取一种特征来描述手工标注点集与人脸器官在几何空间上的对应关系。这种关系包括全局形变(人脸平移、缩放、旋转)和局部形变(描述不同人、不同表情之间脸部形状的不同)。这种特征的训练结果包含如下内容:

我们训练该特征,就是为了得到样本图像的标注点投影到人脸特征子空间的投影矩阵。另外,该投影矩阵内的k个局部形变参数代表了k个表情,一次投影将产生k组子空间坐标。

博文地址:

涉及主要技术:奇异值分解、Procrustes Analysis、求施密特正交矩阵

3.patch_model:团块特征模版,即人脸每个部位的特征图像。团块特征的训练结果包含如下内容:

参考形状矩阵reference:通过人工指定参数向量p,在人脸子空间产生k种投影的坐标集合。由于图像的全局几何约束,为了提取更好的团块模型,我们需要求人工标注点到该矩阵reference的仿射变化矩阵(calc_simil函数完成),从而对样本图像也进行相应的仿射变化团块矩阵P:它是一种归一化的图像,代表当前特征点附近的图像特征

在人脸跟踪时,需要对人脸不同部位各自的描述信息,以便于对每个特征点周围的图像进行模版匹配,达到人脸精细化跟踪的目的。

博文地址:

涉及主要技术:随机梯度下降法、最小二乘法

二、打算怎么去跟踪,完整的跟踪方案

1.手工标注数据,获取原始训练样本(多人,多表情)

2.训练形状模型(提取这些表情模型,几何依赖关系保证后面的跟踪“像人脸”)

3.训练团块模型(提取每个表情所包含的团块特征,人脸跟踪全靠这个模版匹配了)

4.初始化人脸检测器(怎么在第一帧或跟踪失败时,开始/继续人脸检测)

5.根据上一帧的人脸特征点,结合形状和团块信息,估计当前帧的人脸特征点集(考虑空间高斯噪声,此噪声是跟踪错误导致,不是图像噪声)

三、如何初始化第一帧及检测人脸

由于人脸在相邻帧之间的动作变化较小,所以到目前为止,我们假设每帧图像中人脸的特征都分布在当前估计点周围的合理范围内。但是我们仍面临着一个严重的问题,到底怎样初始化第一帧得到其中的人脸特征模型。

对于第一帧,这里我们采用比较直观的方式,使用opencv内置的级联检测器来寻找人脸的大致区域,用外接矩形来描述。按照数据驱动的思路,通过学习训练使我们的系统能够学习人脸外界矩形与人脸跟踪特征之间的几何关系detector_offset向量,然后利用该向量对人脸参考形状矩阵reference进行仿射变换,获得外界矩形区域内的人脸特征点。

接下来,我们首先介绍训练的步骤,然后展示我们的训练结果。

训练过程:

1.载入样本标注点ft_data及形状模型数据shape_model

2.设置参数向量p,构造人脸子空间坐标点集合作为人脸特征的参考点集

3.调用trian函数,学习外界矩形与人脸特征点之间的几何关系detector_offset,

train函数入参:

data:ft_data对象实例,包含了手工标注信息

fname:级联分类器名称(比如:haarcascade_frontalface_alt.xml)

ref:参考形状矩阵,在人脸子空间的k种投影点集

mirror:镜像样本图像标记

visi:训练过程可视化标记

frac:有效特征点比率阈值

这里train函数的目标是获取detector_offset向量,该向量的作用是将之前训练得到的形状模型以合理的方式镶嵌到人脸上。detector_offset向量通过外接矩形的width和该区域内手工标注点集合pt的重心计算得到。具体过程如下:

(1)加载级联分类器、形状参考矩阵

(2)对手工标注的每一幅图片,使用级联分类器搜索人脸区域

(3)判断人脸的外接矩形内是否包含足够多的标注点(防止错误学习)

(4)如果包含足够的标注点,则按照如下公式计算

重心:

计算每一幅图像的offset,构成平面坐标集合(X,Y)及缩放比例集合Z:

对X、Y、Z集合分别按升序排序,去各自的中值作为最终的detector_offset(Xm,Ym,Zm)

具体实现代码:

//====================================================================voidface_detector::train(ft_data &data,const string fname,const Mat &ref,const bool mirror,const bool visi,const float frac,const float scaleFactor,const int minNeighbours,const Size minSize){ //载入级联分类器 detector.load(fname.c_str()); detector_fname = fname; reference = ref.clone(); vector<float> xoffset(0),yoffset(0),zoffset(0); for(int i = 0; i < data.n_images(); i++) {//获取每一张训练图片Mat im = data.get_image(i,0); if(im.empty())continue;//获取训练图片对应的标注点vector<Point2f> p = data.get_points(i,false); int n = p.size();Mat pt = Mat(p).reshape(1,2*n);vector<Rect> faces;Mat eqIm; //直方图均衡化equalizeHist(im,eqIm);//人脸检测detector.detectMultiScale(eqIm,faces,scaleFactor,minNeighbours,0|CV_HAAR_FIND_BIGGEST_OBJECT|CV_HAAR_SCALE_IMAGE,minSize);if(faces.size() >= 1){if(visi) {//框出人脸区域Mat I; cvtColor(im,I,CV_GRAY2RGB);for(int i = 0; i < n; i++)circle(I,p[i],1,CV_RGB(0,255,0),2,CV_AA);rectangle(I,faces[0].tl(),faces[0].br(),CV_RGB(255,0,0),3);imshow("face detector training",I); waitKey(10);}//check if enough points are in detected rectangleif(this->enough_bounded_points(pt,faces[0],frac)) {Point2f center = this->center_of_mass(pt); float w = faces[0].width;xoffset.push_back((center.x – (faces[0].x+0.5*faces[0].width ))/w);yoffset.push_back((center.y – (faces[0].y+0.5*faces[0].height))/w);zoffset.push_back(this->calc_scale(pt)/w);}}if(mirror){im = data.get_image(i,1); if(im.empty())continue;p = data.get_points(i,true);pt = Mat(p).reshape(1,2*n);equalizeHist(im,eqIm);detector.detectMultiScale(eqIm,faces,scaleFactor,minNeighbours,0|CV_HAAR_FIND_BIGGEST_OBJECT|CV_HAAR_SCALE_IMAGE,minSize);if(faces.size() >= 1){if(visi){Mat I; cvtColor(im,I,CV_GRAY2RGB);for(int i = 0; i < n; i++)circle(I,p[i],1,CV_RGB(0,255,0),2,CV_AA);rectangle(I,faces[0].tl(),faces[0].br(),CV_RGB(255,0,0),3);imshow("face detector training",I); waitKey(10);}//check if enough points are in detected rectangleif(this->enough_bounded_points(pt,faces[0],frac)){Point2f center = this->center_of_mass(pt); float w = faces[0].width;xoffset.push_back((center.x – (faces[0].x+0.5*faces[0].width ))/w);yoffset.push_back((center.y – (faces[0].y+0.5*faces[0].height))/w);zoffset.push_back(this->calc_scale(pt)/w);}}} } //choose median value,选取集合中值 Mat X = Mat(xoffset),Xsort,Y = Mat(yoffset),Ysort,Z = Mat(zoffset),Zsort; cv::sort(X,Xsort,CV_SORT_EVERY_COLUMN|CV_SORT_ASCENDING); int nx = Xsort.rows; cv::sort(Y,Ysort,CV_SORT_EVERY_COLUMN|CV_SORT_ASCENDING); int ny = Ysort.rows; cv::sort(Z,Zsort,CV_SORT_EVERY_COLUMN|CV_SORT_ASCENDING); int nz = Zsort.rows; detector_offset = Vec3f(Xsort.fl(nx/2),Ysort.fl(ny/2),Zsort.fl(nz/2)); return;}幸福就是重复。每天跟自己喜欢的人一起,

《Master Opencv…读书笔记》非刚性人脸跟踪 IV (终)

相关文章:

你感兴趣的文章:

标签云: