前言
基於深度學習的人臉識別系統,一共用到了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);
}
程序顯示完全一致,表示正確:
=================================================================