model 类中被完毕,  训练人脸跟踪算法的多少貌似由以下四有的构成

面向对象设计

  与人脸检测和人脸识别一样,人脸跟踪也由两片段组成:数据和算法。算法通过先行储存(即离线)的数量来磨练模型,然后对新来的(即在线)数据举办某类操作。由此,采用面向对象设计是毋庸置疑的挑三拣四。

  在
opencv 2.x 版本中,可便宜引入
XML/YAML 文件存储类型,对算法来讲,会大大简化社团离线数据任务。上面通过三个假象类来呈现那一个职能

  

  • 自定义类
    foo

     1 // foo.h
     2 /*
     3     在下面的代码中,定义了一个序列化函数,可对 I/O 函数 read 和 write 实现序列化。
     4     FileStorage 类支持两种能被序列化的数据结构类型。
     5     为了简单起见,本章所有类将采用映射,其中每个用于存储的变量都会创建一个 FileNode::MAP 类型的 FileNode 对象。
     6     这需要分配给变量中的每个元素唯一键。为了保持一致性,将变量名作为标签
     7 */
     8 
     9 #include <opencv2/opencv.hpp>
    10 #include <iostream>
    11 using namespace cv;
    12 using namespace std;
    13 
    14 class foo {
    15 public:
    16     int a, b;        
    17     void write(FileStorage &fs) const {            // 序列化存储自定义数据类型
    18         assert(fs.isOpened());
    19         fs << "{" << "a" << a << "b" << b << "}";        // 创建 FileNode::MAP 类型的对象
    20     }
    21     void read(const FileNode& node) {            // 读取数据
    22         assert(node.type() == FileNode::MAP);
    23         node["a"] >> a;    node["b"] >> b;
    24     }
    25 };
    
  • 为了使
    FileStorage 类的体系化能健康工作,还必要定义write,
    read函数

     1 template<class T>
     2 void 
     3 write(FileStorage& fs, 
     4       const string&, 
     5       const T& x)
     6 {
     7   x.write(fs);
     8 }
     9 //==============================================================================
    10 template<class T>
    11 void 
    12 read(const FileNode& node, 
    13      T& x,
    14      const T& d)
    15 {
    16   if(node.empty())x = d; else x.read(node);
    17 }
    

     

 

  • 为了让保存和加载采纳了种类化的用户自定义类变得简单,接纳模块化函数定义了load_ft,save_ft函数

     1 template <class T> 
     2 T load_ft(const char* fname){
     3   T x; FileStorage f(fname,FileStorage::READ);
     4   f["ft object"] >> x; f.release(); return x;    // 定义与对象关联的标签都为 ft object
     5 }
     6 //==============================================================================
     7 template<class T>
     8 void save_ft(const char* fname,const T& x){
     9   FileStorage f(fname,FileStorage::WRITE);
    10   f << "ft object" << x; f.release();
    11 }
    
  • 将以上定义在
    ft.hpp 中
    图片 1图片 2

     1 /*
     2     ft.hpp
     3     用于加载、保存对象数据
     4 */
     5 
     6 #ifndef _FT_FT_HPP_
     7 #define _FT_FT_HPP_
     8 #include <opencv2/opencv.hpp> 
     9 //==============================================================================
    10 // 为了让保存和加载采用了序列化的用户自定义类变得容易,采用模块化函数定义了load_ft,save_ft函数
    11 template <class T> 
    12 T load_ft(const char* fname){
    13   T x; FileStorage f(fname,FileStorage::READ);
    14   f["ft object"] >> x; f.release(); return x;    // 定义与对象关联的标签都为 ft object
    15 }
    16 //==============================================================================
    17 template<class T>
    18 void save_ft(const char* fname,const T& x){
    19   FileStorage f(fname,FileStorage::WRITE);
    20   f << "ft object" << x; f.release();
    21 }
    22 //==============================================================================
    23 // 为了使 FileStorage 类的序列化能正常工作,还需要定义write, read函数
    24 template<class T>
    25 void 
    26 write(FileStorage& fs, 
    27       const string&, 
    28       const T& x)
    29 {
    30   x.write(fs);
    31 }
    32 //==============================================================================
    33 template<class T>
    34 void 
    35 read(const FileNode& node, 
    36      T& x,
    37      const T& d)
    38 {
    39   if(node.empty())x = d; else x.read(node);
    40 }
    41 //==============================================================================
    42 #endif
    

    ft.hpp

  • 主函数,有贰个标题,储存到
    xml 文件连续报错,而 yaml 文件可以寻常存取

     1 /*
     2     main.cpp
     3     测试 opencv 文件储存
     4 */
     5 
     6 #include "opencv_hotshots/ft/ft.hpp"
     7 #include "foo.h"
     8 
     9 int main() {
    10     foo A;                // 初始化自定义对象 A
    11     A.a = 1; A.b = 2;
    12     save_ft<foo>("foo.yaml", A);    // 将自定义对象存到 foo.yaml
    13     foo B = load_ft<foo>("foo.yaml");    // 读取对象
    14     cout << B.a << "," << B.b << endl;
    15 
    16     system("pause");
    17     return 0;
    18 }
    
  • 程序运营结果

       
        图片 3               
  图片 4

 

 

 

面向对象设计

  与人脸检测和人脸识别一样,人脸跟踪也由两片段组成:数据和算法。算法通过事先储存(即离线)的数量来练习模型,然后对新来的(即在线)数据实施某类操作。由此,拔取面向对象设计是未可厚非的挑三拣四。

  在
opencv 2.x 版本中,可方便引入
XML/YAML 文件存储类型,对算法来讲,会大大简化协会离线数据任务。下边通过一个假象类来展现这么些作用

  

  • 自定义类
    foo

     1 // foo.h
     2 /*
     3     在下面的代码中,定义了一个序列化函数,可对 I/O 函数 read 和 write 实现序列化。
     4     FileStorage 类支持两种能被序列化的数据结构类型。
     5     为了简单起见,本章所有类将采用映射,其中每个用于存储的变量都会创建一个 FileNode::MAP 类型的 FileNode 对象。
     6     这需要分配给变量中的每个元素唯一键。为了保持一致性,将变量名作为标签
     7 */
     8 
     9 #include <opencv2/opencv.hpp>
    10 #include <iostream>
    11 using namespace cv;
    12 using namespace std;
    13 
    14 class foo {
    15 public:
    16     int a, b;        
    17     void write(FileStorage &fs) const {            // 序列化存储自定义数据类型
    18         assert(fs.isOpened());
    19         fs << "{" << "a" << a << "b" << b << "}";        // 创建 FileNode::MAP 类型的对象
    20     }
    21     void read(const FileNode& node) {            // 读取数据
    22         assert(node.type() == FileNode::MAP);
    23         node["a"] >> a;    node["b"] >> b;
    24     }
    25 };
    
  • 为了使
    FileStorage 类的连串化能健康干活,还亟需定义write,
    read函数

     1 template<class T>
     2 void 
     3 write(FileStorage& fs, 
     4       const string&, 
     5       const T& x)
     6 {
     7   x.write(fs);
     8 }
     9 //==============================================================================
    10 template<class T>
    11 void 
    12 read(const FileNode& node, 
    13      T& x,
    14      const T& d)
    15 {
    16   if(node.empty())x = d; else x.read(node);
    17 }
    

     

 

  • 为了让保存和加载接纳了系列化的用户自定义类变得不难,接纳模块化函数定义了load_ft,save_ft函数

     1 template <class T> 
     2 T load_ft(const char* fname){
     3   T x; FileStorage f(fname,FileStorage::READ);
     4   f["ft object"] >> x; f.release(); return x;    // 定义与对象关联的标签都为 ft object
     5 }
     6 //==============================================================================
     7 template<class T>
     8 void save_ft(const char* fname,const T& x){
     9   FileStorage f(fname,FileStorage::WRITE);
    10   f << "ft object" << x; f.release();
    11 }
    
  • 将以上定义在
    ft.hpp 中
    图片 5图片 6

     1 /*
     2     ft.hpp
     3     用于加载、保存对象数据
     4 */
     5 
     6 #ifndef _FT_FT_HPP_
     7 #define _FT_FT_HPP_
     8 #include <opencv2/opencv.hpp> 
     9 //==============================================================================
    10 // 为了让保存和加载采用了序列化的用户自定义类变得容易,采用模块化函数定义了load_ft,save_ft函数
    11 template <class T> 
    12 T load_ft(const char* fname){
    13   T x; FileStorage f(fname,FileStorage::READ);
    14   f["ft object"] >> x; f.release(); return x;    // 定义与对象关联的标签都为 ft object
    15 }
    16 //==============================================================================
    17 template<class T>
    18 void save_ft(const char* fname,const T& x){
    19   FileStorage f(fname,FileStorage::WRITE);
    20   f << "ft object" << x; f.release();
    21 }
    22 //==============================================================================
    23 // 为了使 FileStorage 类的序列化能正常工作,还需要定义write, read函数
    24 template<class T>
    25 void 
    26 write(FileStorage& fs, 
    27       const string&, 
    28       const T& x)
    29 {
    30   x.write(fs);
    31 }
    32 //==============================================================================
    33 template<class T>
    34 void 
    35 read(const FileNode& node, 
    36      T& x,
    37      const T& d)
    38 {
    39   if(node.empty())x = d; else x.read(node);
    40 }
    41 //==============================================================================
    42 #endif
    

    ft.hpp

  • 主函数,有3个题材,储存到
    xml 文件延续报错,而 yaml 文件可以符合规律存取

     1 /*
     2     main.cpp
     3     测试 opencv 文件储存
     4 */
     5 
     6 #include "opencv_hotshots/ft/ft.hpp"
     7 #include "foo.h"
     8 
     9 int main() {
    10     foo A;                // 初始化自定义对象 A
    11     A.a = 1; A.b = 2;
    12     save_ft<foo>("foo.yaml", A);    // 将自定义对象存到 foo.yaml
    13     foo B = load_ft<foo>("foo.yaml");    // 读取对象
    14     cout << B.a << "," << B.b << endl;
    15 
    16     system("pause");
    17     return 0;
    18 }
    
  • 程序运转结果

       
        图片 7               
  图片 8

 

 

 

  本节将用一种象征方法来树立人脸特征检测器,该措施大概是人们觉得最简单易行的模子,即:线性图像模型。由于该算法需表示一个图象块,由此那种面部特征检测器称为块模型(
patch model
)。该模型在 patch_model 类中被达成,该类的定义和落到实处可个别在 patch_model.hpp 和 patch_model.cpp 文件中找到。上面的那段代码给出了 patch_model 类的最主要功能:

