首頁 資料庫 mysql教程 OpenCV 脸部跟踪(2)

OpenCV 脸部跟踪(2)

Jun 07, 2016 pm 03:43 PM
http opencv 追蹤

【原文:http://www.cnblogs.com/mikewolf2002/p/3474188.html】 前面一篇文章中提到,我们在一副脸部图像上选取76个特征点来描述脸部形状特征,本文中我们会把这些特征点映射到一个标准形状模型。 通常,脸部形状特征点能够参数化分解为两个变量,一个是全

【原文:http://www.cnblogs.com/mikewolf2002/p/3474188.html】

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

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

 

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

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 &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   calc_shape();                   //shape described by parameters @p
 

...

void    train(const vector > &p, //N-example shapes 
    const vector &con = vector(), //point-connectivity 
    const float frac = 0.95,           //fraction of variation to retain 
    const int kmax = 10);              //maximum number of modes to retain 
...

}

本文中,我们通过Procrustes analysis来处理特征点,移去全局刚性变化Procrustes analysis算法可以参考:http://en.wikipedia.org/wiki/Procrustes_analysis

      在数学上,Procruster analysis就是寻找一个标准形状,然后把所有其它特征点数据都和标准形状对齐,对齐的时候采用最小平方距离,用迭代的方法不断逼近。下面通过代码来了解如何实现Procrustes analysis。

