1. 程式人生 > >BFM模型介紹及視覺化實現(C++)

BFM模型介紹及視覺化實現(C++)

BFM模型介紹及視覺化實現(C++)

BFM模型基本介紹

Basel Face Model是一個開源的人臉資料庫,其基本原理是3DMM,因此其便是在PCA的基礎上進行儲存的。
目前有兩個版本的資料庫(2009和2017)。
官方網站:2009,2017

資料內容(以2009版本為例)

檔案內容

01_MorphableModel.mat(資料主體)

BFM模型由53490個頂點構成,其shape/texture的資料長度為160470(53490*3),因為其排列方式如下:

shape: x_1, y_1, z_1, x_2, y_2, z_2, ..., x_{53490}, y_{53490}, z_{53490}
texture: r_1, g_1, b_1, r_2, g_2, b_2, ..., r_{53490}, g_{53490}, b_{53490}

.h5檔案與.mat檔案對應關係

[注] .h5檔案中的tl數量與.mat數量不同,主成分方差的值也不同,且shape的值是.mat中shape值的0.001倍(見/shape/representer/length-unit)。

Matlab指令碼

建議閱讀script_gen_random_head.m檔案,該指令碼實現瞭如何生成隨機臉,從中我們可以學習到BFM模型的使用方法。

2009與2017版本區別