多少搜集:图像和录像标注

  现代人脸跟踪技术大概完全是多少驱动,即用来检测图像中面部特征地点的算法依靠面部特征的外观模型和几何正视性,该倚重性来自样本集中人脸间的相对地点。样本集越大,算法就更有着鲁棒性,因为人脸所表现出的转变范围就更精通。由此,创设人脸跟踪算法的率先步是创设用于进行图像/录像的标号工具,用户可用此工具来指定在种种样本图中想要的颜面特征地方。

  1. ### 练习数据类型

  锻练人脸跟踪算法的数码貌似由以下四局地组成:

    • 图像:这一部分是富含全部人脸图像(图像或摄像帧)的集合
    • 标明:那有的行使手工方法标明每幅图像中被盯梢的颜面特征的相对地点
    • 对称性索引:那部分对定义了二者对称特征的面庞特征点都封存了二个数码,以便用来镜像训练图像,可有效地让教练集大小扩张一倍
    • 连通性索引:那有的是一组标注的目录对,它们定义了颜面特征的语义解释。连通性对可视化跟踪结果很有用

  那两个零部件的可视化境况突显在下图中,从左到右依次是本来图像、脸部特征标注、颜色编码的两边对称点、镜像图像与相应标注、面部特征的连通性。

   
  图片 9

 

  为了方便管理那种数据,需兑现全数读写功效的类。本章将使用在
ft_data.hpp 头文件中定义的
ft_data 类,它是按面部跟踪数据的表征专门布置的。全部因素都定义成类的国有成员变量,如下所示

1 class ft_data{                             //人脸跟踪数据
2 public:
3   vector<int> symmetry;                    // 人脸特征点的索引,维数与用户定义的特征点数一样
4   vector<Vec2i> connections;               // 定义一对连通的面部特征
5   vector<string> imnames;                  // 存储每个图像文件名
6   vector<vector<Point2f> > points;         // 存储特征点的位置
7   ...
8 }

 

 

  ft_data 类达成了广大拜访数据的一蹴而就方法。为了访问数据集的图像,可用
get_image 函数加载图像。使用该函数需点名加载图像的索引 idx
,以及是或不是将图像以 y 轴做镜像。该函数达成如下:

 1 Mat
 2 ft_data::
 3 get_image(const int idx,    // 图像索引
 4       const int flag)        // 0=gray,1=gray+flip,2=rgb,3=rgb+flip
 5 {
 6   if((idx < 0) || (idx >= (int)imnames.size()))return Mat();
 7   Mat img,im;
 8   if(flag < 2)img = imread(imnames[idx],0);        // gray
 9   else img = imread(imnames[idx],1);            // rgb
10   if(flag % 2 != 0)flip(img,im,1);                // 以 y 轴做镜像
11   else im = img;
12   return im;
13 }

 

 

  为了通过点名的目录来收获相应图像的一个点集,可利用
get_points 函数经过镜像索引来得到三个基于浮点的坐标向量

 1 vector<Point2f>
 2 ft_data::
 3 get_points(const int idx,        // 相应图像的索引
 4        const bool flipped)        // 是否以 y 轴做镜像
 5 {
 6   if((idx < 0) || (idx >= (int)imnames.size()))return vector<Point2f>();
 7   vector<Point2f> p = points[idx];
 8   if(flipped){        // 以 y 轴做镜像
 9     Mat im = this->get_image(idx,0);    // im 用来获取图像的宽度
10     int n = p.size(); vector<Point2f> q(n);
11     for(int i = 0; i < n; i++){            // 沿竖直方向翻转    
12       q[i].x = im.cols-1-p[symmetry[i]].x;
13       q[i].y = p[symmetry[i]].y;
14     }return q;
15   }else return p;
16 }

 

 

  ft_data 类还完结了二个函数
rm_incomplete_samples,该函数删除集合中从不开展对应标注的样本,具体落成如下:

 1 void
 2 ft_data::
 3 rm_incomplete_samples()        // 删除集合中没有进行相应标注的样本
 4 {
 5   int n = points[0].size(),N = points.size();
 6   // 找出标注数最多的样本,作为标准样本
 7   for(int i = 1; i < N; i++)n = max(n,int(points[i].size()));    
 8   for(int i = 0; i < int(points.size()); i++){
 9     if(int(points[i].size()) != n){        // 样本标注点的数量小于标准样本标注点数,从样本中删除
10       points.erase(points.begin()+i); imnames.erase(imnames.begin()+i); i--;
11     }else{
12       int j = 0;
13       for(; j < n; j++){
14         // 若点的(x,y)存在小于0,则可认为它在相应的图像中不存在
15         if((points[i][j].x <= 0) || (points[i][j].y <= 0))break;
16       }
17       if(j < n){    // 从样本中删除
18     points.erase(points.begin()+i); imnames.erase(imnames.begin()+i); i--;
19       }
20     }
21   }
22 }

 

 

  ft_data 类还已毕了函数 read 和 write 的系列化,那样就足以便宜地蕴藏和加载该类。

图片 10图片 11

 1 void 
 2 ft_data::
 3 write(FileStorage &fs) const
 4 {
 5   assert(fs.isOpened()); 
 6   fs << "{";
 7   fs << "n_connections" << (int)connections.size();        // 面部特征的语义解释
 8   for(int i = 0; i < int(connections.size()); i++){
 9     char str[256]; const char* ss;
10     sprintf(str,"connections %d 0",i); ss = str; fs << ss << connections[i][0];
11     sprintf(str,"connections %d 1",i); ss = str; fs << ss << connections[i][1];
12   }
13   fs << "n_symmetry" << (int)symmetry.size();            // 特征点的索引
14   for(int i = 0; i < int(symmetry.size()); i++){
15     char str[256]; const char* ss;
16     sprintf(str,"symmetry %d",i); ss = str; fs << ss << symmetry[i];
17   }
18   fs << "n_images" << (int)imnames.size();                // 图像绝对路径
19   for(int i = 0; i < int(imnames.size()); i++){
20     char str[256]; const char* ss;
21     sprintf(str,"image %d",i); ss = str; fs << ss << imnames[i];
22   }
23   int n = points[0].size(),N = points.size();            // 描述人脸特征点的结构
24   Mat X(2*n,N,CV_32F); X = -1;
25   for(int i = 0; i < N; i++){
26     if(int(points[i].size()) == n){
27       for(int j = 0; j < n; j++){
28     X.at<float>(2*j  ,i) = points[i][j].x;
29     X.at<float>(2*j+1,i) = points[i][j].y;
30       }
31     }
32   }
33   fs << "shapes" << X << "}";
34 }
35 //==============================================================================
36 void
37 ft_data::
38 read(const FileNode& node)
39 {
40   assert(node.type() == FileNode::MAP);
41   int n; node["n_connections"] >> n; connections.resize(n);
42   for(int i = 0; i < n; i++){
43     char str[256]; const char* ss;
44     sprintf(str,"connections %d 0",i); ss = str; node[ss] >> connections[i][0];
45     sprintf(str,"connections %d 1",i); ss = str; node[ss] >> connections[i][1];
46   }
47   node["n_symmetry"] >> n; symmetry.resize(n);
48   for(int i = 0; i < n; i++){
49     char str[256]; const char* ss;
50     sprintf(str,"symmetry %d",i); ss = str; node[ss] >> symmetry[i];
51   }
52   node["n_images"] >> n; imnames.resize(n);
53   for(int i = 0; i < n; i++){
54     char str[256]; const char* ss;
55     sprintf(str,"image %d",i); ss = str; node[ss] >> imnames[i];
56   }
57   Mat X; node["shapes"] >> X; int N = X.cols; n = X.rows/2; 
58   points.resize(N);
59   for(int i = 0; i < N; i++){
60     points[i].clear();
61     for(int j = 0; j < n; j++){
62       Point2f p(X.at<float>(2*j,i),X.at<float>(2*j+1,i));
63       if((p.x >= 0) && (p.y >= 0))points[i].push_back(p);
64     }
65   }
66 }

