zoukankan      html  css  js  c++  java
  • BFM模型介绍及可视化实现(C++)

    BFM模型介绍及可视化实现(C++)

    BFM模型基本介绍

    Basel Face Model是一个开源的人脸数据库,其基本原理是3DMM,因此其便是在PCA的基础上进行存储的。
    目前有两个版本的数据库(2009和2017)。
    官方网站:20092017

    数据内容(以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-tool

  • 相关阅读:
    [HAOI2015]树上操作(树链剖分)
    树链剖分——解决树上路径问题利器
    [CodeForces 833B] The Bakery(数据结构优化dp)
    [国家集训队]最长双回文串(马拉车)
    [国家集训队]拉拉队排练(Manacher)
    【ATcoder】AtCoder Beginner Contest 159 题解
    蒟蒻的数列[BZOJ4636](线段树)
    Manacher马拉车算法——解决最长回文子串问题
    HDU 1501 Zipper (记忆化搜索)
    HDU 1428 漫步校园 (dfs+记忆化搜索dfs)
  • 原文地址:https://www.cnblogs.com/bemfoo/p/11788638.html
Copyright © 2011-2022 走看看