2009年版本資料集:

  • 提供資料格式:mat(01_MorphableModel.mat
    )和h5(model2009-publicmm1-bfm.h5);
  • 提供一系列Matlab指令碼,有生成隨機臉等功能;
  • 提供多種特徵點(PublicMM1/11_feature_points);
  • 提供segment的mask(PublicMM1/09_mask);
  • 提供對稱點的對應關係(PublicMM1/13_symmetry_indices);
  • 提供屬性(PublicMM1/04_attributes.mat
  • 不提供表情;

2017年版本資料集:

  • 提供資料格式:h5(原版(model2017-1_bfm_nomouth.h5)和裁剪過的版本(model2017-1_face12_nomouth.h5));
  • 不提供Matlab指令碼(本身也無mat格式資料);
  • 提供單種特徵點(metadata/landmarks/text);
  • 不提供segment、對稱點的對應關係和屬性;
  • 提供表情(expression);

基本原理

目標shape或者texture都可以通過如下式子得到:

obj = average + pc * (coeficient .* pcVariance)

其中係數(coeficient)是變數,其餘均是資料庫裡的常量,其是一個199維(對應199個PC)的向量。

C++實現BFM模型可視工具

資料讀取

我們可以讀取.mat檔案或者.h檔案,因為讀取.mat檔案需要使用Matlab的庫檔案,我們暫時不考慮。

讀取.h5格式檔案

.h5檔案無法直接通過文字工具開啟,需要下載專門的可視工具,此處我使用了HDFView。

通過該檔案我們可以瞭解到HDF5檔案的內部格式。
在C++中使用HDF5讀寫需要下載官方的庫:
HDF5庫下載地址
官網右上角註冊後下載,隨後選擇對應版本下載。
[注] 在Windows的Visual Studio使用shared庫需要編譯過程定義H5_BUILT_AS_DYNAMIC_LIB。(若出現LINK2001錯誤可以新增這個來解決)
[注] static庫命名前面以lib開頭,例如hdf5.lib是shared庫,libhdf5.lib是static庫。
在VS的包含目錄和庫目錄中新增對應的inlcude和lib目錄。
在連結器的輸入中增加szip.lib;zlib.lib;hdf5.lib;hdf5_cpp.lib;,並將對應的.dll檔案放置到Windows/System32Windows/SysWOW64

我們只需要用到HDF5中的讀取功能,步驟是開啟檔案->開啟資料庫->讀取資料->關閉資料庫->關閉檔案。我們以shape平均值為例:

float *shape_mu_raw = new float[N_VERTICE * 3];
H5File file(bfm_h5_path, H5F_ACC_RDONLY);
DataSet shape_mu_data = file.openDataSet("/shape/model/mean"); 
shape_mu_data.read(shape_mu_raw, PredType::NATIVE_FLOAT); 
raw2vector(shape_mu, shape_mu_raw);   // 自行將陣列轉換成想要存放的格式
shape_mu_data.close(); 
file.close();

shape平均值讀取後需要再乘以1000才等同於.mat格式的讀取。
需要注意的是資料的讀取型別一定要根資料庫中的型別一致。shape/tex的型別均為float,對應PredType::NATIVE_FLOAT,tl的型別為unsigned int,對應PredType::NATIVE_UINT32
[注] 因為缺少pdb檔案,HDF5中的程式碼如果報錯可能無法進行除錯,需要逐行進行錯誤的排除,常見錯誤就是型別不匹配或者長度不匹配。

其他讀取方式

在最開始不瞭解.h5格式的時候,我便使用一些笨方法進行讀取,例如先將.mat格式資料轉換成二進位制檔案/文字檔案再進行讀取。
例如這樣一個matlab指令碼:

function mat2binary(filename, mat, type)
    fid=fopen(filename, 'wb');
    matrix = mat;                        
    [m,n]=size(matrix);
     for i=1:1:m
       for j=1:1:n
            fwrite(fid, matrix(i,j), type);
       end
    end
    fclose(fid);
end

這些指令碼能夠簡單地將mat格式進行轉換,成為容易被C++進行讀取的格式。但是弊端也很明顯,在C++中的讀寫速度非常慢。.h5格式讀寫1s左右完成,二進位制檔案讀寫1分鐘左右完成,文字檔案讀寫5分鐘左右完成。且在儲存大小上,.h5檔案(249MB)≈ 二進位制檔案 < 文字檔案(超過710M)。

生成人臉

即按照上述基本原理中的式子進行實現。

OpenGL進行顯示

這裡使用了Qt5內建的OpenGL模組,通過最簡單的glBegin()glEnd()即可繪出人臉。

double sint = sin(theta), cost = cos(theta);
for (auto t = tl.begin(); t != tl.end(); t++) {
       glBegin(GL_TRIANGLES);
       vec3 tmp = *t;
       glColor3f(tex[tmp.x].x / 255.0, tex[tmp.x].y / 255.0, tex[tmp.x].z / 255.0);
       glVertex3f(shape[tmp.x].x * scale * cost - shape[tmp.x].z * scale * sint, shape[tmp.x].y * scale, shape[tmp.x].x * scale * sint + shape[tmp.x].z * scale * cost);
       glColor3f(tex[tmp.y].x / 255.0, tex[tmp.y].y / 255.0, tex[tmp.y].z / 255.0);
       glVertex3f(shape[tmp.y].x * scale * cost - shape[tmp.y].z * scale * sint, shape[tmp.y].y * scale, shape[tmp.y].x * scale * sint + shape[tmp.y].z * scale * cost);
       glColor3f(tex[tmp.z].x / 255.0, tex[tmp.z].y / 255.0, tex[tmp.z].z / 255.0);
       glVertex3f(shape[tmp.z].x * scale * cost - shape[tmp.z].z * scale * sint, shape[tmp.z].y * scale, shape[tmp.z].x * scale * sint + shape[tmp.z].z * scale * cost);
       glEnd();
}

使用thetascale引數用於實現滑鼠和鍵盤對模型方向的控制。
根據模型大小,我們設定相應的視角:

void OpenGLWidget::resizeGL(int width, int height) {
        glViewport(0, 0, width, height);
        glMatrixMode(GL_PROJECTION);
        glLoadIdentity();
        gluPerspective(60.0, (GLfloat)width / (GLfloat)height, 1.0, 600000.0);
        glMatrixMode(GL_MODELVIEW);
        glLoadIdentity();
        gluLookAt(0, 0, 300000.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0);
}

結果展示

初始介面(左側顯示一個彩色三角形):

當隨機性設定為0(即coeficient設為[0, ..., 0]),生成平均臉:

隨機生成人臉,或隨機設定PC值:

原始碼

GitHub:https://github.com/Great-Keith/bfm-visual-t