read write

 

 

 

  为对数码集举办可视化操作,
ft_data 达成了累累用于绘图的函数。

图片 12图片 13

  1 void
  2 ft_data::
  3 draw_points(Mat &im,
  4         const int idx,
  5         const bool flipped,
  6         const Scalar color,
  7         const vector<int> &pts)
  8 {
  9   if((idx < 0) || (idx >= (int)imnames.size()))return;
 10   int n = points[idx].size();
 11   if(pts.size() == 0){
 12     for(int i = 0; i < n; i++){
 13       if(!flipped)circle(im,points[idx][i],1,color,2,CV_AA);
 14       else{
 15     Point2f p(im.cols - 1 - points[idx][symmetry[i]].x,
 16           points[idx][symmetry[i]].y);
 17     circle(im,p,1,color,2,CV_AA);
 18       }
 19     }
 20   }else{
 21     int m = pts.size();
 22     for(int j = 0; j < m; j++){
 23       int i = pts[j]; if((i < 0) || (i >= n))continue;
 24       if(!flipped)circle(im,points[idx][i],1,color,2,CV_AA);
 25       else{
 26     Point2f p(im.cols - 1 - points[idx][symmetry[i]].x,
 27           points[idx][symmetry[i]].y);
 28     circle(im,p,1,color,2,CV_AA);
 29       }
 30     }
 31   }
 32 }
 33 //==============================================================================
 34 void
 35 ft_data::
 36 draw_sym(Mat &im,
 37      const int idx,
 38      const bool flipped,
 39      const vector<int> &pts)
 40 {
 41   if((idx < 0) || (idx >= (int)imnames.size()))return;
 42   int n = points[idx].size();
 43   RNG rn; vector<Scalar> colors(n); 
 44   for(int i = 0; i < n; i++)colors[i] = Scalar::all(0.0);
 45   for(int i = 0; i < n; i++){
 46     if(colors[i] == Scalar::all(0.0)){
 47       colors[i] = Scalar(rn.uniform(0,255),rn.uniform(0,255),rn.uniform(0,255));
 48       colors[symmetry[i]] = colors[i];
 49     }
 50   }
 51   vector<Point2f> p = this->get_points(idx,flipped); 
 52   if(pts.size() == 0){
 53     for(int i = 0; i < n; i++){circle(im,p[i],1,colors[i],2,CV_AA);}
 54   }else{
 55     int m = pts.size();
 56     for(int j = 0; j < m; j++){
 57       int i = pts[j]; if((i < 0) || (i >= n))continue;
 58       circle(im,p[i],1,colors[i],2,CV_AA);
 59     }
 60   }
 61 }
 62 //==============================================================================
 63 void
 64 ft_data::
 65 draw_connect(Mat &im,
 66          const int idx,
 67          const bool flipped,
 68          const Scalar color,
 69          const vector<int> &con)
 70 {
 71   if((idx < 0) || (idx >= (int)imnames.size()))return;
 72   int n = connections.size();
 73   if(con.size() == 0){    
 74     for(int i = 0; i < n; i++){
 75       int j = connections[i][0],k = connections[i][1];
 76       if(!flipped)line(im,points[idx][j],points[idx][k],color,1);
 77       else{
 78     Point2f p(im.cols - 1 - points[idx][symmetry[j]].x,
 79           points[idx][symmetry[j]].y);
 80     Point2f q(im.cols - 1 - points[idx][symmetry[k]].x,
 81           points[idx][symmetry[k]].y);
 82     line(im,p,q,color,1);
 83       }
 84     }
 85   }else{
 86     int m = con.size();
 87     for(int j = 0; j < m; j++){
 88       int i = con[j]; if((i < 0) || (i >= n))continue;
 89       int k = connections[i][0],l = connections[i][1];
 90       if(!flipped)line(im,points[idx][k],points[idx][l],color,1);
 91       else{
 92     Point2f p(im.cols - 1 - points[idx][symmetry[k]].x,
 93           points[idx][symmetry[k]].y);
 94     Point2f q(im.cols - 1 - points[idx][symmetry[l]].x,
 95           points[idx][symmetry[l]].y);
 96     line(im,p,q,color,1);
 97       }
 98     }
 99   }
100 }

绘图函数

 

 

 

数量收集:图像和视频标注

  现代人脸跟踪技术大约全盘是数量驱动,即用来检测图像中面部特征地点的算法依靠面部特征的外观模型和几何体贴性,该倚重性来自样本集中人脸间的顶牛位置。样本集越大,算法就更具备鲁棒性,因为人脸所突显出的变迁范围就更明亮。由此,创设人脸跟踪算法的率先步是创建用于开展图像/录像的标号工具,用户可用此工具来指定在各样样本图中想要的面孔特征地方。

  1. ### 练习数据类型

  练习人脸跟踪算法的数目一般由以下四部分构成:

    • 图像:那部分是蕴含全部人脸图像(图像或视频帧)的成团
    • 标明:这一部分行使手工方法标明每幅图像中被跟踪的面孔特征的相持地方
    • 对称性索引:那有些对定义了双面对称特征的脸面特征点都保留了壹个编号,以便用来镜像练习图像,可使得地让教练集大小扩展一倍
    • 连通性索引:那部分是一组标注的目录对,它们定义了面部特征的语义解释。连通性对可视化跟踪结果很有用

  那五个零件的可视化景况显示在下图中,从左到右依次是土生土长图像、脸部特征标注、颜色编码的双面对称点、镜像图像与相应标注、面部特征的连通性。

   
  图片 14

 

  为了方便管理这种数据,需兑现全数读写效能的类。本章将动用在
ft_data.hpp 头文件中定义的
ft_data 类,它是按面部跟踪数据的风味专门安排的。全数因素都定义成类的国有成员变量,如下所示

1 class ft_data{                             //人脸跟踪数据
2 public:
3   vector<int> symmetry;                    // 人脸特征点的索引,维数与用户定义的特征点数一样
4   vector<Vec2i> connections;               // 定义一对连通的面部特征
5   vector<string> imnames;                  // 存储每个图像文件名
6   vector<vector<Point2f> > points;         // 存储特征点的位置
7   ...
8 }

 

 

  ft_data 类完毕了很多走访数据的立见成效方法。为了访问数据集的图像,可用
get_image 函数加载图像。使用该函数需点名加载图像的索引 idx
,以及是还是不是将图像以 y 轴做镜像。该函数完结如下:

 1 Mat
 2 ft_data::
 3 get_image(const int idx,    // 图像索引
 4       const int flag)        // 0=gray,1=gray+flip,2=rgb,3=rgb+flip
 5 {
 6   if((idx < 0) || (idx >= (int)imnames.size()))return Mat();
 7   Mat img,im;
 8   if(flag < 2)img = imread(imnames[idx],0);        // gray
 9   else img = imread(imnames[idx],1);            // rgb
10   if(flag % 2 != 0)flip(img,im,1);                // 以 y 轴做镜像
11   else im = img;
12   return im;
13 }

 

 

  为了通过点名的目录来赢得相应图像的二个点集,可利用
get_points 函数因此镜像索引来拿到贰个基于浮点的坐标向量

 1 vector<Point2f>
 2 ft_data::
 3 get_points(const int idx,        // 相应图像的索引
 4        const bool flipped)        // 是否以 y 轴做镜像
 5 {
 6   if((idx < 0) || (idx >= (int)imnames.size()))return vector<Point2f>();
 7   vector<Point2f> p = points[idx];
 8   if(flipped){        // 以 y 轴做镜像
 9     Mat im = this->get_image(idx,0);    // im 用来获取图像的宽度
10     int n = p.size(); vector<Point2f> q(n);
11     for(int i = 0; i < n; i++){            // 沿竖直方向翻转    
12       q[i].x = im.cols-1-p[symmetry[i]].x;
13       q[i].y = p[symmetry[i]].y;
14     }return q;
15   }else return p;
16 }

 

 

  ft_data 类还落到实处了八个函数
