基于深度学习的人脸识别系统系列(Caffe+OpenCV+Dlib)——【五】设计人脸识别的注册类

作者:Mr_Curry

前言

基于深度学习的人脸识别系统,一共用到了5个开源库:OpenCV(计算机视觉库)、Caffe(深度学习库)、Dlib(机器学习库)、libfacedetection(人脸检测库)、cudnn(gpu加速库)。
用到了一个开源的深度学习模型:VGG model。
最终的效果是很赞的,识别一张人脸的速度是0.039秒,而且最重要的是:精度高啊!!!
CPU:intel i5-4590
GPU:GTX 980
系统:Win 10
OpenCV版本:3.1(这个无所谓)
Caffe版本:Microsoft caffe (微软编译的Caffe,安装方便,在这里安利一波)
Dlib版本:19.0(也无所谓
CUDA版本:7.5
cudnn版本:4
libfacedetection:6月份之后的(这个有所谓,6月后出了64位版本的)
这个系列纯C++构成,有问题的各位朋同学可以直接在博客下留言,我们互相交流学习。
====================================================================

本篇是该系列的第五篇博客,介绍设计一个人脸识别的注册类与识别类用于具体的识别任务。

思路

回顾一下这个系列的前四篇博文,把人脸识别的整个任务剖析为了一个个的小任务。我们现在希望用我们定义的这些接口能够非常方便的进行人脸识别的任务,而且可以实现很短的时间内就匹配一个人脸。我们现在希望,可以将其用于一个具体的分类任务中。
我们可以自己来定义这个任务:假设我有20个人要进行人脸识别,我们希望通过我们设计的代码来实现这个任务。如何方便而又快捷的进行实现呢?
这里考虑设计两个类:Register与Recognition。当有人需要注册时,其信息会添加入Register类中的某个成员空间,这里我们用到的信息可以稍微简单一点:
姓名、注册图片、人脸空间。
其中姓名、注册图片是必须要的,而这个人脸空间的意思就是:我们可以实现很多不同场景下的人脸识别。比如在考勤方面,学校想要人脸识别时,受限于人脸数目众多,不可能把所有的人都放在一个数据库里。这个时候我们可以某个信息来区分他们,比如说:课程。 课程这个信息就可以算作一个人脸空间。当我们进行具体的识别时,只会在相应的人脸空间内进行匹配,而不会在其他的人脸空间内匹配。
由于在上一篇博客我们介绍了如何使用CUBLAS来进行人脸向量的运算,那么我们需要对代码进行一些修改,将其换为二维数组。

修改

ExtractFeature.h:

 #include <opencv.hpp>
using namespace cv;
using namespace std;

float* ExtractFeature_(Mat FaceROI);//添加一个提取特征的函数
vector<float> ExtractFeature(Mat FaceROI);
void Caffe_Predefine();

在这里面添加相应的函数ExtractFeature.cpp:

float* ExtractFeature_(Mat FaceROI)
{
    caffe::Caffe::set_mode(caffe::Caffe::GPU);
    std::vector<Mat> test;
    std::vector<int> testLabel;
    test.push_back(FaceROI);
    testLabel.push_back(0);
    memory_layer->AddMatVector(test, testLabel);// memory_layer and net , must be define be a global variable.
    test.clear();
    testLabel.clear();
    std::vector<caffe::Blob<float>*> input_vec;
    net->Forward(input_vec);
    boost::shared_ptr<caffe::Blob<float>> fc8 = net->blob_by_name("fc8");
    int test_num = 0;
    float FaceVector[2622];
    while (test_num < 2622)
    {
        FaceVector[test_num] = (fc8->data_at(0, test_num, 1, 1));
        test_num++;
    }
    return FaceVector;
}

实现

我们希望整个类能够具有这样一些功能:可以新加入人脸到某个人脸空间中,可以将人脸空间中的人脸提取出来保存到一个矩阵当中(很多个向量组成的矩阵),可以把提取出来的矩阵和人脸的名字都保存起来以便于下一次的读取。
SaveVector.h:

#include <opencv.hpp>
#include <fstream>
#include <sstream>
using namespace cv;
using namespace std;

void SaveNameVector(vector<string>   &NameVector, string filename);//保存姓名,需要输入
vector<string> LoadNameVector(vector<string>   &NameVector, string filename);
void SaveFaceMatrix(float **FaceMatrix, string filename, int rows);//用于保存提取出来特征的人脸矩阵,需要输入:人脸矩阵、保存的文件名,矩阵的行数(列均为2622维)
Mat LoadMat(string file);//将xml文件提取出来转换为OpenCV的Mat类
float** MatToVector2d(Mat &FaceMatrix_mat);//将Mat类转换为二维数组

SaveVector.cpp:

#include <SaveVector.h>
Mat Vector2dToMat(float **FaceMatrix,int rows)
{
    //know FaceMatrix's col and row.
    //FaceVector->Mat
    Mat T(rows, 2622, CV_32F);
    for (int i = 0; i < rows; i++)
        for (int j = 0; j < 2622; j++)
        {
            T.at<float>(i, j) = FaceMatrix[i][j];
        }
    return T;
}
void SaveMat(Mat &FaceMatrix_,string filename)
{
    FileStorage fs(filename, FileStorage::WRITE);
    fs << "FaceMatrix" << FaceMatrix_;
    fs.release();
}
Mat LoadMat(string file)//文件名
{
    FileStorage fs(file, FileStorage::READ);
    Mat FaceMatrix_;
    fs["FaceMatrix"] >> FaceMatrix_;
    return FaceMatrix_;
}
float** MatToVector2d(Mat &FaceMatrix_mat)
{
    float **array2d = new float*[FaceMatrix_mat.rows];
    for (int i = 0; i<FaceMatrix_mat.rows; ++i)
        array2d[i] = new float[FaceMatrix_mat.cols];

    for (int i = 0; i<FaceMatrix_mat.rows; ++i)
        array2d[i] = FaceMatrix_mat.ptr<float>(i);

    return array2d;
}

void SaveFaceMatrix(float *FaceMatrix[], string filename,int rows)
{
    Mat T = Vector2dToMat(FaceMatrix, rows);
    if (!T.empty())
        SaveMat(T, filename);
    else 
    { 
        cout << "Please check out your the matrix and the file.We can not read any information." << endl;
        exit(0);
    }
}

//存储姓名
void SaveNameVector(vector<string>   &NameVector, string filename){
    int Num = 0;
    ofstream NameSaveFile(filename, ios::app);
    while (Num < NameVector.size())
        NameSaveFile << NameVector[Num++] << endl;
    NameSaveFile.clear();
}
vector<string> LoadNameVector(vector<string>   &NameVector_, string filename)
{
    ifstream in(filename);
    int Num = 0;
    string line;
    if (in){
        while (getline(in, line))
        {
            NameVector_.push_back(line);
        }
    }
    in.clear();
    return NameVector_;
}

这里在存储时,先将二维数组转换为OpenCV的Mat类型,再使用FileStorage将其序列化。当然,你也可以使用boost库来做这个事情。
存储的结果是这样的(一个人):
这里写图片描述
这里写图片描述
然后我们再来实现类:
Register.h:
//define a register

#include <opencv.hpp>
#include <SaveVector.h>
using namespace cv;
using namespace std;

class Register
{
public:
    string FaceSpace;//The name of FaceSpace
    vector<string> FaceName;//People's name ,the same as FaceNumber
    //float *
    float* MatToVector_(Mat TrainMat);//将Mat在人脸识别、预处理后转换为一个向量
    float *FaceMatrix[20];//20个人
    void JoinFaceSpace_(Mat newFace, string FaceSpace, string FaceName);//join the new face to FaceSpace
    float** LoadVector_(string FaceSpace);//读入数据
    Mat FaceMatrix_mat;//临时存储读取的Mat类型
private:

    void SaveVector_(float *FaceMatrix_[], vector<string> FaceName_, string FaceSpace_) // save the people's  face vector
    {
        //使用xml来存储数据
        if (!(FaceMatrix_ == NULL) && !FaceName_.empty())
        {
            SaveFaceMatrix(FaceMatrix_, "data/" + FaceSpace_ + "_FaceMatrix.xml", FaceName_.size());
            SaveNameVector(FaceName, "data/" + FaceSpace_ + "_NameVector.txt");
        }
        else { cout << "Sorry.There are some problems while saving your face and name. please try again" << endl; }
    }
};

Register.cpp:

#include <Register.h>
#include <FaceDetect.h>
#include <ComputeDistance.h>
#include <ExtractFeature_.h>

float* Register::MatToVector_(Mat TrainMat)
{
    Mat TrainMat_ROI = Facedetect(TrainMat);
    if (!TrainMat_ROI.empty())
        return ExtractFeature_(TrainMat_ROI);
    else return NULL;
}

void arrayJoinArray2d(float *feature, float *FaceMatrix[], int i)//实现人脸向量加入人脸矩阵
{
    FaceMatrix[i] = feature;
}
void Register::JoinFaceSpace_(Mat newFace, string FaceSpace, string Name)
{
    float *FaceVector = MatToVector_(newFace);
    if (FaceVector!=NULL)//如果不为空,即存在人脸
    {
        //加入两个数据表
        arrayJoinArray2d(FaceVector, FaceMatrix, FaceName.size());
        FaceName.push_back(Name);
        //保存这两个数据表
        //格式为:矩阵类型;向量类型
        //下次可直接读入
        SaveVector_(FaceMatrix, FaceName, FaceSpace);
    }
    else
    {
        cout << "Please try again,We can not find your face." << endl;
    }
}
float** Register::LoadVector_(string FaceSpace) // start load the features.
{

    string FaceVectorRoad = "data/" + FaceSpace + "_FaceMatrix.xml";
    string NameVectorRoad = "data/" + FaceSpace + "_NameVector.txt";
    vector<string>   NameVector;
    NameVector=LoadNameVector(NameVector, NameVectorRoad);
    if ( !NameVector.empty())
    {
        FaceName = NameVector;
        FaceMatrix_mat = LoadMat(FaceVectorRoad);
        if (!FaceMatrix_mat.empty())
        {
            float** a = MatToVector2d(FaceMatrix_mat);
            cout << "Sucessfully read the FaceSpace:" + FaceSpace + "'s data!" << endl;
            return a;
        }
    }
    else { cout << "There is no data in this FaceSpace:" + FaceSpace + ",Please input ." << endl; }
}

这样一来,我们在main函数里就可以非常方便的调用。比如我们要提取一张人脸的图片(这个人的名字叫lena),将其保存在LLEENNAA这个人脸空间中:

#include <Register.h>
#include <FaceDetect.h>
#include <ExtractFeature_.h>

int main()
{
    Caffe_Predefine();
    Dlib_Predefine();
    Register train;
    Mat lena = imread("lena.jpg");
    train.JoinFaceSpace_(lena,"LLEENNAA","lena");
}

即可。
我们可以做个试验,看看程序的正确性。在执行train.JoinFaceSpace_(lena,”LLEENNAA”,”lena”)后,Register里的FaceMatrix会相应增加,并且也会保存为xml和txt。我们再读取这个xml和txt来看看。

#include <Register.h>
#include <FaceDetect.h>
#include <ExtractFeature_.h>

int main()
{
    Caffe_Predefine();
    Dlib_Predefine();
    Register train;
    Mat lena = imread("lena.jpg");
    train.JoinFaceSpace_(lena,"LLEENNAA","lena");

    cout << "当前的人脸矩阵的第一个元素为" << train.FaceMatrix[0][0] << endl;
    cout << "当前的人脸名容器的第一个元素为" << train.FaceName[0]<< endl;

    Register test;

    cout<<"读取保存的人脸矩阵,其第一个元素为"<<test.LoadVector_("LLEENNAA")[0][0]<<endl;
    cout << "读取保存的人脸名字容器,其第一个元素为" << test.FaceName[0]<<endl;
    imshow("lena.jpg",lena);
    waitKey(0);
}

程序显示完全一致,表示正确:
这里写图片描述

=================================================================

基于深度学习的人脸识别系统系列(Caffe+OpenCV+Dlib)——【五】设计人脸识别的注册类 完结,如果在代码过程中出现了任何问题,直接在博客下留言即可,共同交流学习。

发表评论

23个评论

  • chary8088

    首先 感谢楼主写这么高质量的博文~,我一直想寻找一个使用dlib+caffe/tensorflow深度学习,而且是c++的开源东东玩玩,看到此文如获至宝;其次 楼主所发的博文的确没有缩水没有偷工减料;我已经按照博主的文章在Ubuntu上跑起来了,因为是虚拟机,没法使用GPU,对比两张照片的时间是57ms左右,性能还是可以接受的;对比同一个人不同角度的,不同背景的照片,使用余弦距离得出的结果是0.7678,如果使用fc7,得出的结果是0.88左右;对比不同人的照片结果是0.2到0.6左右,很准确;然后 想问下博主,如果单纯使用余弦来识别是不是有点太单一了?不知道可靠性有多少?业界主流的也是余弦吗?

    2017-07-06 14:45:50回复

  • qq_23947237

    您好我的图片经过了处理cvtColor(img, img, CV_BGR2RGB); resize(img, img, Size(224, 224));,而且也生成了xml文件,<FaceMatrix type_id="opencv-matrix"> <rows>5</rows> <cols>2622</cols> <dt>f</dt> <data>但是里面会有一些.Nan,说明提取特征出现了not a number,这是怎么回事呢,程序往下不能再进行余弦计算了

    2017-04-20 18:27:59回复

  • weixin_38626949

    回复qq_23947237: 你好,请问你的问题解决了吗,我也遇到了相同的问题。如果解决了,希望你能帮助我。

    2017-05-03 11:57:23回复

  • qq_23947237

    回复weixin_38626949: 我没有直接存到xml里面 你放到vector里面就没事了

    2017-05-03 17:31:07回复

  • qq_23947237

    回复weixin_38626949: 直接放到vector里面 不要存到xml里面再读就行

    2017-05-03 17:32:13回复

加载更多
我要留言×

技术领域:

我要留言×

留言成功,我们将在审核后加至投票列表中!

提示x

人工智能开发框架知识库已成功保存至我的图谱现在你可以用它来管理自己的知识内容了

删除图谱提示×

你保存在该图谱下的知识内容也会被删除,建议你先将内容移到其他图谱中。你确定要删除知识图谱及其内容吗?

删除节点提示×

无法删除该知识节点,因该节点下仍保存有相关知识内容!

删除节点提示×

你确定要删除该知识节点吗?