OpenCV 脸部跟踪(2)

      前面一篇文章中提到,我们在一副脸部图像上选取76个特征点,以及这些特征点的连通性信息来描述脸部形状特征,本文中我们会把这些特征点映射到一个标准形状模型。

      通常,脸部形状特征点能够参数化分解为两个变量,一个是全局的刚体变化,一个是局部的变形。全局的刚体变化主要是指脸部能够在图像中移动,旋转,缩放,局部的变形则是指脸部的表情变化,不同人脸的特征等等。

 

下面我们通过train函数,一步步了解下如何把标记点特征数据转化为标准形状模型:

本文参考了下面两篇文章:

http://blog.csdn.net/raby_gyl/article/details/13148193

http://blog.csdn.net/raby_gyl/article/details/13024867

 

train函数的输入为n个样本图像的采样特征点,该点集会被首先转化为行152,列为样本数量的矩阵表示,另外还有连通性点集索引,以及方差的置信区间以及保留模型的最大数量,后两个参数主要在模型空间降维时候使用。

void train(const vector<vector<Point2f> > &p, //N-example shapes
const vector<Vec2i> &con = vector<Vec2i>(), //point-connectivity
const float frac = 0.95, //fraction of variation to retain
const int kmax = 10) //maximum number of modes to retain
{
Mat X = this->pts2mat(points);

二维向量组成的特征点集points经过函数pts2mat转化为矩阵X,X矩阵为152行,N列,N表示样本图像的数量,每一列为一副图像的76个特征点的x,y坐标。

//N = 2914*2 n = 76,共2914副图像特征点,但因为我们使用镜像,所以采样图像特征数据翻了一倍。

int N = X.cols,n = X.rows/2;

 

Mat Y = this->procrustes(X);
上面的函数中,我们通过Procrustes analysis来处理特征点,Procrustes analysis算法可以参考:http://en.wikipedia.org/wiki/Procrustes_analysis

在数学上,Procruster analysis就是寻找一个标准形状,然后把所有其它特征点数据都和标准形状对齐,对齐的时候采用最小平方距离,用迭代的方法不断逼近。

下面通过代码来了解如何实现Procrustes analysis, http://www.cnblogs.com/mikewolf2002/p/3659964.html

经过Procrustes analysis变化后得到矩阵Y,矩阵Y仍是152*5828的矩阵,但它的每列表示的特征向量都接近于标准形状。下面我们要做的就是根据Y计算刚体变化矩阵R。R是一个152*4的矩阵。

//计算得到刚体变化矩阵R
Mat R = this->calc_rigid_basis(Y);

计算R的原理如下:

通过Procrustes analysis对齐的特征向量,我们要用一个统一的矩阵把平移和旋转统一起来表示(成为线性表示),然后把该矩阵追加到局部变形空间,注意对该矩阵表示,我们最后进行了史密斯正交处理。这儿x1,y1,....xn,yn是Y矩阵的平均列向量,也就是标准形状(权威形状)。

该函数的代码如下:

Mat  shape_model::calc_rigid_basis(const Mat &X)
    {
//计算均值形状
    int N = X.cols,n = X.rows/2;
    Mat mean = X*Mat::ones(N,1,CV_32F)/N;

注意这儿的x1,y1,..., xn, yn是Procrustes结果的平均值
    Mat R(2*n,4,CV_32F);
    for(int i = 0; i < n; i++)
        {
        R.fl(2*i,0) =  mean.fl(2*i  );
        R.fl(2*i+1,0) =  mean.fl(2*i+1);
        R.fl(2*i,1) = -mean.fl(2*i+1);
        R.fl(2*i+1,1) =  mean.fl(2*i  );
        R.fl(2*i,2) =  1.0;  
        R.fl(2*i+1,2) =  0.0;
        R.fl(2*i,3) =  0.0; 
        R.fl(2*i+1,3) =  1.0;
        }
//Gram-Schmidt正交化处理
    for(int i = 0; i < 4; i++)
        {
        Mat r = R.col(i);
        for(int j = 0; j < i; j++)
            {
            Mat b = R.col(j);
            r -= b*(b.t()*r);
            }
        normalize(r,r);
        }
    return R;
    }

接着看train函数剩余的代码,前面计算了刚体变化矩阵R,下面计算非刚体变化。

//计算非刚体变化

Y是152*5828的矩阵,它是procrustes分析的结果,R是刚体变化矩阵152*4,它的转置就是4*152 。

我们先了解一个矩阵相乘的概念,假设V是一个空间,那么:

s=V.t()*p; //表示矢量p在V空间下的坐标投影。

p=V*s,表示由坐标(权重)和与之对应的V的列向量,叠加,组合而成的一个形状矢量。

特别的,如果V.t()==V.inv(),即矩阵转置等于矩阵逆,则V为正交矩阵,也即V.t()*V.inv()=E,此时上式就是完全的可逆变换了。

Mat P = R.t()*Y; //就是把Y中的所有特征向量投影到R空间(刚性空间),得到坐标投影, 4*5828
则,R*P为152*5828
Mat dY = Y - R*P; //dy变量的每一列表示减去均值的Procrustes对齐形状,投影刚体运动

这儿Y-R*P后的dY表示Y减去刚性变化,则dY表示非刚性变化。

非刚性空间dY是152*5828的高维矩阵,我们需要对其进行降维操作,这儿采用矩阵奇异分解法来进行降维操作:

    奇异值分解SVD有效的应用到形状数据的协方差矩阵(即,dY.t()*dY),OpenCV的SVD类的w成员存储着数据变化性的主要方向的变量,从最大到最小排序。一个选择子空间维数的普通方法是选择保存数据总能量分数frac的方向最小集(即占总能量的比例为frac),这是通过svd.w记录表示的,因为这些记录是从最大的到最小的排序的,它充分地用来评估子空间,通过用变化性方向的最大值k来评估能量。他们自己的方向存储在SVD类的u成员内。svd.w和svd.u成分一般分别被成为特征值和特征矢量。 http://www.cnblogs.com/mikewolf2002/category/351010.html

SVD svd(dY*dY.t());
int m = min(min(kmax,N-1),n-1);
float vsum = 0;
for(int i = 0; i < m; i++)
   vsum += svd.w.fl(i);
float v = 0;
int k = 0;

达到了95%的主成分量,退出,frac=0.95
for(k = 0; k < m; k++)
{
   v += svd.w.fl(k);
   if(v/vsum >= frac){k++; break;}
}
if(k > m) k = m;

取前k个特征向量,这儿k=1,表示第一个特征向量已经占到了95%的主成分量,m=14,为最大保留14个特征向量(如果使用了—mirror参数k=1,如果没有,则为14)。
Mat D = svd.u(Rect(0,0,k,2*n));

D矩阵为152行,1列。

把全局刚体运动和局部变形运动结合起来,组成一个统一矩阵V。

V.create(2*n,4+k,CV_32F);  //组合子空间
Mat Vr = V(Rect(0,0,4,2*n)); //刚体子空间
R.copyTo(Vr); 
Mat Vd = V(Rect(4,0,k,2*n)); //非刚体子空间
D.copyTo(Vd);

最后我们要注意的一点是如何约束子空间坐标,以使得子空间内的面部形状都是有效的。在下面的图中,我们可以看到,对于子空间内的图像,如果在某个方向改变坐标值,当坐标值小的时候,它仍是一个脸的形状,但是变化值大时候,就不知道是什么玩意了。防止出现这种情况的最简单方法,就是把变化的值clamp在一个范围内,通常是现在± 3 的范围,这样可以cover到99.7%的脸部变化。clamping的值通过下面的代码计算:

//compute variance (normalized wrt scale)

Mat Q = V.t()*X; //把数据投影到子空间,Q为5*5828矩阵
for(int i = 0; i < N; i++) //normalize coordinates w.r.t scale
{ //用第一个坐标缩放,防止太大的缩放值影响脸部识别
float v = Q.fl(0,i);
Mat q = Q.col(i);
q /= v;
}

e为参数方差,注意方差是在子空间标准化后的坐标上计算的,标准化是关于第一维的坐标(即尺度)。这样可以阻止相对大尺度的样本数据主导估计。也注意到一个负值被分配到刚性子空间坐标的方差(即V的前四列)。e.create(4+k,1,CV_32F);
pow(Q,2,Q);
for(int i = 0; i < 4+k; i++)
{
if(i < 4)
e.fl(i) = -1; //no clamping for rigid coefficients
else
e.fl(i) = Q.row(i).dot(Mat::ones(1,N,CV_32F))/(N-1);
}

连通性信息
//store connectivity
if(con.size() > 0)
{ //default connectivity
int m = con.size();
C.create(m,2,CV_32F);
for(int i = 0; i < m; i++)
{
C.at<int>(i,0) = con[i][0];
C.at<int>(i,1) = con[i][1];
}
}
else
{ //user-specified connectivity
C.create(n,2,CV_32S);
for(int i = 0; i < n-1; i++)
{
C.at<int>(i,0) = i; C.at<int>(i,1) = i+1;
}
C.at<int>(n-1,0) = n-1; C.at<int>(n-1,1) = 0;
}
}