rm_incomplete_samples,该函数删除集合中尚无举行相应标注的范本,具体贯彻如下:

 1 void
 2 ft_data::
 3 rm_incomplete_samples()        // 删除集合中没有进行相应标注的样本
 4 {
 5   int n = points[0].size(),N = points.size();
 6   // 找出标注数最多的样本,作为标准样本
 7   for(int i = 1; i < N; i++)n = max(n,int(points[i].size()));    
 8   for(int i = 0; i < int(points.size()); i++){
 9     if(int(points[i].size()) != n){        // 样本标注点的数量小于标准样本标注点数,从样本中删除
10       points.erase(points.begin()+i); imnames.erase(imnames.begin()+i); i--;
11     }else{
12       int j = 0;
13       for(; j < n; j++){
14         // 若点的(x,y)存在小于0,则可认为它在相应的图像中不存在
15         if((points[i][j].x <= 0) || (points[i][j].y <= 0))break;
16       }
17       if(j < n){    // 从样本中删除
18     points.erase(points.begin()+i); imnames.erase(imnames.begin()+i); i--;
19       }
20     }
21   }
22 }

 

 

  ft_data 类还完结了函数 read 和 write 的连串化,这样就足以一本万利地蕴藏和加载该类。

图片 15图片 16

 1 void 
 2 ft_data::
 3 write(FileStorage &fs) const
 4 {
 5   assert(fs.isOpened()); 
 6   fs << "{";
 7   fs << "n_connections" << (int)connections.size();        // 面部特征的语义解释
 8   for(int i = 0; i < int(connections.size()); i++){
 9     char str[256]; const char* ss;
10     sprintf(str,"connections %d 0",i); ss = str; fs << ss << connections[i][0];
11     sprintf(str,"connections %d 1",i); ss = str; fs << ss << connections[i][1];
12   }
13   fs << "n_symmetry" << (int)symmetry.size();            // 特征点的索引
14   for(int i = 0; i < int(symmetry.size()); i++){
15     char str[256]; const char* ss;
16     sprintf(str,"symmetry %d",i); ss = str; fs << ss << symmetry[i];
17   }
18   fs << "n_images" << (int)imnames.size();                // 图像绝对路径
19   for(int i = 0; i < int(imnames.size()); i++){
20     char str[256]; const char* ss;
21     sprintf(str,"image %d",i); ss = str; fs << ss << imnames[i];
22   }
23   int n = points[0].size(),N = points.size();            // 描述人脸特征点的结构
24   Mat X(2*n,N,CV_32F); X = -1;
25   for(int i = 0; i < N; i++){
26     if(int(points[i].size()) == n){
27       for(int j = 0; j < n; j++){
28     X.at<float>(2*j  ,i) = points[i][j].x;
29     X.at<float>(2*j+1,i) = points[i][j].y;
30       }
31     }
32   }
33   fs << "shapes" << X << "}";
34 }
35 //==============================================================================
36 void
37 ft_data::
38 read(const FileNode& node)
39 {
40   assert(node.type() == FileNode::MAP);
41   int n; node["n_connections"] >> n; connections.resize(n);
42   for(int i = 0; i < n; i++){
43     char str[256]; const char* ss;
44     sprintf(str,"connections %d 0",i); ss = str; node[ss] >> connections[i][0];
45     sprintf(str,"connections %d 1",i); ss = str; node[ss] >> connections[i][1];
46   }
47   node["n_symmetry"] >> n; symmetry.resize(n);
48   for(int i = 0; i < n; i++){
49     char str[256]; const char* ss;
50     sprintf(str,"symmetry %d",i); ss = str; node[ss] >> symmetry[i];
51   }
52   node["n_images"] >> n; imnames.resize(n);
53   for(int i = 0; i < n; i++){
54     char str[256]; const char* ss;
55     sprintf(str,"image %d",i); ss = str; node[ss] >> imnames[i];
56   }
57   Mat X; node["shapes"] >> X; int N = X.cols; n = X.rows/2; 
58   points.resize(N);
59   for(int i = 0; i < N; i++){
60     points[i].clear();
61     for(int j = 0; j < n; j++){
62       Point2f p(X.at<float>(2*j,i),X.at<float>(2*j+1,i));
63       if((p.x >= 0) && (p.y >= 0))points[i].push_back(p);
64     }
65   }
66 }

read write

 

 

 

  为对数码集进行可视化操作,
ft_data 完毕了很多用来绘图的函数。

图片 17图片 18

  1 void
  2 ft_data::
  3 draw_points(Mat &im,
  4         const int idx,
  5         const bool flipped,
  6         const Scalar color,
  7         const vector<int> &pts)
  8 {
  9   if((idx < 0) || (idx >= (int)imnames.size()))return;
 10   int n = points[idx].size();
 11   if(pts.size() == 0){
 12     for(int i = 0; i < n; i++){
 13       if(!flipped)circle(im,points[idx][i],1,color,2,CV_AA);
 14       else{
 15     Point2f p(im.cols - 1 - points[idx][symmetry[i]].x,
 16           points[idx][symmetry[i]].y);
 17     circle(im,p,1,color,2,CV_AA);
 18       }
 19     }
 20   }else{
 21     int m = pts.size();
 22     for(int j = 0; j < m; j++){
 23       int i = pts[j]; if((i < 0) || (i >= n))continue;
 24       if(!flipped)circle(im,points[idx][i],1,color,2,CV_AA);
 25       else{
 26     Point2f p(im.cols - 1 - points[idx][symmetry[i]].x,
 27           points[idx][symmetry[i]].y);
 28     circle(im,p,1,color,2,CV_AA);
 29       }
 30     }
 31   }
 32 }
 33 //==============================================================================
 34 void
 35 ft_data::
 36 draw_sym(Mat &im,
 37      const int idx,
 38      const bool flipped,
 39      const vector<int> &pts)
 40 {
 41   if((idx < 0) || (idx >= (int)imnames.size()))return;
 42   int n = points[idx].size();
 43   RNG rn; vector<Scalar> colors(n); 
 44   for(int i = 0; i < n; i++)colors[i] = Scalar::all(0.0);
 45   for(int i = 0; i < n; i++){
 46     if(colors[i] == Scalar::all(0.0)){
 47       colors[i] = Scalar(rn.uniform(0,255),rn.uniform(0,255),rn.uniform(0,255));
 48       colors[symmetry[i]] = colors[i];
 49     }
 50   }
 51   vector<Point2f> p = this->get_points(idx,flipped); 
 52   if(pts.size() == 0){
 53     for(int i = 0; i < n; i++){circle(im,p[i],1,colors[i],2,CV_AA);}
 54   }else{
 55     int m = pts.size();
 56     for(int j = 0; j < m; j++){
 57       int i = pts[j]; if((i < 0) || (i >= n))continue;
 58       circle(im,p[i],1,colors[i],2,CV_AA);
 59     }
 60   }
 61 }
 62 //==============================================================================
 63 void
 64 ft_data::
 65 draw_connect(Mat &im,
 66          const int idx,
 67          const bool flipped,
 68          const Scalar color,
 69          const vector<int> &con)
 70 {
 71   if((idx < 0) || (idx >= (int)imnames.size()))return;
 72   int n = connections.size();
 73   if(con.size() == 0){    
 74     for(int i = 0; i < n; i++){
 75       int j = connections[i][0],k = connections[i][1];
 76       if(!flipped)line(im,points[idx][j],points[idx][k],color,1);
 77       else{
 78     Point2f p(im.cols - 1 - points[idx][symmetry[j]].x,
 79           points[idx][symmetry[j]].y);
 80     Point2f q(im.cols - 1 - points[idx][symmetry[k]].x,
 81           points[idx][symmetry[k]].y);
 82     line(im,p,q,color,1);
 83       }
 84     }
 85   }else{
 86     int m = con.size();
 87     for(int j = 0; j < m; j++){
 88       int i = con[j]; if((i < 0) || (i >= n))continue;
 89       int k = connections[i][0],l = connections[i][1];
 90       if(!flipped)line(im,points[idx][k],points[idx][l],color,1);
 91       else{
 92     Point2f p(im.cols - 1 - points[idx][symmetry[k]].x,
 93           points[idx][symmetry[k]].y);
 94     Point2f q(im.cols - 1 - points[idx][symmetry[l]].x,
 95           points[idx][symmetry[l]].y);
 96     line(im,p,q,color,1);
 97       }
 98     }
 99   }
100 }

