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

一、我们眼下为止拥有什么

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

 

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

  •  图像名称集合imnames:表明在哪幅图像上标注特征点。
  • 二维坐标集合points:手工标准点。兴许更高级别特征均环绕这些特征点展开;
  • 对称坐标索引集合symmetry:标注样本图像的镜像图像上的特征点,扩大样本库;
  •  连接索引集合connections:描写叙述手工标注的人脸特征点之间的几何约束及相对位置。

博文地址:http://blog.csdn.net/jinshengtao/article/details/42614091

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

 

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

  • 參数向量p:在手工标注的特征点集被投影到人脸子空间(之前被称作联合分布空间)前。须要设置被投影点集在子空间的缩放比例、旋转角度、还有投影的范围;
  • 子空间标准基V:描写叙述人脸模型的联合投影矩阵。包括k个局部形变參数(表情模型)和4个全局形变參数。用于将图像标注点投影到人脸特征子空间;
  • 參数变化向量e:手工标注样本点投影到人脸特征子空间后得到坐标集合的标准差,因为投影本身会导致人脸特征的失真,所以用该标准差作为阈值,修正失真clamp函数;
  • 连接矩阵C:描写叙述之前标注的连接关系矩阵,沿用ft_data中的连接索引集合,并未做其它操作;

     我们训练该特征。就是为了得到样本图像的标注点投影到人脸特征子空间的投影矩阵。

另外,该投影矩阵内的k个局部形变參数代表了k个表情,一次投影将产生k组子空间坐标。

博文地址:http://blog.csdn.net/jinshengtao/article/details/43376049

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

 

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

  • 參考形状矩阵reference:通过人工指定參数向量p,在人脸子空间产生k种投影的坐标集合。

    因为图像的全局几何约束,为了提取更好的团块模型,我们须要求人工标注点到该矩阵reference的仿射变化矩阵(calc_simil函数完毕),从而对样本图像也进行相应的仿射变化

  • 团块矩阵P:它是一种归一化的图像。代表当前特征点附近的图像特征

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

博文地址:http://blog.csdn.net/jinshengtao/article/details/43974311

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

 

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

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

详细实现代码:

