非刚性人脸跟踪(四)

人脸检测与初始化

人脸跟踪方法:假设图像中所找到的面部特征与当前的估计比较接近。

存在问题:如何使用视频第一帧来初始化模型。

解决方法:最简单的是使用opencv内置的级联检测器来搜索人脸,但是模型的检测区域位置将取决于对所跟踪的面部特征的选择。一种基于数据驱动模式的简单方案是,学习人脸检测区域与人脸特征的几何关系。

实现:定义一个face_detector类

class face_detector                     //face detector for initialsation
{
public:
    string detector_fname;              //file containing cascade classifier
    Vec3f detector_offset;              //offset from center of dectection
    Mat reference;                      //reference shape
    CascadeClassifier detector;         //face detector

    vector<Point2f>                             //points for detected face in image
        dectect (const Mat &im,                 //image containing face
        const float scaleFactor = 1.1,          //scale increment
        const int minNeighbours = 2,            //minimum neighbourhood size
        const Size minSize = Size(30,30));      //minimum detection window size

    void train(ft_data &data,                   //trining data
        const string fname,                     //cascade detector
        const Mat &ref,                         //reference shape
        const bool visi = false,                //visualise training?
        const bool mirror = false,              //mirror datd?
        const float frac = 0.8,                 //fraction of points in detected rect
        const float scaleFactor = 1.1,          //scale increment
        const int minNeight = 2,                //minimum neightbourhood size
        const Size minSize = Size(30,30));      //minimum detection window size

    void write(FileStorage &fs) const;          //file storage object to write to

    void read(const FileNode &node);            //file stroge node to raed from

protected:

    bool                                        //are enough points bounded?
        enough_bounded_points(const Mat &pts,   //points to evaluate
        const Rect R,                           //bounding retctangle
        const float frac);                      //fraction of points bounded

    Point2f                                     //center of mass
        center_of_mass(const Mat &pts);         //[x1;y1;...;xn;yn]

    float                                       //scaling from reference to pts
        calc_scale(const Mat &pts);             //[x1;y1;...;xn;yn]

};

detector_fname:保存用于级联分类的文件名;
detect_offset:偏移量集合,保存图像的检测区域到人脸位置和尺度的偏移量;
reference:参考形状;
detector:保存着人脸检测器;

detect函数是人脸跟踪的主要函数,该函数将一幅图像作为输入,该函数返回对图像中面部特征位置的粗略估计。具体实现:

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

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

(3) 根据人脸外界矩形,结合detector_offset和参考形状模型,重新计算标注点的坐标

vector<Point2f> face_detector::dectect(const Mat &im, const float scaleFactor /* = 1.1 */, const int minNeighbours /* = 2 */, const Size minSize /* = Size */)
{
    //convert image to grayscale
    Mat gray;
    if(im.channels() == 1) gray = im; 
    else cvtColor(im, gray, CV_RGB2GRAY);

    //face dectected
    vector<Rect> faces; Mat eqIm; equalizeHist(gray, eqIm);
    //跟踪图像中最明显的人脸
    detector.detectMultiScale(eqIm, faces,scaleFactor,minNeighbours, CV_HAAR_FIND_BIGGEST_OBJECT, minSize);

    //根据人脸检测方框来将参考形状放到图像中
    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.at<float>(2*i) + R.x + 0.5*R.width + scale[0];
        p[i].y = scale[2]*reference.at<float>(2*i + 1) + R.y + 0.5*R.height + scale[1];
    }
    return p;
}

detect_ofdset:由三部分构成:
偏移量(x,y):表示人脸中心到人脸检测方框的距离;
尺度因子:表示为了最好的适应图像中的人脸,调整参考形状大小的比例。
这三部分都是检测方框宽度的线性函数。

问题:检测方框宽度与detect_ofdset变量之间的线性关系如何实现?

解决:加载训练数据到内存并分配参考形状后,使用train函数从标注数据中学习。

具体实现:
训练过程:每幅图首先利用opencv级联分离检索人脸位置,然后在样本图像上标出标注点。接下来判断矩形内标注点的数量是否合理,最后计算我们的偏移量detector_offset。

void face_detector::train(ft_data &data, const string fname, const Mat &ref, const bool visi /* = false */, const bool mirror /* = false */, const float frac /* = 0.8 */, const float scaleFactor /* = 1.1 */, const int minNeighbours /* = 2 */, const Size minSize /* = Size */)
{
    char names[20] = {0};
    //载入级联分配器
    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,CV_HAAR_FIND_BIGGEST_OBJECT, minSize);

        if(faces.size() >= 1)
        {
            if (visi)
            {
                sprintf(names,"train%d.jpg",i);
                //框出人脸区域
                Mat I; cvtColor(im,I,CV_GRAY2BGR);
                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);
                imwrite(names,I);
            }
            //检查检测结果,确保是否有足够多的标注点
            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,CV_HAAR_FIND_BIGGEST_OBJECT,minSize);

            if (faces.size() >= 1)
            {
                if(visi)
                {
                    Mat I; cvtColor(im, I,CV_GRAY2BGR);
                    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 ifenough 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);
                }
            }
        }
    }

    //chosse 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.at<float>(nx/2), Ysort.at<float>(ny/2), Zsort.at<float>(na/2));
    return;
}

显示训练过程的标注结果:

这里写图片描述

获得face_detector对象

与形状模型和块模型一样,我们需要获得face_detector对象并将其保存,用于后续的跟踪系统中。

具体实现

#include "ft.h"
#include "ft_data.h"
#include "face_detector.h"
#include "shape_model.h"
#include <highgui.h>
#include <iostream>