绘图函数

 

 

 

 1 class patch_model{                         //correlation-based patch expert
 2 public:
 3   Mat P;                                   //normalised patch
 4   ...   
 5   Mat                                      //response map (CV_32F)
 6   calc_response(const Mat &im,             //image to compute response from
 7         const bool sum2one=false); //normalize response to sum to one?
 8   void 
 9   train(const vector<Mat> &images,         //feature centered training images
10     const Size psize,                  //desired patch size
11     const float var = 1.0,             //variance of annotation error
12     const float lambda = 1e-6,         //regularization weight
13     const float mu_init = 1e-3,        //initial stoch-grad step size
14     const int nsamples = 1000,         //number of stoch-grad samples
15     const bool visi = false);          //visualize intermediate results?
16     ...
17 };

  2. 标明工具

   为了使生成的标号能被本章中的代码应用,可在 annotate.cpp 文件中找到1个着力的标号工具。该工具将二个视屏流作为输入,这一个视频流可以来自文件或相机、使用该工具的进度有如下八个步骤:

  • 抓获图像:第3步是将图像流显示在屏幕上,用户按下
    S 键就可选择图像进行标注。

    • 重中之重代码如下:

       1 //选择图像进行标注
       2 annotation.set_capture_instructions();        // 显示帮助信息
       3 while (cam.get(CV_CAP_PROP_POS_AVI_RATIO) < 0.999999){    // 循环遍历每一帧
       4     Mat im, img; cam >> im; 
       5     annotation.image = im.clone();
       6     annotation.draw_instructions();
       7     imshow(annotation.wname, annotation.image);        // 显示当前帧
       8     int c = waitKey(0);        // 等待按键,q 退出,s 选择图像进行标注,其它任意键 下一帧
       9     if (c == 'q')break;
      10     else if (c == 's'){
      11         int idx = annotation.data.imnames.size(); char str[1024];
      12         if (idx < 10)sprintf(str, "00%d.png", idx);
      13         else if (idx < 100)sprintf(str, "0%d.png",idx);
      14         else               sprintf(str, "%d.png", idx);        // 文件名格式 三位整数.png
      15         imwrite(str, im);        // 保存该帧图像
      16         annotation.data.imnames.push_back(str);
      17         cam >> im;                // 显示下一帧
      18         imshow(annotation.wname, im);
      19     }
      20 }
      21 if (annotation.data.imnames.size() == 0)return 0;
      22 annotation.data.points.resize(annotation.data.imnames.size());
      

       

    • 运维效果: 
                                                                     
                                                                   
         图片 19 
           图片 20

  • 标明第贰幅图:第2、步首先将上一步中第壹幅图显示给用户,然后用户会在那幅图中精选要求跟踪的颜面特征地点。

    • 首要代码如下:

       1 // 标注第一幅图像
       2 setMouseCallback(annotation.wname, pp_MouseCallback, 0);
       3 annotation.set_pick_points_instructions();    // 显示帮助信息
       4 annotation.set_current_image(0);        // 选择第一幅图像
       5 annotation.draw_instructions();
       6 annotation.idx = 0;
       7 while (1){            // 在键入 q 之前,鼠标单击标注特征点
       8     annotation.draw_points();
       9     imshow(annotation.wname, annotation.image); 
      10     if (waitKey(0) == 'q')break;
      11 }
      12 if (annotation.data.points[0].size() == 0)return 0;
      13 annotation.replicate_annotations(0);    // 保存特征点位置信息
      
    • 运作效果(为检察代码,只采用五个特征点):

      
        图片 21

 

  • 标明连通性:在这一步中,用户需拔取将两组点连接起来,以创建人脸模型的连通性结构

    • 重点代码如下:

       1 //标注连通性
       2 setMouseCallback(annotation.wname, pc_MouseCallback, 0);
       3 annotation.set_connectivity_instructions();    // 帮助信息
       4 annotation.set_current_image(0);
       5 annotation.draw_instructions();
       6 annotation.idx = 0;
       7 while (1){            // 在键入 q 之前,鼠标单击一组点建立连接
       8     annotation.draw_connections();
       9     imshow(annotation.wname, annotation.image); if (waitKey(0) == 'q')break;
      10 }
      11 save_ft(fname.c_str(), annotation.data);
      

       

    • 运作效果如下:

      
     图片 22

  •  标注对称性:这一步依然使用上一步的图像,用户需选出左右对称的点。

    • 关键代码如下:

       1 //标注对称性
       2 setMouseCallback(annotation.wname, ps_MouseCallback, 0);
       3 annotation.initialise_symmetry(0);
       4 annotation.set_symmetry_instructions();
       5 annotation.set_current_image(0);
       6 annotation.draw_instructions();
       7 annotation.idx = 0; annotation.pidx = -1;
       8 while (1){            // 在键入 q 之前,鼠标单击特征点标注对称性
       9     annotation.draw_symmetry();
      10     imshow(annotation.wname, annotation.image); if (waitKey(0) == 'q')break;
      11 }
      12 save_ft(fname.c_str(), annotation.data);
      

       

    •  运维效果如下:

      
      图片 23

  •  标明剩下的图像:重复第贰, 步至第 4 步,移动特征点使特征点对应特征地点

    • 一言九鼎代码如下:

       1 //标注剩下的图像
       2 if (type != 2){
       3     setMouseCallback(annotation.wname, mv_MouseCallback, 0);
       4     annotation.set_move_points_instructions();        // 帮助信息
       5     annotation.idx = 1; annotation.pidx = -1;
       6     while (1){
       7         annotation.set_current_image(annotation.idx);
       8         annotation.draw_instructions();
       9         annotation.set_clean_image();        // 背景图
      10         annotation.draw_connections();        // 连线
      11         imshow(annotation.wname, annotation.image);
      12         int c = waitKey(0);        // q 退出,p 下一幅图像,o 上一幅图像
      13         if (c == 'q')break;
      14         else if (c == 'p'){ annotation.idx++; annotation.pidx = -1; }
      15         else if (c == 'o'){ annotation.idx--; annotation.pidx = -1; }
      16         if (annotation.idx < 0)annotation.idx = 0;
      17         if (annotation.idx >= int(annotation.data.imnames.size()))
      18             annotation.idx = annotation.data.imnames.size() - 1;
      19     }
      20 }
      21 save_ft(fname.c_str(), annotation.data);
      

       

    • 运行效果如下:

      
      图片 24

 

  该工具将标注数据存储到
ann.yaml 中,如下:

     
                  图片 25

  2. 标号工具

   为了使生成的标号能被本章中的代码应用,可在 annotate.cpp 文件中找到二个为主的标注工具。该工具将多个视屏流作为输入,那么些视频流可以来自文件或相机、使用该工具的进程有如下七个步骤:

  • 抓获图像:第二步是将图像流展将来显示屏上,用户按下
    S 键就可采纳图像举办标注。

    • 根本代码如下:

       1 //选择图像进行标注
       2 annotation.set_capture_instructions();        // 显示帮助信息
       3 while (cam.get(CV_CAP_PROP_POS_AVI_RATIO) < 0.999999){    // 循环遍历每一帧
       4     Mat im, img; cam >> im; 
       5     annotation.image = im.clone();
       6     annotation.draw_instructions();
       7     imshow(annotation.wname, annotation.image);        // 显示当前帧
       8     int c = waitKey(0);        // 等待按键,q 退出,s 选择图像进行标注,其它任意键 下一帧
       9     if (c == 'q')break;
      10     else if (c == 's'){
      11         int idx = annotation.data.imnames.size(); char str[1024];
      12         if (idx < 10)sprintf(str, "00%d.png", idx);
      13         else if (idx < 100)sprintf(str, "0%d.png",idx);
      14         else               sprintf(str, "%d.png", idx);        // 文件名格式 三位整数.png
      15         imwrite(str, im);        // 保存该帧图像
      16         annotation.data.imnames.push_back(str);
      17         cam >> im;                // 显示下一帧
      18         imshow(annotation.wname, im);
      19     }
      20 }
      21 if (annotation.data.imnames.size() == 0)return 0;
      22 annotation.data.points.resize(annotation.data.imnames.size());
      

       

    • 运行效果: 
                                                                     
                                                                   
         图片 26 
           图片 27

  • 标明第贰,幅图:第叁步首先将上一步中首先幅图突显给用户,然后用户会在这幅图中精选要求跟踪的面部特征地点。

    • 根本代码如下:

       1 // 标注第一幅图像
       2 setMouseCallback(annotation.wname, pp_MouseCallback, 0);
       3 annotation.set_pick_points_instructions();    // 显示帮助信息
       4 annotation.set_current_image(0);        // 选择第一幅图像
       5 annotation.draw_instructions();
       6 annotation.idx = 0;
       7 while (1){            // 在键入 q 之前,鼠标单击标注特征点
       8     annotation.draw_points();
       9     imshow(annotation.wname, annotation.image); 
      10     if (waitKey(0) == 'q')break;
      11 }
      12 if (annotation.data.points[0].size() == 0)return 0;
      13 annotation.replicate_annotations(0);    // 保存特征点位置信息
      
    • 运作效果(为检查代码,只接纳多个特征点):

      
        图片 28

 

  • 标明连通性:在这一步中,用户需拔取将两组点连接起来,以建立人脸模型的连通性结构

    • 根本代码如下:

       1 //标注连通性
       2 setMouseCallback(annotation.wname, pc_MouseCallback, 0);
       3 annotation.set_connectivity_instructions();    // 帮助信息
       4 annotation.set_current_image(0);
       5 annotation.draw_instructions();
       6 annotation.idx = 0;
       7 while (1){            // 在键入 q 之前,鼠标单击一组点建立连接
       8     annotation.draw_connections();
       9     imshow(annotation.wname, annotation.image); if (waitKey(0) == 'q')break;
      10 }
      11 save_ft(fname.c_str(), annotation.data);
      

       

    • 运行效果如下:

      
     图片 29

  •  标注对称性:这一步如故拔取上一步的图像,用户需选出左右对称的点。

    • 紧要代码如下:

       1 //标注对称性
       2 setMouseCallback(annotation.wname, ps_MouseCallback, 0);
       3 annotation.initialise_symmetry(0);
       4 annotation.set_symmetry_instructions();
       5 annotation.set_current_image(0);
       6 annotation.draw_instructions();
       7 annotation.idx = 0; annotation.pidx = -1;
       8 while (1){            // 在键入 q 之前,鼠标单击特征点标注对称性
       9     annotation.draw_symmetry();
      10     imshow(annotation.wname, annotation.image); if (waitKey(0) == 'q')break;
      11 }
      12 save_ft(fname.c_str(), annotation.data);
      

       

    •  运行效果如下:

      
      图片 30

  •  标明剩下的图像:重复第贰, 步至第 4 步,移动特征点使特征点对应特征地方

    • 重在代码如下:

       1 //标注剩下的图像
       2 if (type != 2){
       3     setMouseCallback(annotation.wname, mv_MouseCallback, 0);
       4     annotation.set_move_points_instructions();        // 帮助信息
       5     annotation.idx = 1; annotation.pidx = -1;
       6     while (1){
       7         annotation.set_current_image(annotation.idx);
       8         annotation.draw_instructions();
       9         annotation.set_clean_image();        // 背景图
      10         annotation.draw_connections();        // 连线
      11         imshow(annotation.wname, annotation.image);
      12         int c = waitKey(0);        // q 退出,p 下一幅图像,o 上一幅图像
      13         if (c == 'q')break;
      14         else if (c == 'p'){ annotation.idx++; annotation.pidx = -1; }
      15         else if (c == 'o'){ annotation.idx--; annotation.pidx = -1; }
      16         if (annotation.idx < 0)annotation.idx = 0;
      17         if (annotation.idx >= int(annotation.data.imnames.size()))
      18             annotation.idx = annotation.data.imnames.size() - 1;
      19     }
      20 }
      21 save_ft(fname.c_str(), annotation.data);
      

       

    • 运作效果如下:

      
      图片 31

 

  该工具将标注数据存储到