//Procrustes分析的基本思想是最小化所有形状到平均形状的距离和 
Mat shape_model::procrustes(const Mat &X, 
    const int itol,  //最大迭代次数 
    const float ftol //精度

     ) 
    {

X矩阵就是多副样本图像76个特征点组成的矩阵,共152行,列数为图像的个数,每列表示一个样本图像特征点的x,y坐标。 
    int N = X.cols,n = X.rows/2;

    //remove centre of mass 
    //所有的形状向量(特征)对齐到原点,即每个向量的分量减去其平均值,每列是一个形状向量。
 
    Mat P = X.clone(); 
    for(int i = 0; i         { 
        Mat p = P.col(i); //第i个向量 
        float mx = 0,my = 0; 
        for(int j = 0; j //x,y分别计算得到平均值。
 
            { 
            mx += p.fl(2*j); 
            my += p.fl(2*j+1); 
            } 
        mx /= n; my /= n; 
        for(int j = 0; j             { 
            p.fl(2*j) -= mx; 
            p.fl(2*j+1) -= my; 
            } 
        }


    //optimise scale and rotation 
    Mat C_old; 
    for(int iter = 0; iter         {   

      注意下边的一行代码,会把每个图像对齐到原点特征点x,y分别加起来,求平均值,得到一个152*1的矩阵,然后对该矩阵进行归一化处理。 
        Mat C = P*Mat::ones(N,1,CV_32F)/N;  //计算形状变换后的平均值 
        normalize(C,C); //canonical shape (对x-进行标准化处理) 
        if(iter > 0) //converged?//收敛?当两个标准形状或者标准形状的误差小于某一值这里是ftol则,停止迭代。  
            {

norm函数默认是矩阵各元素平方和的根范式OpenCV 脸部跟踪(2) 
            if(norm(C,C_old)                 break; 
            } 
        C_old = C.clone(); //remember current estimate//记下当前的矩阵,和下一次进行比较 
        for(int i = 0; i             {

rot_scale_align函数求每副图像的特征点向量和平均向量满足最小乘法时候的旋转矩阵。即求得a,b值组成的旋转矩阵。

OpenCV 脸部跟踪(2)

OpenCV 脸部跟踪(2)

该函数的代码:

Mat    shape_model::rot_scale_align(<span>const</span> Mat &src,    <span>const</span> Mat &dst)
    {
    <span>//construct linear system</span>
    <span>int</span> n = src.rows/2;
    <span>float</span> a=0,b=0,d=0;
    <span>for</span>(<span>int</span> i = 0; i //x*x+y*y
        a += src.fl(2*i) * dst.fl(2*i  ) + src.fl(2*i+1) * dst.fl(2*i+1);
        b += src.fl(2*i) * dst.fl(2*i+1) - src.fl(2*i+1) * dst.fl(2*i  );
        }
    a /= d; 
    b /= d;
    <span>//solved linear system</span>
    <span>return</span> (Mat_float>(2,2) 
<br>

<br>
              Mat R = this->rot_scale_align(P.col(i),C); <span>//求两个形状之间的误差满足最小二乘时的旋转矩阵。即相当于两个形状"最靠近"时,需要的旋转的仿射矩阵 <br>
</span>            for(int j = 0; j 
                { <span>//apply similarity transform//应用相似变换,这对形状中的每一个点,应用仿射矩阵。  变化后,该特征点向量会靠近平均特征向量。之后经过反复迭代,直到平均向量和上次比较变化很小时,退出迭代。 <br>
</span>                float x = P.fl(2*j,i),y = P.fl(2*j+1,i); <br>
                P.fl(2*j  ,i) = R.fl(0,0)*x + R.fl(0,1)*y; <br>
                P.fl(2*j+1,i) = R.fl(1,0)*x + R.fl(1,1)*y; <br>
                } <br>
            } <br>
        } <br>
    return P; <br>
    } <br>

<p>
</p>
<p>
</p>
<p>
</p>
<p>
</p>
<p>
</p>
<p>
</p>
<p>
</p>
<p>
</p>
<p>
</p>
<p>
</p>
<p>
</p>
<p>
</p>
<p>
</p>
<p>
</p>
<p>
</p>
<p>
</p>
<p>
</p>
<p>
</p>
<p>
</p>
<p>
<span>      通过<span>Procrustes analysis对齐的特征向量,我们要用一个统一的矩阵把平移和旋转统一起来表示<span>(成为线性表示),</span>然后把该矩阵追加到局部变形空间,注意对该矩阵表示,我们最后进行了史密斯正交处理。</span></span></p>
<p>
<img  src="/static/imghw/default1.png" data-src="/inc/test.jsp?url=http%3A%2F%2Fimages.cnitblog.com%2Fblog%2F361409%2F201312%2F14113411-9fdaefb021b14f52a72d2e3dae833c22.png&refer=http%3A%2F%2Fblog.csdn.net%2Fzhazhiqiang%2Farticle%2Fdetails%2F20158453" class="lazy" alt="OpenCV 脸部跟踪(2)" ></p>
<p>
<span>我们通过函数 calc_rigid_basis得到该矩阵表示:</span></p>
<p>
<span>Mat  shape_model::calc_rigid_basis(const Mat &X) <br>
    { <br>
    <span>//compute mean shape</span> <br>
    int N = X.cols,n = X.rows/2; <br>
    Mat mean = X*Mat::ones(N,1,CV_32F)/N;</span></p>
<p>
<span>    <span>//construct basis for similarity transform <br>
</span>    Mat R(2*n,4,CV_32F); <br>
    for(int i = 0; i 
        { <br>
        R.fl(2*i,0) =  mean.fl(2*i  ); <br>
        R.fl(2*i+1,0) =  mean.fl(2*i+1); <br>
        R.fl(2*i,1) = -mean.fl(2*i+1); <br>
        R.fl(2*i+1,1) =  mean.fl(2*i  ); <br>
        R.fl(2*i,2) =  1.0;   <br>
        R.fl(2*i+1,2) =  0.0; <br>
        R.fl(2*i,3) =  0.0;  <br>
        R.fl(2*i+1,3) =  1.0; <br>
        } <br>
    <span>//Gram-Schmidt orthonormalization <br>
</span>    for(int i = 0; i 
        { <br>
        Mat r = R.col(i); <br>
        for(int j = 0; j 
            { <br>
            Mat b = R.col(j); <br>
            r -= b*(b.t()*r); <br>
            } <br>
        normalize(r,r); <br>
        } <br>
    return R; <br>
    }</span></p>
<p>
</p>
<p>
<span>下面我们看看train函数的实现:</span></p>
<p>
<span>两篇参考的翻译:http://blog.csdn.net/raby_gyl/article/details/13148193</span></p>
<p>
<span>http://blog.csdn.net/raby_gyl/article/details/13024867</span></p>
<p>
</p>
<p>
<span>      该函数的输入为n个样本图像的采样特征点,该点集会被首先转化为行152,列为样本数量的矩阵表示,另外还有连通性点集索引,以及方差的置信区间以及保留模型的最大数量。</span></p>
<p>
<span>void    train(const vector<vector> > &p, <span>//N-example shapes</span> <br>
    const vector<vec2i> &con = vector<vec2i>(), <span>//point-connectivity <br>
</span>    const float frac = 0.95,           <span>//fraction of variation to retain <br>
</span>    const int kmax = 10)              <span>//maximum number of modes to retain <br>
</span>    { <br>
    <span>//vectorize points</span> <br>
    Mat X = this->pts2mat(points);</vec2i></vec2i></vector></span></p>
<p>
<span><span>N是样本的数目,n是76,表示76个特征点。 <br>
</span>    int N = X.cols,n = X.rows/2;</span></p>
<p>
<span>    <span>//align shapes <br>
</span>    Mat Y = this->procrustes(X);</span></p>
<p>
<span>    <span>//compute rigid transformation</span> <span>计算得到刚体变化矩阵R <br>
</span>    Mat R = this->calc_rigid_basis(Y);</span></p>
<p>
<span>      脸部局部变形我们用一个线性模型表示,主要的思想如下图所示:一个有N个面部特征组成面部形状,被建模成一个2N维空间的点。我们要尽量找到一个低维的超平面,在这个平面内,所有的面部形状都在里面,由于这个超平面只是2N维空间的子集,占用刚少的空间,处理起来更快。可以想得到,如果这个子空间来自于一个人,则子空间的点,表现这个人的各种表情变化。前面的教程中,我们知道PCA算法能够找到低维子空间,但PCA算法需要指定子空间的维数,在启发式算法中有时候这个值很难选择。在本程序中,我们通过SVD算法来模拟PCA算法。</span></p>
<p>
 </p>
<p>
<img  src="/static/imghw/default1.png" data-src="/inc/test.jsp?url=http%3A%2F%2Fimages.cnitblog.com%2Fblog%2F361409%2F201312%2F15192025-1229008c4629424d8379873e5a6e517b.png&refer=http%3A%2F%2Fblog.csdn.net%2Fzhazhiqiang%2Farticle%2Fdetails%2F20158453" class="lazy" alt="OpenCV 脸部跟踪(2)" ></p>
<p>
 </p>
<p>
<span>    <span>//compute non-rigid transformation</span> </span></p>
<p>
<span><span>Y是152*1294的矩阵,它是procrustes分析的结果,R是刚体变化矩阵152*4,它的转置就是4*152 <br>
</span>    Mat P = R.t()*Y; <span>//原始的位置 <br>
</span>    <span>Mat dY = Y - R*P; </span><span>//</span><span>dy变量的每一列表示减去均值的Procrustes对齐形状,<span>投影刚体运动</span></span></span></p>
<p>
<span>      奇异值分解SVD有效的应用到形状数据的<span>协方差矩阵</span>(即,dY.t()*dY),OpenCV的SVD类的w成员存储着数据变化性的主要方向的变量,从最大到最小排序。一个选择子空间维数的普通方法是选择保存数据总能量分数frac的方向最小集(即占总能量的比例为frac),这是通过svd.w记录表示的,因为这些记录是从最大的到最小的排序的,它充分地用来评估子空间,通过用变化性方向的最大值k来评估能量。他们自己的方向存储在SVD类的u成员内。svd.w和svd.u成分一般分别被成为特征值和特征矢量。</span><span><span> <br>
</span><span>    SVD svd(dY*dY.t()); <br>
    int m = min(min(kmax,N-1),n-1); <br>
    float vsum = 0; <br>
    for(int i = 0; i 
        vsum += svd.w.fl(i); <br>
    float v = 0; <br>
    int k = 0;</span></span></p>
<p>
<span><span><span>达到了95%的主成分量,退出,frac=0.95</span> <br>
    for(k = 0; k 
        { <br>
        v += svd.w.fl(k); <br>
        if(v/vsum >= frac){k++; break;} <br>
        } <br>
    if(k > m)  k = m;</span></span></p>
<p>
<span><span><span>取前k个特征向量 <br>
</span>    <span>Mat D = svd.u(Rect(0,0,k,2*n));</span></span></span></p>
<p>
<span><span>把全局刚体运动和局部变形运动结合起来,注意V的第一列是缩放,第三、四列分别是x,y偏移。</span></span></p>
<p>
<span>//combine bases <br>
    V.create(2*n,4+k,CV_32F); <br>
    Mat Vr = V(Rect(0,0,4,2*n));   <span>//刚体子空间 <br>
</span>    R.copyTo(Vr);     <span>//非刚体子空间</span> <br>
    Mat Vd = V(Rect(4,0,k,2*n)); <br>
    D.copyTo(Vd);</span></p>
<p>
<span>      最后我们要注意的一点是如何约束子空间坐标,以使得子空间内的面部形状都是有效的。在下面的图中,我们可以看到,对于子空间内的图像,如果在某个方向改变坐标值,当坐标值小时候,它仍是一个脸的形状,但是变化值大时候,就不知道是什么玩意了。防止出现这种情况的最简单方法,就是把变化的值clamp在一个范围内,通常是现在± 3 的范围,这样可以cover到99.7%的脸部变化。clamping的值通过下面的代码计算:</span></p>
<p>
 </p>
<p>
<img  src="/static/imghw/default1.png" data-src="/inc/test.jsp?url=http%3A%2F%2Fimages.cnitblog.com%2Fblog%2F361409%2F201312%2F15192029-199dc6314d9e4c14bbf7fe4b35f098c0.png&refer=http%3A%2F%2Fblog.csdn.net%2Fzhazhiqiang%2Farticle%2Fdetails%2F20158453" class="lazy" alt="OpenCV 脸部跟踪(2)" ></p>
<p>
<span> <span>   //compute variance (normalized wrt scale)</span> </span></p>
<p>
<span><br>
   <span>Mat Q = V.t()*X; </span><span>//把数据投影到子空间 <br>
</span>    for(int i = 0; i //normalize coordinates w.r.t scale <br>
</span>        {  <span>//用第一个坐标缩放,防止太大的缩放值影响脸部识别 <br>
</span>        float v = Q.fl(0,i); <br>
        Mat q = Q.col(i); <br>
        q /= v; <br>
        } <br>
    e.create(4+k,1,CV_32F); <br>
    pow(Q,2,Q); <br>
    for(int i = 0; i 
        { <br>
        if(i 
            e.fl(i) = -1; <span>//no clamping for rigid coefficients <br>
</span>        else <br>
            e.fl(i) = Q.row(i).dot(Mat::ones(1,N,CV_32F))/(N-1); <br>
        } <br>
    <span>//store connectivity <br>
</span>    if(con.size() > 0) <br>
        { //default connectivity <br>
        int m = con.size(); <br>
        C.create(m,2,CV_32F); <br>
        for(int i = 0; i 
            { <br>
            C.at<int>(i,0) = con[i][0]; <br>
            C.at<int>(i,1) = con[i][1]; <br>
            } <br>
        } <br>
    else <br>
        {              //user-specified connectivity <br>
            C.create(n,2,CV_32S); <br>
            for(int i = 0; i 
                { <br>
                C.at<int>(i,0) = i; C.at<int>(i,1) = i+1; <br>
                }    <br>
            C.at<int>(n-1,0) = n-1; C.at<int>(n-1,1) = 0; <br>
        } <br>
    }</int></int></int></int></int></int></p>
<p>
</p>
<p>
<span>工程文件:FirstOpenCV40,</span></p>
<p>
<span>程序的运行参数为:annotations.yaml shapemodle.yaml</span></p>
<p>
<span>程序执行后,可以看到我们只保留了14个模型。</span></p>
<p>
<img  src="/static/imghw/default1.png" data-src="/inc/test.jsp?url=http%3A%2F%2Fimages.cnitblog.com%2Fblog%2F361409%2F201312%2F14113419-908c110a67d3464abcd452edcc027668.png&refer=http%3A%2F%2Fblog.csdn.net%2Fzhazhiqiang%2Farticle%2Fdetails%2F20158453" class="lazy" alt="OpenCV 脸部跟踪(2)" ></p>
<p>
<span>我们也可以使用下面的运行参数:annotations.yaml shapemodle.yaml –mirror</span></p>
<p>
<span>这时候,每副图像的特征点,会生成一个y轴对称的镜像特征点集,这时训练的采样数目翻倍,为5828。</span></p>
<p>
<span>在工程文件FirstOpenCV41中,我们可视化了生成的模型,会连续显示14个模型的不同姿态:</span></p>
<p>
<img  src="/static/imghw/default1.png" data-src="/inc/test.jsp?url=http%3A%2F%2Fimages.cnitblog.com%2Fblog%2F361409%2F201312%2F14113424-ac033ab541ca4a349466840ce5b41628.png&refer=http%3A%2F%2Fblog.csdn.net%2Fzhazhiqiang%2Farticle%2Fdetails%2F20158453" class="lazy" alt="OpenCV 脸部跟踪(2)" ></p>


登入後複製
本網站聲明
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn

熱AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover

AI Clothes Remover

用於從照片中去除衣服的線上人工智慧工具。

Undress AI Tool

Undress AI Tool

免費脫衣圖片

Clothoff.io

Clothoff.io

AI脫衣器

Video Face Swap

Video Face Swap

使用我們完全免費的人工智慧換臉工具,輕鬆在任何影片中換臉!

熱工具

記事本++7.3.1

記事本++7.3.1

好用且免費的程式碼編輯器

SublimeText3漢化版

SublimeText3漢化版

中文版,非常好用

禪工作室 13.0.1

禪工作室 13.0.1

強大的PHP整合開發環境

Dreamweaver CS6

Dreamweaver CS6

視覺化網頁開發工具

SublimeText3 Mac版

SublimeText3 Mac版

神級程式碼編輯軟體(SublimeText3)

http狀態碼520是什麼意思 http狀態碼520是什麼意思 Oct 13, 2023 pm 03:11 PM

http狀態碼520是指伺服器在處理請求時遇到了一個未知的錯誤,無法提供更具體的資訊。用於表示伺服器在處理請求時發生了一個未知的錯誤,可能是由於伺服器配置問題、網路問題或其他未知原因導致的。通常是由伺服器配置問題、網路問題、伺服器過載或程式碼錯誤等原因導致的。如果遇到狀態碼520錯誤,最好聯絡網站管理員或技術支援團隊以取得更多的資訊和協助。

使用pip軟體套件管理器快速安裝OpenCV學習指南 使用pip軟體套件管理器快速安裝OpenCV學習指南 Jan 18, 2024 am 09:55 AM

使用pip命令輕鬆安裝OpenCV教程,需要具體程式碼範例OpenCV(OpenSourceComputerVisionLibrary)是一個開源的電腦視覺庫,它包含了大量的電腦視覺演算法和函數,可以幫助開發者快速建立圖像和視訊處理相關的應用程式。在使用OpenCV之前,我們需要先安裝它。幸運的是,Python提供了一個強大的工具pip來管理第三方函式庫

遊戲視覺效果的路徑追蹤與光線追蹤 遊戲視覺效果的路徑追蹤與光線追蹤 Feb 19, 2024 am 11:36 AM

決定使用路徑追蹤或光線追蹤對於遊戲開發者來說是一個關鍵的選擇。雖然它們在視覺上都有出色的表現,但在實際應用上存在一些差異。因此,遊戲愛好者需要仔細權衡兩者的優勢和劣勢,以確定哪種技術更適合實現他們想要的視覺效果。什麼是光線跟踪?光線追蹤是一種複雜的渲染技術,用於模擬光線在虛擬環境中的傳播和互動。與傳統的光柵化方法不同,光線追蹤透過追蹤光線的路徑來產生真實的光照和陰影效果,從而提供更逼真的視覺體驗。這種技術不僅可以產生更真實的影像,還可以模擬更複雜的光線效果,使場景看起來更加逼真和生動。其主要概念

OpenCV安裝教學:PyCharm用戶必看 OpenCV安裝教學:PyCharm用戶必看 Feb 22, 2024 pm 09:21 PM

OpenCV是一種用於電腦視覺和影像處理的開源庫,廣泛應用於機器學習、影像辨識、視訊處理等領域。在使用OpenCV進行開發時,為了能夠更好地調試和運行程序,許多開發者選擇使用PyCharm這款強大的Python整合開發環境。本文將為PyCharm用戶提供OpenCV的安裝教學課程,並附上具體的程式碼範例。第一步:安裝Python首先,請確保您已經安裝了Python

http狀態碼403是什麼 http狀態碼403是什麼 Oct 07, 2023 pm 02:04 PM

http狀態碼403是伺服器拒絕了客戶端的請求的意思。解決http狀態碼403的方法是:1、檢查身份驗證憑證,如果伺服器要求身份驗證,確保提供正確的憑證;2、檢查IP位址限制,如果伺服器對IP位址進行了限制,確保客戶端的IP位址被列入白名單或未列入黑名單;3、檢查文件權限設置,如果403狀態碼與文件或目錄的權限設置有關,確保客戶端具有足夠的權限訪問這些文件或目錄等等。

瞭解網頁重定向的常見應用場景並了解HTTP301狀態碼 瞭解網頁重定向的常見應用場景並了解HTTP301狀態碼 Feb 18, 2024 pm 08:41 PM

掌握HTTP301狀態碼的意思:網頁重定向的常見應用場景隨著網路的快速發展,人們對網頁互動的要求也越來越高。在網頁設計領域,網頁重定向是一種常見且重要的技術,透過HTTP301狀態碼來實現。本文將探討HTTP301狀態碼的意義以及在網頁重新導向中的常見應用場景。 HTTP301狀態碼是指永久重新導向(PermanentRedirect)。當伺服器接收到客戶端發

如何使用Nginx Proxy Manager實現HTTP到HTTPS的自動跳轉 如何使用Nginx Proxy Manager實現HTTP到HTTPS的自動跳轉 Sep 26, 2023 am 11:19 AM

如何使用NginxProxyManager實現HTTP到HTTPS的自動跳轉隨著互聯網的發展,越來越多的網站開始採用HTTPS協議來加密傳輸數據,以提高數據的安全性和用戶的隱私保護。由於HTTPS協定需要SSL憑證的支持,因此在部署HTTPS協定時需要有一定的技術支援。 Nginx是一款強大且常用的HTTP伺服器和反向代理伺服器,而NginxProxy

HTTP 200 OK:了解成功回應的意義與用途 HTTP 200 OK:了解成功回應的意義與用途 Dec 26, 2023 am 10:25 AM

HTTP狀態碼200:探索成功回應的意義與用途HTTP狀態碼是用來表示伺服器回應狀態的數字代碼。其中,狀態碼200表示請求已成功被伺服器處理。本文將探討HTTP狀態碼200的具體意義與用途。首先,讓我們來了解HTTP狀態碼的分類。狀態碼分為五個類別,分別是1xx、2xx、3xx、4xx和5xx。其中,2xx表示成功的回應。而200是2xx中最常見的狀態碼

See all articles