int main()
{
    float frac = 0.8;
    bool mirror = false;

    //load data
    ft_data data = load_ft<ft_data>("annotations.xml");
    shape_model smodel = load_ft<shape_model>("shape.xml");
    smodel.set_identity_params();
    vector<Point2f> r = smodel.calc_shape();
    Mat ref = Mat(r).reshape(1, 2*r.size());

    //train face detector 训练和保存人脸检测器
    face_detector detector;
    detector.train(data, "harrcascade_frontalface_alt.xml", ref, mirror, true, frac);

    //save detector
    save_ft<face_detector>("detector.xml", detector);

    return 0 ;
}

可视化face_detector

跟之前的每个模块的训练与可视化一样,在我们得到face_detector类的detector.xml文件后, 我们可以利用该文件来检测人脸,实现如下:

#include "ft.h"
#include "face_detector.h"
#include <highgui.h>
#include <iostream>

int main()
{
    //load detector model
    face_detector detector = load_ft<face_detector>("detector.xml");

    //detect untial user quits
    namedWindow("face detector");

    Mat im = imread("test.jpg", 1);
    vector <Point2f> p = detector.dectect(im);
    if (p.size() > 0)
    {
        for (int i = 0; i < int(p.size()); i++)
        {
            circle(im, p[i], 1, CV_RGB(0,255,0), 2, CV_AA);
        }
    }
    imwrite("result.jpg", im);
    imshow("face detector", im);
    waitKey(0);

    destroyAllWindows;
    return 0;

}

检测效果图:
这里写图片描述

人脸跟踪

实现:将人脸特征提取的结果投影到形状模型的线性子空间(shape model),也就是最小化原始点集到其在人脸子空间最接近合理形状分布的投影点集的距离。(就是说,把通过模版匹配检测到的原始点集A投影到人脸子空间产生新的点集B,再按照某种约束规则,通过对A迭代变化,使得A’到B的距离最小)。

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

跟踪实现:

#ifndef _FT_FACE_TRACKER_HPP_
#define _FT_FACE_TRACKER_HPP_
#include "patch_model.hpp"
#include "shape_model.hpp"
#include "face_detector.hpp"

class fps_timer{                           //frames/second timer for tracking
public:
  int64 t_start;                           //start time
  int64 t_end;                             //end time
  float fps;                               //current frames/sec
  int fnum;                                //number of frames since @t_start

  fps_timer(){this->reset();}              //default constructor

  void increment();                        //increment timer index

  void reset();                            //reset timer

  void 
  display_fps(Mat &im,                     //image to display FPS on
          Point p = Point(-1,-1));     //bottom left corner of text
};

class face_tracker_params{                 //face tracking parameters
public:
  vector<Size> ssize;                      //search region size/level
  bool robust;                             //use robust fitting?
  int itol;                                //maximum number of iterations to try
  float ftol;                              //convergence tolerance
  float scaleFactor;                       //OpenCV Cascade detector parameters
  int minNeighbours;                       //...
  Size minSize;                            //...

  face_tracker_params();                   //sets default parameter settings

  void 
  write(FileStorage &fs) const;            //file storage object to write to

  void 
  read(const FileNode& node);              //file storage node to read from
};

class face_tracker{                        //face tracking class
public:
  bool tracking;                           //are we in tracking mode?
  fps_timer timer;                         //frames/second timer
  vector<Point2f> points;                  //current tracked points
  face_detector detector;                  //detector for initialisation
  shape_model smodel;                      //shape model
  patch_models pmodel;                     //feature detectors

  face_tracker(){tracking = false;}

  int                                      //0 = failure
  track(const Mat &im,                     //image containing face
    const face_tracker_params &p =     //fitting parameters 
    face_tracker_params());            //default tracking parameters

  void 
  reset(){                                 //reset tracker 
    tracking = false; timer.reset();
  }
  void
  draw(Mat &im,
       const Scalar pts_color = CV_RGB(255,0,0),
       const Scalar con_color = CV_RGB(0,255,0));
  void 
  write(FileStorage &fs) const;            //file storage object to write to

  void 
  read(const FileNode& node);              //file storage node to read from

protected:
  vector<Point2f>                          //points for fitted face in image
  fit(const Mat &image,                    //image containing face
      const vector<Point2f> &init,         //initial point estimates
      const Size ssize = Size(21,21),      //search region size
      const bool robust = false,           //use robust fitting?
      const int itol = 10,                 //maximum number of iterations to try
      const float ftol = 1e-3);            //convergence tolerance
};

#endif

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

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

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

face_tracker::track函数,实现两种功能。当tracking标志位为fasle时,表示跟踪器进入检测模式(detectmode),为第一帧或下一帧图像初始化的人脸特征;当tracking标志位为true时,则根据上一帧人脸特征点的位置估计下一帧的人脸特征,这个操作主要由fit函数完成。跟踪算法的核心是多层次拟合(multi-level-fitting)过程。

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;
}

....

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(); 
  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;
}

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

参数:
image:当前帧灰度图像
init:上一帧人脸特征点集(几何位置)
ssize:搜索区域大小
robust:标志位,决定是否采用robustmodel fitting流程,应对人脸特征的孤立点
itol:robust modelfitting迭代上限
ftol:迭代收敛判断阈值
返回值:
pts:在给定的搜索区域大小后,当前帧中人脸特征位置点集

训练与可视化

跟踪实现:

#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;
        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();![这里写图片描述](http://img.blog.csdn.net/20150510202317519)
    }
    destroyWindow("face tracker"); 
    cam.release(); 
    return 0;
}

结果:

这里写图片描述

代码:http://download.csdn.net/detail/yiluoyan/8684973

reference:http://blog.csdn.net/jinshengtao/article/details/44814297

  • 2
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值