ann.yaml 中,如下:

     
                  图片 32

  用于检测脸部特征的块模型储存在矩阵
P 中。该类有多个至关紧要的函数: calc_response 和 train
。calc_response 函数会对寻找区域
im 的种种成分统计块模型的响应值。train 函数用来拿到块模型,其大小由
psize 参数决定。

   3. 预备标注数据( MUCT 数据集)

  为了让本章的标注工作变得自在一些,可接纳公开的
MUCT 数据集。这几个数据集由
3755 张人脸图像构成,每张人脸有捌拾一个点作为标志。数据集的图像是在不一致光照条件和底部姿势下雕塑的人,他们来自不一致年龄和种族。

  该数据集只包涵了标注点,须要自定义连通性和对称性。标注连通性和对称性之后效果如下图左,标注数据储存在 annotations.yaml 中,如下图右:

   
 图片 33   
  图片 34

  

  visualize_annotations.cpp 达成对数码集可视化操作,关键代码如下:

图片 35图片 36

 1 cout << "n images: " << data.imnames.size() << endl
 2     << "n points: " << data.symmetry.size() << endl
 3     << "n connections: " << data.connections.size() << endl;
 4 // 可视化标注数据
 5 namedWindow("Annotations");
 6 int index = 0; bool flipped = false;
 7 while(1){
 8 Mat image;
 9 if(flipped)image = data.get_image(index,3);
10 else image = data.get_image(index,2);            // 背景图片
11 data.draw_connect(image,index,flipped);            // 连通
12 data.draw_sym(image,index,flipped);                // 对称
13 imshow("Annotations",image);
14 int c = waitKey(0);            // q 退出,p 下一张,o 上一张,f 翻转
15 if(c == 'q')break;
16 else if(c == 'p')index++;
17 else if(c == 'o')index--;
18 else if(c == 'f')flipped = !flipped;
19 if(index < 0)index = 0;
20 else if(index >= int(data.imnames.size()))index = data.imnames.size()-1;
21 }

可视化数据

  运转效果如下:

     
 图片 37 
  图片 38   
 图片 39

 

 

 

   3. 预备标注数据( MUCT 数据集)

  为了让本章的标号工作变得轻松局地,可拔取公开的
MUCT 数据集。那一个数量集由
3755 张人脸图像构成,每张人脸有柒十多个点作为标志。数据集的图像是在不一样光照条件和底部姿势下拍录的人,他们来自不一样年龄和种族。

  该数量集只包涵了标注点,须求自定义连通性和对称性。标注连通性和对称性之后效果如下图左,标注数据储存在 annotations.yaml 中,如下图右:

   
 图片 40   
  图片 41

  

  visualize_annotations.cpp 完结对数据集可视化操作,关键代码如下:

图片 42图片 43

 1 cout << "n images: " << data.imnames.size() << endl
 2     << "n points: " << data.symmetry.size() << endl
 3     << "n connections: " << data.connections.size() << endl;
 4 // 可视化标注数据
 5 namedWindow("Annotations");
 6 int index = 0; bool flipped = false;
 7 while(1){
 8 Mat image;
 9 if(flipped)image = data.get_image(index,3);
10 else image = data.get_image(index,2);            // 背景图片
11 data.draw_connect(image,index,flipped);            // 连通
12 data.draw_sym(image,index,flipped);                // 对称
13 imshow("Annotations",image);
14 int c = waitKey(0);            // q 退出,p 下一张,o 上一张,f 翻转
15 if(c == 'q')break;
16 else if(c == 'p')index++;
17 else if(c == 'o')index--;
18 else if(c == 'f')flipped = !flipped;
19 if(index < 0)index = 0;
20 else if(index >= int(data.imnames.size()))index = data.imnames.size()-1;
21 }

可视化数据

  运转效果如下:

     
 图片 44 
  图片 45   
 图片 46

 

 

 

  本节将介绍该类的意义。上面先来谈谈有关性块(correlation
patch)和它的练习进程,相关性块将被用来学学块模型。接下来介绍
patch_models 类,该类保存着各种面部特征的局地块模型,并达成了全局变换的职能。在文书
train_patch_model.cpp 和文件 visualize_patch_model.cpp 中的程序分别用来练习可视化块模型。在本节最终将简单介绍它们的用法。

 

 

一、 相关性块模型

  学习检测器可用两种重大方式:生成方法(generative)和判断格局(discriminative),那二种方式的思索完全分裂。生成方法会学习三个图象块底层表示,那种代表在各个气象下都能最恰当地生成对象外观。而判断格局依照已有个别对象的新闻来对新目的做出最好的论断,这样已有样本来源于运转的种类。生成方法的优势在于能对现实对象的习性实行操作,可直观地翻看新的靶子实例的情况。闻明的特征脸(Eigenface)方法就是一种流行的生成方法。判别方法的优势在于所建模型直接针对当下题材,通过已有目的来对新指标做出判别。全数判别方法中最有名的或然是支撑向量机。将三个图象块作为面部特征来建模时,判别方法更好有的。

  

  1. 就学基于判别法的块模型

  给定3个标明数据集,特征检测器可从那几个多少学习收获。判别块模型的学习目的是为着社团那样的图象块:当图象块与分包面部特征的图像区域陆续相关时,对特色区域有三个强的响应,而其他一些响应很弱。该模型用数学化方式可代表为:

    图片 47  

  那里,P 表示块模型,Ii 表示第
i 个教练图像,Ii(a:b,c:d) 表示左上角地点和右下角地点分别是
(a,c) 和 (b,d),圆点表示内积操作,翼虎代表可以的响应矩阵那么些指标函数的解就是二个块模型,此模型会拿到3个响应矩阵,该矩阵寻常在细微二乘的心路标准下最靠近理想的响应矩阵。对美好的响应矩阵
本田CR-V 的一个广大接纳(假定面部特征集中在操练图象块的着力)是,除中央外其余地方都设为令。在实践中,由于图像被手工标记,总会有标注误差。为了消除这一个标题,常常用衰减函数来形容
帕杰罗,该函数从着力开头,随着距离增添,函数值会很快变小。二维高斯分布可很好地担任那种函数,那也也等于假定标注误差遵守高斯分布,这么些历程可用下图来描述,该图为人脸左眼角外部。

    图片 48

  上面给出的目标函数平时称为线性最小二乘,但该难点的自由度,相当于该措施的变量数与块中像素一样多,由此求解最优块模型的测算代价和所需内存会令人望而生畏。

  化解该难题的有效性代表情势是将其看成线性方程,用随意梯度下落法来求解。该算法将团块模型的解集看成一幅地势图,通过持续迭代得到地势图梯度方向的类似估摸,并每一遍向梯度的反方向进步,直到走到对象函数的极小值(达到阈值或迭代次数上限)。此外,随机梯度法每一次随机挑选样本,只必要很少的样本就能达标最优解,十分适合实时性须求较高的系统。目的函数的梯度公式为:

    图片 49

  切实已毕步骤:

    1. 转变坚守高斯分布的可观反馈图像