//====================================================================
void
face_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 rectangle
      if(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 rectangle
		if(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;
}

      训练步骤例如以下。一共9幅样本图。每幅图首先利用opencv级联分离检索人脸位置,然后在样本图像上标出标注点。接下来推断矩形内标注点的数量是否合理,最后计算我们的偏移量detector_offset。下图是我们的样本图像、标注点、人脸位置示意图:




      经过上述训练后,我们将序列化存储:级联分类器名称、偏移向量、形状參考点集。

接下来。在展示训练结果之前,先介绍下detect函数,怎样将我们训练的矩形与特征点的偏移关系套用到測试图像上。

detect函数,输入一副人脸測试图片。依据训练结果,在该人脸图像上标准人脸特征点。详细操作过程:

(1)    彩色图像转化成灰度图像。并进行直方图均衡化

(2)    利用Opencv的级联分类器检測人脸位置

(3)    依据人脸外界矩形。结合detector_offset和參考形状模型,又一次计算标注点的坐标

这步公式比較简单不列了。但作者上边和这里设计的意图须要自己体会下。关键代码:

//predict face placement
  Rect R = faces[0]; //人脸外界矩形
  Vec3f scale = detector_offset*R.width;
  int n = reference.rows/2; 
vector<Point2f> p(n);
  for(int i = 0; i < n; i++){
    //scale[2]代表缩放比例
    p[i].x = scale[2]*reference.fl(2*i  ) + R.x + 0.5 * R.width  + scale[0];
    p[i].y = scale[2]*reference.fl(2*i+1) + R.y + 0.5 * R.height + scale[1];
  }
  return p;

     展示训练的结果例如以下,任老头子拿出来晒晒,好肥:


       从上图来看,我们的训练使得人脸特征点基本标注都正确了。本节内容说白了就是训练一个外界矩形与标注点的几何关系,然后让之后的測试图像人脸特征点得到合理标注。这里须要细细体会的是。老外几何关系detector_offset的数学设计。!如今讲了第一帧图像的处理方法。接下来在第四节介绍兴许帧,人脸检測与特征点标注怎样实现。

四、人脸跟踪实现步骤及代码

      本文的人脸跟踪问题,就是寻找一种高效、健壮的方法,将多个独立的人脸特征通过之前训练的几何依赖关系联合起来,实现精确跟踪每幅图像中人脸特征的几何位置。或许有人会问,在已经用Opencv级联分类器检測人脸了。再考虑几何依赖关系是否有意义。下面两幅图像,各自是带与不带几何依赖的人脸跟踪效果图。


      上面对照的结果清晰地展示了人脸特征之间空间内部依赖关系的优势(这里因为pathc_model已经训练了非常多人脸特征,因为每帧变化不大,仅仅依靠这些特征点就能产生no dependency的结果了,仅仅是假设有了空间内部依赖关系。每一个点的跟踪效果会更好)。

      图像对照非常明显。造成这样的差异的原因:仅依照检測到人脸特征的位置进行跟踪会导致过度噪声。因为每一个人脸特征跟踪时。採用模版匹配法,即便在正确的位置,该区域图像在人脸模版上的反馈,也有可能不是最佳的。不管是图像噪声、光照变化、还是表情变化,解决人脸特征”模版匹配式跟踪”局限性的唯一方法,就是借助每一个人脸特征之间的几何关系。

     那么这个几何依赖关系究竟在人脸跟踪时怎么做呢?我们将人脸特征提取的结果投影到形状模型的线性子空间(shape model),也就是最小化原始点集到其在人脸子空间最接近合理形状分布的投影点集的距离。(就是说,把通过模版匹配检測到的原始点集A投影到人脸子空间产生新的点集B,再依照某种约束规则,通过对A迭代变化,使得A’到B的距离最小)。

     这么做的优点:在用融合了几何关系的人脸特征模版匹配法跟踪人脸时,即便空间噪声满足高斯近乎于高斯分布。其检測效果也“最像“人脸的形状。

上一节我们学习了怎样初始化第一帧中的人脸特征点模型。接下来我们要搞明确怎样使用上一帧或第一帧的人脸特征点来预计当前帧的人脸特征点,达到跟踪的目的。我们先来认识下面3个类:

fps_timer类:计算程序运算的速度XX帧/秒,在face_tracker类中track函数调用。

face_tracker_params类:完毕face_tracker中基本參数的初始化、序列化存储。包括搜索区域集合,最大迭代次数,级联分类器參数等等。

face_tracker类:人脸跟踪的核心模块,也是本次介绍的重点,包括train和track两个部分。

 

    人脸跟踪的训练过程face_tracker::train,事实上就是简单的把以往的训练数据又一次打包序列化保存,包括shape_model、patch_model、face_detector。

int main(int argc,char** argv)
{
	//create face tracker model
	face_tracker tracker;
	tracker.smodel = load_ft<shape_model>("shape.xml");
	tracker.pmodel = load_ft<patch_models>("patch.xml");
	tracker.detector = load_ft<face_detector>("detector.xml");

	//save face tracker
	save_ft<face_tracker>("tracker.xml",tracker); 
return 0;
}

    人脸跟踪的track函数。拥有两种功能。

当tracking标志位为fasle时。程序属于构建模型(detectmode)阶段。为第一帧或下一帧图像初始化的人脸特征。所用的技术就是上一节所讲的。当tracking标志位为true时,则依据上一帧人脸特征点的位置预计下一帧的人脸特征,这个操作主要由fit函数完毕。

int
face_tracker::
track(const Mat &im,const face_tracker_params &p)
{
  //convert image to greyscale
  Mat gray; 
  if(im.channels()==1)
     gray = im;
  else
     cvtColor(im,gray,CV_RGB2GRAY);

  //initialise。为第一帧或下一帧初始化人脸特征
  if(!tracking)
    points = detector.detect(gray,p.scaleFactor,p.minNeighbours,p.minSize);
  if((int)points.size() != smodel.npts())
    return 0;

  //fit,通过迭代缩小的搜索范围,预计当前帧中的人脸特征点
  for(int level = 0; level < int(p.ssize.size()); level++)
    points = this->fit(gray,points,p.ssize[level],p.robust,p.itol,p.ftol);

  //set tracking flag and increment timer
  tracking = true; 
  timer.increment(); 
  return 1;
}

     face_tracker::fit函数的主要功能:给定一帧图像及上一帧人脸特征点集,在当前图像上搜索该点集附近的人脸特征,并产生新的人脸特征点集。

fit函数入參:

image:当前帧灰度图像

init:上一帧人脸特征点集(几何位置)

ssize:搜索区域大小

robust:标志位,决定是否採用robustmodel fitting流程,应对人脸特征的孤立点

itol:robust modelfitting迭代上限

ftol:迭代收敛推断阈值

 

返回值:

pts:在给定的搜索区域大小后,当前帧中人脸特征位置点集

//==========================================================================
vector<Point2f>
face_tracker::
fit(const Mat &image,
    const vector<Point2f> &init,
    const Size ssize,
    const bool robust,
    const int itol,
    const float ftol)
{
  int n = smodel.npts();//number of points in shape model
  assert((int(init.size())==n) && (pmodel.n_patches()==n));
  smodel.calc_params(init); vector<Point2f> pts = smodel.calc_shape();

  //find facial features in image around current estimates
  vector<Point2f> peaks = pmodel.calc_peaks(image,pts,ssize);

  //optimise
  if(!robust){
    smodel.calc_params(peaks); //compute shape model parameters        
    pts = smodel.calc_shape(); //update shape
  }else{
    Mat weight(n,1,CV_32F),weight_sort(n,1,CV_32F);
    vector<Point2f> pts_old = pts;
    for(int iter = 0; iter < itol; iter++){
      //compute robust weight
      for(int i = 0; i < n; i++)weight.fl(i) = norm(pts[i] - peaks[i]);
      cv::sort(weight,weight_sort,CV_SORT_EVERY_COLUMN|CV_SORT_ASCENDING);
      double var = 1.4826*weight_sort.fl(n/2); if(var < 0.1)var = 0.1;
      pow(weight,2,weight); weight *= -0.5/(var*var); cv::exp(weight,weight); 

      //compute shape model parameters    
      smodel.calc_params(peaks,weight);
      
      //update shape
      pts = smodel.calc_shape();
      
      //check for convergence
      float v = 0; for(int i = 0; i < n; i++)v += norm(pts[i]-pts_old[i]);
      if(v < ftol)break; else pts_old = pts;
    }
  }return pts;
}

上面代码中,有两个函数:

    shape_mode::calc_param,为了得到合理的人脸子空间投影,我们须要求參数向量p,该函数通过人脸子空间投影坐标集合pts与人脸特征空间标准基V,计算得到參数向量

    patch_models::calc_peaks,依据人脸子空间点集在当前图像内搜索人脸特征,并产生新的人脸特征位置预计

 

    calc_param函数:计算參数向量p时可分为两种投影:simpleprojection和scale projection

假设权值为空,则採用简单投影,因为V是标准正交基:


否则对每一个被投影点设置尺度权值,採用尺度投影,利用opencv提供的神秘值分解法求解非齐次线性系统:

(1)    遍历每一个原始点集,取出联合分布矩阵V2n*k(人脸模型)相应的矩形区域宽k,高2,起始点(0,2i),存入矩阵v2*k

(2)    Opecnv函数solve求解非齐次线性方程,求p


 当中。。w为每一个点的权重,p是须要求解的向量。

(至于权值,老外在后面robust model fitting流程实用到。

因为H和g是对每种表情模式作累加的,所以我猜想权值w的作用是控制每一个人脸特征点对每种表情模式的影响,即仅仅要哪些点就能显著表达相应表情)

最后。不管哪种投影,都经过clamp函数处理。依据c_factor个标准差e约束调整參数向量p,防止人脸投影失真。

//===============================================================
void 
shape_model::
calc_params(const vector<Point2f> &pts,const Mat weight,const float c_factor)
{
  int n = pts.size(); assert(V.rows == 2*n);
  Mat s = Mat(pts).reshape(1,2*n); //point set to vector format
  if(weight.empty())p = V.t()*s;   //simple projection
  else{                            //scaled projection
    if(weight.rows != n){cout << "Invalid weighting matrix" << endl; abort();}
    int K = V.cols; Mat H = Mat::zeros(K,K,CV_32F),g = Mat::zeros(K,1,CV_32F);
    for(int i = 0; i < n; i++){
      Mat v = V(Rect(0,2*i,K,2)); float w = weight.fl(i);
      H += w*v.t()*v; g += w*v.t()*Mat(pts[i]);
    }
    solve(H,g,p,DECOMP_SVD);
  }this->clamp(c_factor);          //clamp resulting parameters
}

[另外,calc_param与calc_shape,假设排除权值,那么是一对互逆的操作]

 

calc_peaks函数:

     通过上面的函数我们得到设置投影范围的參数向量p。然后再调用calc_shape函数就能够得到人脸子空间中的特征点pts。如今我们要在每一个人脸子空间特征点附近搜索包括人脸特征的区域。终于为下一帧生成新的特征预计。

这也是为什么。我们仅仅需在第一帧或者跟踪失败时,才须要调用Opencv级联分类器又一次定位人脸的原因。

calc_peaks函数入參:

image:当前包括人脸的灰度图像

pts:前一帧预计的人脸特征点集在人脸子空间投影坐标集合

ssize:搜索区域窗体大小

返回值:

  当前帧人脸特征预计的点集


//========================================================================
vector<Point2f> 
patch_models::
calc_peaks(const Mat &im,
       const vector<Point2f> &points,
       const Size ssize)
{
  int n = points.size(); assert(n == int(patches.size()));
  Mat pt = Mat(points).reshape(1,2*n);
  Mat S = this->calc_simil(pt);// 计算当前点集到人脸參考模型的变化矩阵
  Mat Si = this->inv_simil(S); //对矩阵S求逆
  vector<Point2f> pts = this->apply_simil(Si,points);
  for(int i = 0; i < n; i++){
    Size wsize = ssize + patches[i].patch_size(); Mat A(2,3,CV_32F);     
    A.fl(0,0) = S.fl(0,0); A.fl(0,1) = S.fl(0,1);
    A.fl(1,0) = S.fl(1,0); A.fl(1,1) = S.fl(1,1);
    A.fl(0,2) = pt.fl(2*i  ) - 
      (A.fl(0,0) * (wsize.width-1)/2 + A.fl(0,1)*(wsize.height-1)/2);
    A.fl(1,2) = pt.fl(2*i+1) - 
      (A.fl(1,0) * (wsize.width-1)/2 + A.fl(1,1)*(wsize.height-1)/2);
    Mat I; warpAffine(im,I,A,wsize,INTER_LINEAR+WARP_INVERSE_MAP);
    Mat R = patches[i].calc_response(I,false);
    Point maxLoc; minMaxLoc(R,0,0,0,&maxLoc);
    pts[i] = Point2f(pts[i].x + maxLoc.x - 0.5*ssize.width,
             pts[i].y + maxLoc.y - 0.5*ssize.height);
  }return this->apply_simil(S,pts);
}

上面代码片段中,介绍下面函数:

(1)    apply_simil函数,对点集points依照Si进行仿射变化(将人脸特征子空间中的坐标经过仿射变换转成图像空间中的坐标,或者反过来)

(2)    calc_response函数,在灰度图像上搜索人脸特征(团块图像)的匹配位置,核心技术是Opencv API:matchTemplate模版匹配函数(图像I,模版T。匹配结果,算法标记)。这里採用的算法是CV_TM_CCOEFF_NORMED,标准相关匹配宏。详细做法就是在原始图像上滑动模版窗体。在一次移动一个像素,最后在每一个像素点上的匹配度量值R(x):



w,h为模版T的宽和高

(3)    minMaxLoc函数(数组 。最小值,最大值,最小值坐标。最大值坐标):寻找矩阵中的最大最小值的位置,这里在矩阵R中寻找最大值,即最佳匹配位置。不须要关注的,API内直接填0就可以。

 

calc_peak整体说来,利用上一帧坐标构造人脸特征的搜索区域,借助之前训练得到的团块模型,在搜索区域内进行模版匹配,找到最优匹配点。作为新一帧人脸特征点的坐标。完整操作步骤例如以下:

(1)    计算前一帧人脸特征坐标到人脸參考模型坐标的仿射变换 S2*3

(2)    计算上述仿射变化的逆矩阵 Si 2*3

(3)    通过逆矩阵Si将人脸特征子空间中的坐标还原成图像帧中的坐标

(4)    遍历全部点。在原始图像中搜索每一个人脸特征模版图像匹配的坐标点

(5)    利用(4)中的坐标,修正人脸特征预计点位置

(6)    利用仿射变化矩阵,再次将图像中的坐标投影到人脸特征子空间中。作为下一帧的人脸特征坐标预计

 

     在对每帧图像进行人脸跟踪时,track函数都会通过fit函数迭代产生多个人脸子空间坐标集合,而且每次迭代的时候。搜索区域都在减小。在迭代过程中,可能会产生非常多孤立的特征点(孤立点,我觉得是模版匹配时得到人脸特征错误预计点。因为本文没有一种机制保证R(X)的反馈一定包括人脸特征)。

为了得到更精确的人脸跟踪效果。假设存在孤立点时。仍採用简单投影simple projection。会严重影响跟踪效果。因此。老外在计算投影參数calc_param时引入了权重。搞了一套robust model fitting流程,特意去除孤立点。

 

权值的计算:

pts,上一帧人脸特征子空间预计点集。

peaks。当前帧人脸特征子空间投影点集(模版匹配)。


     以上公式,本节一開始有提到,想要表达:前后两帧预计点集之差服从高斯分布,即空间噪声满足高斯近乎于高斯分布。

咱有了权值后,就能够计算带权值的參数向量。然后更新投影。

循环退出的条件。循环次数达到最大。或者前后两次又一次计算的投影之间的距离满足阈值ftol。

 

还有两个问题:

a.      为什么用一个逐渐缩小的搜索窗体,多次模版匹配人脸特征?

因为採用模版匹配法。通过多次在尺度不断减小的窗体中寻找人脸特征。能够使得预计点更好的表达人脸特征所在的位置(毕竟是俺像素遍历,在R(X)中挑最大值,多挑几次总是不错的,越挑越细)

 

b.      怎样理解通常情况下。随着搜索范围的减小。孤立点会被自己主动排除(robust=false)?

因此在模版匹配时,挑选R(x)的最大值作为当前特征点,即使因为噪声啥的导致了错误。在下次搜索时,搜索窗体变小了,该错误点附近图像的反馈R(x)一定会变小。所以能够被剔除。

这里假设模版匹配总能找到收敛的位置,假设实在离谱了。那么请按”d”键。又一次初始化人脸检測器把。

 

接下来,完整展示跟踪结果:

#include "ft.hpp"
#include "face_tracker.hpp"
#include <opencv2/highgui/highgui.hpp>
#include <iostream>
#define fl at<float>
//===========================================================================
void
draw_string(Mat img,                       //image to draw on
			const string text)             //text to draw
{
	Size size = getTextSize(text,FONT_HERSHEY_COMPLEX,0.6f,1,NULL);
	putText(img,text,Point(0,size.height),FONT_HERSHEY_COMPLEX,0.6f,
		Scalar::all(0),1,CV_AA);
	putText(img,text,Point(1,size.height+1),FONT_HERSHEY_COMPLEX,0.6f,
		Scalar::all(255),1,CV_AA);
}

//========================================================================
int main(int argc,char** argv)
{
	//load detector model
	Mat im;
	face_tracker tracker = load_ft<face_tracker>("tracker.xml");

	//create tracker parameters
	face_tracker_params p; 
	p.robust = false;
	p.ssize.resize(3);
	p.ssize[0] = Size(21,21);
	p.ssize[1] = Size(11,11);
	p.ssize[2] = Size(5,5);

	//open video stream
	VideoCapture cam; 
	namedWindow("face tracker");

	cam.open("test.avi");
	if(!cam.isOpened()){
		cout << "Failed opening video file." << endl
			<< usage << endl; return 0;
	}
	//detect until user quits

	while(cam.get(CV_CAP_PROP_POS_AVI_RATIO) < 0.999999){		 
		cam >> im; 
		if(tracker.track(im,p))
			tracker.draw(im);
		draw_string(im,"d - redetection");
		tracker.timer.display_fps(im,Point(1,im.rows-1));
		imshow("face tracker",im);
		int c = waitKey(10);
		if(c == 'q')
			break;
		else if(c == 'd')
			tracker.reset();
	}
	destroyWindow("face tracker"); 
	cam.release(); 
	return 0;
}









     老外也比較实在。当人脸跟踪失败的时候,你仅仅要按d,又一次复位人脸跟踪器就可以。

缺点。优点也非常明显,仅仅能一个次跟踪一个人,对于側脸效果非常差

 

五、写了这么多,我们究竟还能做什么(心得与拓展)

    分了4个批次终于写完了,我想应该表达了老外80%-90%的意思。注意我的训练图片太少,而且受限于opencv级联分类器的选择(人脸检測宏CV_HAAR_FIND_BIGGEST_OBJECT),所以对于側脸的跟踪效果不好。

后面的网友能够使用老外的提供的强大数据库。并挑选opencv合理的人脸分类器,应该能够满足側脸跟踪的要求。


全部代码下载地址:

 http://download.csdn.net/detail/jinshengtao/8555713


表情识别:

有人问到这个事情,我是这么想的:

模仿PCA人脸检測算法:http://blog.csdn.net/jinshengtao/article/details/18599165

在那个文章里。我把每一个人脸图像转化成一维向量,多个图像这么转换后就得到训练集。还记那个36000*20的矩阵嘛?然后利用PCA算法,提取主成份,构造平均脸什么的。最后,将測试集图像也投影到平均脸的空间里,计算二者的距离。挑距离最小的作为终于匹配。

这里也能够这么模仿。如今能够比較精确的跟踪每一个人脸的特征点了,我们把每幅图像相应的人脸特征点集也搞成一维的,然后挑选多个表情充分独立的图像所相应的点集构成训练集,一样採用PCA提取主成份,搞个投影空间。

然后表情识别,无非就是算距离。给标签罢了。

 

至于。行人动作识别。这个有难度,须要查文献。

毕竟行人的动作幅度可比人脸表情幅度大多了。非常easy跟丢。

 

最后附上一个叫“大嘴说图”的网友的连接,他罗列了“人脸器官精确定位/人脸特征点的跟踪”的主流算法及代码文档资源(ASM,活动形状模型),有兴趣的朋友能够拓展下。

 

http://blog.sina.com.cn/s/blog_ebbe6d790102vmez.html



posted on 2017-05-13 12:13  wgwyanfs  阅读(348)  评论(0)    收藏  举报

导航