形状模型类主要成员如下:

class shape_model
    {                         //2d linear shape model
    public:
        Mat p;                                  //parameter vector (kx1) CV_32F,参数向量
        Mat V;                                   //shape basis (2nxk) CV_32F, line subspace,线性子空间
        Mat e;                                   //parameter variance (kx1) CV_32F 参数方差
        Mat C;                                   //connectivity (cx2) CV_32S 连通性

//把一个点集投影到一个可信的脸部形状空间
void     calc_params(const vector<Point2f> &pts, //points to compute parameters from
    const Mat weight = Mat(),    //weight of each point (nx1) CV_32F 点集的权重
    const float c_factor = 3.0); //clamping factor

//该函数用人脸模型V和e,把向量p转化为点集
vector<Point2f>   calc_shape();                   //shape described by parameters @p

工程文件:FirstOpenCV40,

程序的运行参数为:annotations.yaml shapemodle.yaml

程序执行后,可以看到我们只保留了14个模型。

我们也可以使用下面的运行参数:annotations.yaml shapemodle.yaml –mirror

这时候,每副图像的特征点,会生成一个y轴对称的镜像特征点集,这时训练的采样数目翻倍,为5828。此时竟然值保留了一个模型。

在工程文件FirstOpenCV41中,我们可视化了生成的模型,会连续显示14个模型的不同姿态:

 

时间: 2024-09-29 09:55:57

OpenCV 脸部跟踪(2)的相关文章

OpenCV 脸部跟踪(1)

    本文中的知识来自于Mastering  opencv with practical computer vision project一书.     本文实施的脸部跟踪算法都是基于数据驱动的,主要包括两个部分,训练和测试.训练就是通过脸部标记点的采样数据,训练得到一个标准的脸部模型,而测试部分就是把检测到的脸部和标准脸部模型比较,求得眼睛,鼻子等脸部特征.具体来讲,脸部跟踪分为三个部分:shape model形状模型,就是训练数据表示为什么样的形状模型:feature detector特征检

OpenCV 脸部跟踪(3)

   前面一篇文章我们生成了脸部特征的线性形状模型,本章来学习一下显示线性形状的代码. 线性模型类的结构如下: class shape_model     {                         //2d linear shape model     public:         Mat p;                                   //parameter vector (kx1) CV_32F,参数向量         Mat V;          

对象跟踪小白?本文带你玩转OpenCV(C ++ / Python)

作者介绍:Satya Mallick,擅长领域为计算机视觉,机器学习,人工智能. Linkdin:https://www.linkedin.com/in/satyamallick/zh-cn 在本教程中,我们将了解OpenCV 3.0中引入的OpenCV跟踪API. 我们将学习如何以及何时使用OpenCV 3.2中提供的6种不同的跟踪器- BOOSTING,MIL,KCF,TLD,MEDIANFLOW和GOTURN. 我们还将学习现代跟踪算法背后的基本理论. 什么是对象跟踪? 简单地说,在视频的

OpenCv 人脸检测的学习

最近公司要组织开发分享,但是自己还是新手真的不知道分享啥了,然后看了看前段时间研究过OpenCv,那么就分享他把. openCv就不介绍了,说下人脸检测,其实是通过openCv里边已经训练好的xml文件来进行的,我只是在学习. 我测试中我写了俩个Demo,其中一个是通过Carame来通过摄像头来进行人脸检测看看效果图: 可以看出检测出来的面部有线框. 第一个Dmeo是通过Jni编程来实现的人脸检测, (1)这是本地方法 package com.example.opencv.checkface2;

使用JavaScript 实现的人脸检测_javascript技巧

我一直对视频和图片中的人脸标记.检测和人脸识别技术很感兴趣.尽管我知道获取逻辑和算法去开发人脸识别软件或者插件已经超出了我的想象.当我知道Javascript库可以识别微笑,眼睛和脸部结构时,我得到启发去写一个教程.有许多的库,这些库要不就是纯粹的基于Javascript的,要不就是基于java语言的. 今天,我们开始学习tracking.js,它是一个由Eduardo Lundgren开发的轻量级的javascript库,它可以让你做实时的人脸检测,色彩追踪和标记好友的脸.在这个教程中,我们将

离散随机线性系统的卡尔曼滤波器基本原理及实现

     今天介绍Kalman滤波器理论知识,并给出一个演示的例子.由于Kalman滤波在目标跟踪时,需要不断获取观测向量,所以没法单独使用.如果时间充裕,下一篇博文将会做基于MeanShift + Kalman的目标跟踪.这次的主要结构: 1.       卡尔曼滤波器基本原理 2.       卡尔曼滤波器算法 3.       演示例子[来自课本:C语言常用算法程序集(第二版)]+网上广为流传的自由落体小球跟踪matlab   一.离散随机线性系统的卡尔曼滤波器基本原理     卡尔曼滤波

罗技摄像头在Windws Server 2008不能使用的解决方法

&http://www.aliyun.com/zixun/aggregation/37954.html">nbsp;   今日装好了server 2008 x86 企业版,虽然还未激活,但已经开始一股脑装各类驱动和软件了,最后在罗技快看 zoom摄像头的时候发现新版的罗技驱动 11.x for vista 等根本不识别硬件,老是未知设备. 电询罗技,得回复该产品不支持vista ,2008.异常郁闷,这个摄像头好好的,而且自带麦克风,脸部跟踪,不能用就太可惜了. 解决办法如下: 1

没听说过MSQRD?这可是Facebook刚收购用来对抗Snapchat的利器

三月初,Facebook宣布收购了一款鲜为人知的app---Masquerade (MSQRD). 马克·扎克伯格近日在Facebook上发布了一个自拍视频,视频中他使用了一个一个照片视频滤镜新产品,用户能用它给自拍加上面具贴图.在视频中,扎克伯格变成了炫酷的钢铁侠.看了扎克伯格的视频之后,Masquerade这家小公司沉浸在了狂喜之中. Masquerade投资人Nikolay Davidov表示:"我们没有料到我们的产品会如此受欢迎.扎克伯格使用它的方式太酷了,大家都很激动呢."

《Master Opencv...读书笔记》非刚性人脸跟踪 I

      最近又开始学习<MasteringOpenCV系列>之前没看的部分,依旧是英文版.这次主要研究"非刚性人脸跟踪"(non-rigid face tracking),业余时间较少分几次写完吧.      首先谈谈什么是非刚性人脸跟踪.它是对每帧视频图像中人脸特征稠密数据集合的估计.非刚性人脸跟踪侧重于不同脸部表情或不同人物的脸部特征相对距离的变化.它和一般的人脸检测与跟踪算法不同,它不仅仅是找到每一帧中人脸的位置,它还要找到人脸五官的组态关系.[可以作用表情识别]