F

 1 // 生成服从高斯分布的理想反馈图像 F
 2 Size wsize = images[0].size();
 3 if((wsize.width < psize.width) || (wsize.height < psize.height)){
 4 cerr << "Invalid image size < patch size!" << endl; throw std::exception();
 5 }
 6 // 设置反馈图像大小
 7 int dx = wsize.width-psize.width,dy = wsize.height-psize.height;
 8 Mat F(dy,dx,CV_32F);
 9 for(int y = 0; y < dy; y++){   float vy = (dy-1)/2 - y;
10 for(int x = 0; x < dx; x++){ float vx = (dx-1)/2 - x;
11     // 生成函数
12     F.fl(y,x) = exp(-0.5*(vx*vx+vy*vy)/var);
13 }
14 }
15 // 归一化处理
16 normalize(F,F,0,1,NORM_MINMAX);

    2. 专擅梯度法求最优的团块模型

 1 // 利用随机梯度下降法求最优团块模型
 2 RNG rn(getTickCount()); 
 3 // 给定初始更新速率
 4 double mu=mu_init,step=pow(1e-8/mu_init,1.0/nsamples);
 5 for(int sample = 0; sample < nsamples; sample++){ 
 6 int i = rn.uniform(0,N);                            // i 为随机选中的样本图像标记
 7 I = this->convert_image(images[i]); dP = 0.0;        // 将图像转换为灰度图
 8 for(int y = 0; y < dy; y++){
 9     for(int x = 0; x < dx; x++){
10 Mat Wi = I(Rect(x,y,psize.width,psize.height)).clone();
11 Wi -= Wi.dot(O); normalize(Wi,Wi);
12 // 计算目标函数的偏导数 D 
13 dP += (F.fl(y,x) - P.dot(Wi))*Wi;
14     }
15 }  
16 // 更新团块模型 P
17 P += mu*(dP - lambda*P); mu *= step;

 

 

         

  学习进程全体代码
train 函数如下:

图片 50图片 51

 1 void 
 2 patch_model::
 3 train(const vector<Mat> &images,    // 包含多个样本图像的矩阵向量
 4       const Size psize,                // 团块模型窗口的大小
 5       const float var,                // 手工标注错误的方差(生成理想图像时使用)
 6       const float lambda,            // 调整的参数(调整上一次得到的团块模型的大小)
 7       const float mu_init,            // 初始步长(构建梯度下降法求团块模型时的更新速率)
 8       const int nsamples,            // 随机选取的样本数量(梯度下降算法迭代的次数)
 9       const bool visi)                // 训练过程是否可观察标志
10 {
11   int N = images.size(),n = psize.width*psize.height;
12 
13   // 生成服从高斯分布的理想反馈图像 F
14   Size wsize = images[0].size();
15   if((wsize.width < psize.width) || (wsize.height < psize.height)){
16     cerr << "Invalid image size < patch size!" << endl; throw std::exception();
17   }
18   // 设置反馈图像大小
19   int dx = wsize.width-psize.width,dy = wsize.height-psize.height;
20   Mat F(dy,dx,CV_32F);
21   for(int y = 0; y < dy; y++){   float vy = (dy-1)/2 - y;
22     for(int x = 0; x < dx; x++){ float vx = (dx-1)/2 - x;
23       // 生成函数
24       F.fl(y,x) = exp(-0.5*(vx*vx+vy*vy)/var);
25     }
26   }
27   // 归一化处理
28   normalize(F,F,0,1,NORM_MINMAX);
29 
30   //allocate memory
31   Mat I(wsize.height,wsize.width,CV_32F);                // 被选中的样本灰度图像
32   Mat dP(psize.height,psize.width,CV_32F);                // 目标函数的偏导数,大小同团块模型
33   Mat O = Mat::ones(psize.height,psize.width,CV_32F)/n;    // 生成团块模型的归一化模板
34   P = Mat::zeros(psize.height,psize.width,CV_32F);        // 团块模型
35 
36   // 利用随机梯度下降法求最优团块模型
37   RNG rn(getTickCount()); 
38   // 给定初始更新速率
39   double mu=mu_init,step=pow(1e-8/mu_init,1.0/nsamples);
40   for(int sample = 0; sample < nsamples; sample++){ 
41     int i = rn.uniform(0,N);                            // i 为随机选中的样本图像标记
42     I = this->convert_image(images[i]); dP = 0.0;        // 将图像转换为灰度图
43     for(int y = 0; y < dy; y++){
44       for(int x = 0; x < dx; x++){
45     Mat Wi = I(Rect(x,y,psize.width,psize.height)).clone();
46     Wi -= Wi.dot(O); normalize(Wi,Wi);
47     // 计算目标函数的偏导数 D 
48     dP += (F.fl(y,x) - P.dot(Wi))*Wi;
49       }
50     }  
51     // 更新团块模型 P
52     P += mu*(dP - lambda*P); mu *= step;
53     if(visi){
54       Mat R; 
55       matchTemplate(I,P,R,CV_TM_CCOEFF_NORMED);    // 在样本图像上寻找与团块模型匹配的区域
56       Mat PP; normalize(P,PP,0,1,NORM_MINMAX);
57       normalize(dP,dP,0,1,NORM_MINMAX);
58       normalize(R,R,0,1,NORM_MINMAX);
59       imshow("P",PP);            // 归一化的团块模型
60       imshow("dP",dP);            // 归一化的目标函数偏导数
61       imshow("R",R);            // 与团块模型匹配的区域
62       if(waitKey(10) == 27)break;
63     }
64   }return;
65 }

train

 

 

 

 

 

二、解释全局几何变换

  到近年来截止,假定练习图像以面部特征举办中央化并以全局尺度和旋转举行归一化。实际上,在跟踪进程中,人脸图像随时会油可是生规则或旋转变换。因而,系统必须考虑陶冶和测试条件之间的歧异。一种方法是通过人工地在必然限制内开展规范变换和旋转来已毕大家期望的档次。可是出于共同分布团块模型的结合格局相比不难,对于手工安装的范本数量不可以暴发立竿见影的响应图像response
map;另一种艺术,由于在多少个视频中总是帧的扭转相对较小,可选拔前一帧对人脸所估计全局变换成对当前图像的规格变换和旋转举办归一化处理。完结这一历程只需在攻读相关块模型时采用2个参考帧即可。

  patch_models 类对各样面部特征存储了相关块模型,以及教练时收获的参考帧。上面那段代码是
patch_models 类的要紧成效:

 1 class patch_models{                        //collection of patch experts
 2 public:
 3   Mat reference;                           // 指定尺度变换和旋转的点集
 4   vector<patch_model> patches;             //patch models
 5   ...
 6   void 
 7   train(ft_data &data,                     // 存放手工标注的数据
 8     const vector<Point2f> &ref,        // 指定大小和旋转角度的人脸特征的参考点集
 9     const Size psize,                  // 团块模型的大小
10     const Size ssize,                  // 搜索窗口的大小,即在样本图像上可以搜索团块模型的范围
11     const bool mirror = true,          // use mirrored images?
12     const float var = 1.0,             //variance of annotation error
13     const float lambda = 1e-6,         //regularization weight
14     const float mu_init = 1e-3,        //initial stoch-grad step size
15     const int nsamples = 1000,         //number of stoch-grad samples
16     const bool visi = true);           //visualize intermediate results?
17 
18   vector<Point2f>                          //locations of peaks/feature
19   calc_peaks(const Mat &im,                //image to detect features in
20          const vector<Point2f> &points,//initial estimate of shape
21          const Size ssize=Size(21,21));//search window size
22    ...
23 };

 

 

 

 

  那么什么样通过合理的几何变换让样本图像练习出最好的团块模型?大家得以参考上一篇博文中的
Procrustes 算法计算每幅样本图像中特征点集的几何结构到参考人脸模型的转移矩阵,再接纳那些矩阵对样本图像进行仿射变化(缩放、旋转、偏移)。具体完结如下:

 1 void 
 2 patch_models::
 3 train(ft_data &data,
 4       const vector<Point2f> &ref,
 5       const Size psize,
 6       const Size ssize,
 7       const bool mirror,
 8       const float var,
 9       const float lambda,
10       const float mu_init,
11       const int nsamples,
12       const bool visi)
13 {
14   //set reference shape
15   int n = ref.size(); reference = Mat(ref).reshape(1,2*n);
16   Size wsize = psize + ssize;    // wsize 为归一化的样本图像大小
17 
18   //train each patch model in turn
19   // n 个特征点将对应 n 个团块
20   patches.resize(n);
21   for(int i = 0; i < n; i++){
22     if(visi)cout << "training patch " << i << "..." << endl;
23     vector<Mat> images(0);
24     for(int j = 0; j < data.n_images(); j++){    // 遍历所有样本图像
25       Mat im = data.get_image(j,0); 
26       vector<Point2f> p = data.get_points(j,false);    // 获取手工标注的样本点
27       Mat pt = Mat(p).reshape(1,2*n);
28       Mat S = this->calc_simil(pt),A(2,3,CV_32F);    // 计算样本点到参考模型的变化矩阵
29       A.fl(0,0) = S.fl(0,0); A.fl(0,1) = S.fl(0,1);    // 构造仿射变换矩阵
30       A.fl(1,0) = S.fl(1,0); A.fl(1,1) = S.fl(1,1);
31       A.fl(0,2) = pt.fl(2*i  ) - 
32     (A.fl(0,0) * (wsize.width-1)/2 + A.fl(0,1)*(wsize.height-1)/2);
33       A.fl(1,2) = pt.fl(2*i+1) - 
34     (A.fl(1,0) * (wsize.width-1)/2 + A.fl(1,1)*(wsize.height-1)/2);
35       Mat I; warpAffine(im,I,A,wsize,INTER_LINEAR+WARP_INVERSE_MAP); // 对样本进行仿射变换
36       images.push_back(I);
37       if(mirror){
38     im = data.get_image(j,1); 
39     p = data.get_points(j,true);
40     pt = Mat(p).reshape(1,2*n);
41     S = this->calc_simil(pt);
42     A.fl(0,0) = S.fl(0,0); A.fl(0,1) = S.fl(0,1);
43     A.fl(1,0) = S.fl(1,0); A.fl(1,1) = S.fl(1,1);
44     A.fl(0,2) = pt.fl(2*i  ) - 
45       (A.fl(0,0) * (wsize.width-1)/2 + A.fl(0,1)*(wsize.height-1)/2);
46     A.fl(1,2) = pt.fl(2*i+1) - 
47       (A.fl(1,0) * (wsize.width-1)/2 + A.fl(1,1)*(wsize.height-1)/2);
48     warpAffine(im,I,A,wsize,INTER_LINEAR+WARP_INVERSE_MAP);
49     images.push_back(I);
50       }
51     }
52     patches[i].train(images,psize,var,lambda,mu_init,nsamples,visi);    // 从样本图像中训练团块模型
53   }
54 }

 

 

 

 

  其中
calc_simil 函数用来计量仿射变换矩阵,完成进度如下:

 1 Mat
 2 patch_models::
 3 calc_simil(const Mat &pts)
 4 {
 5   //compute translation
 6   int n = pts.rows/2; 
 7   // 计算标注点的重心
 8   float mx = 0,my = 0;
 9   for(int i = 0; i < n; i++){
10     mx += pts.fl(2*i); my += pts.fl(2*i+1);
11   }  
12   Mat p(2*n,1,CV_32F); mx /= n; my /= n;
13   for(int i = 0; i < n; i++){
14     p.fl(2*i) = pts.fl(2*i) - mx; p.fl(2*i+1) = pts.fl(2*i+1) - my;
15   }
16   //compute rotation and scale
17   float a=0,b=0,c=0;
18   for(int i = 0; i < n; i++){
19     a += reference.fl(2*i) * reference.fl(2*i  ) + 
20       reference.fl(2*i+1) * reference.fl(2*i+1);
21     b += reference.fl(2*i) * p.fl(2*i  ) + reference.fl(2*i+1) * p.fl(2*i+1);
22     c += reference.fl(2*i) * p.fl(2*i+1) - reference.fl(2*i+1) * p.fl(2*i  );
23   }
24   b /= a; c /= a;
25   float scale = sqrt(b*b+c*c),theta = atan2(c,b); 
26   float sc = scale*cos(theta),ss = scale*sin(theta);
27   // 前两列为缩放旋转,最后一列为平移
28   return (Mat_<float>(2,3) << sc,-ss,mx,ss,sc,my);    
29 }

 

 

 

 

 

三、锻炼与可视化

  在
train_patch_model.cpp 文件中有通过标注数据来练习块模型的演示程序。命令行
argv[1] 保存着标注数据的门径,通过它可加载数据到内存,并删除不齐全样本,然后先导磨练:

1 //load data
2 ft_data data = load_ft<ft_data>(argv[1]);
3 data.rm_incomplete_samples();
4 if(data.imnames.size() == 0){
5 cerr << "Data file does not contain any annotations."<< endl; return 0;
6 }

  上一篇博客通过对数码集练习已获取多个模样模型,且将该模型储存在
argv[2] 参数所针对的门道中,可通过
argv[2] 参数来加载此模型,然后经过统计选取的 reference 形状。具体完结如下:

1 //load shape model
2 shape_model smodel = load_ft<shape_model>(argv[2]);

 

  下边是对已缩放且被核心化后的平均形状举办总结:

1 //generate reference shape 
2 smodel.p = Scalar::all(0.0); 
3 smodel.p.fl(0) = calc_scale(smodel.V.col(0),width); 
4 vector<Point2f> r = smodel.calc_shape();

  在得到 reference 形状
r 后,可因而上边那么些函数在训练块模型集合并保存:

1 //train patch models
2 patch_models pmodel;
3 pmodel.train(data,r,Size(psize,psize),Size(ssize,ssize),mirror);
4 save_ft<patch_models>(argv[3],pmodel);

 

 

 

  运转程序能够博得练习好的块模型并保存在 patch_model.yaml 中,如下图:

    图片 52

  

  一旦练习成功,就可用文件 visualize_patch_model.cpp 中的程序对获取的块模型进行可视化操作。这些程序可得到:全部块模型的复合图像
P、参考形状中各特征所在地点的着力、reference、展现当前运动块周围有界的矩形区域。

  主要代码如下:

 1 // 加载团块模型
 2 patch_models pmodel = load_ft<patch_models>(argv[1]);
 3   
 4 //compute scale factor
 5 float scale = calc_scale(pmodel.reference,width);
 6 int height = calc_height(pmodel.reference,scale);
 7 
 8 //compute image width
 9 int max_width = 0,max_height = 0;
10 for(int i = 0; i < pmodel.n_patches(); i++){
11 Size size = pmodel.patches[i].patch_size();
12 max_width = max(max_width,int(scale*size.width));
13 max_height = max(max_width,int(scale*size.height));
14 }
15 //create reference image
16 // 将所有块模型根据标注点位置贴到同一图像上
17 Size image_size(width+4*max_width,height+4*max_height);
18 Mat image(image_size.height,image_size.width,CV_8UC3); 
19 image = Scalar::all(255);
20 vector<Point> points(pmodel.n_patches());
21 vector<Mat> P(pmodel.n_patches());
22 for(int i = 0; i < pmodel.n_patches(); i++){
23 Mat I1; normalize(pmodel.patches[i].P,I1,0,255,NORM_MINMAX);
24 Mat I2; resize(I1,I2,Size(scale*I1.cols,scale*I1.rows));    
25 Mat I3; I2.convertTo(I3,CV_8U); cvtColor(I3,P[i],CV_GRAY2RGB);
26 points[i] = Point(scale*pmodel.reference.fl(2*i  ) + 
27             image_size.width /2 - P[i].cols/2,
28             scale*pmodel.reference.fl(2*i+1) + 
29             image_size.height/2 - P[i].rows/2);
30 Mat I = image(Rect(points[i].x,points[i].y,P[i].cols,P[i].rows));
31 P[i].copyTo(I);
32 }
33 //animate
34 namedWindow("patch model");
35 int i = 0;
36 while(1){
37 Mat img = image.clone();
38 Mat I = img(Rect(points[i].x,points[i].y,P[i].cols,P[i].rows));
39 P[i].copyTo(I);
40 rectangle(img,points[i],Point(points[i].x+P[i].cols,points[i].y+P[i].rows),
41         CV_RGB(255,0,0),2,CV_AA);
42 char str[256]; sprintf(str,"patch %d",i); draw_string(img,str);
43 imshow("patch model",img);
44 int c = waitKey(0);        // q 退出,p 下一个块模型,o 上一个块模型
45 if(c == 'q')break;
46 else if(c == 'p')i++;
47 else if(c == 'o')i--;
48 if(i < 0)i = 0; else if(i >= pmodel.n_patches())i = pmodel.n_patches()-1;
49 }
50 destroyWindow("patch model"); 

 

 

 

  运维效果如下:

    图片 53 
   图片 54   

 

四、总结

  回顾一下本节团块特征提取算法都经历了何等操作:

    1.  获取手工标注的样本点、样本图像名称、形变模型
      (v,e,c)
    2.  指定大小与旋转角度,通过形状模型的一块变化矩阵
      v,生成人脸特征点的参考模型 ref
    3.  总括每幅样本图像的标注点到参考特征点的转动角度
      S
    4.  利用旋转结构仿射变化矩阵,对样本图像进行仿射变换
      A
    5. 应用随意梯度下降法对新生成的样本图像求解各种特征点对应的团块模型
      patch_model

 

相关文章