基于deep-learning的人脸识别系统

写在前面

由于教研室项目要求,整个系统构造如下。即利用NPD检测人脸,经过去重过滤操作后,上传至服务器,随后在接收端保留接收图片,进行仿射变换预处理,随后导入训练好的神经网络,提取特征完成人脸识别。

image.png

本篇主要涉及内容:

1.ubuntu下安装opencv及相关配置

1.服务器接收图片压入队列

2.新线程读取队列,完成人脸识别操作

3.测试函数,模拟接收图片。即,windows与linux下扫描文件夹。

安装opencv

主要参考教程ubuntu16.04安装opencv3.4.1教程

编译cpp

利用g++或gcc编译程序时,需调用opencv依赖项。

1.找到lib存储路径,默认路径为/usr/local/lib/pkgconfig

image.png

2.加入环境变量

1
user@ubuntu:~$ gedit .bashrc

将上述路径加入最底部。

image.png

3.编译程序命令

1
user@ubuntu:~$ g++ -o test test.cpp `pkg-config --cflags --libs opencv`

扫描文件夹

windows

需要包含库文件#include<io.h>。主要用到结构体_finddata_t,函数_findfirst(), _findnext(), _findclose()若文件夹为动态,需不停遍历。

1.结构体_finddata_t可以用来获取文件各种信息。

1
2
3
4
5
6
7
8
struct _finddata_t{
unsigned attrib; //文件属性
time_t time_create; //创建时间
time_t time_access; //最后一次访问时间
time_t time_write; //最后一次修改时间
_fsize_t size; //文件大小
char name[260]; //文件名
};

2._findfirst函数long _findfirst(const char *filename, struct _finddata_t *fileinfo);

参数:

  • filename,查找路径下文件名,可以用*.*查找所有文件,*.jpg查找所有.jpg文件。注意*将查找...
  • fileinfo,_finddata_t结构体指针,无须初始化若查找成功,将当前文件信息传入该结构体

输出:查找成功,返回文件句柄;失败,返回-1。

3._findnext函数int _findnext(long hfile, struct _finddata_t * fileinfo);

参数:

  • hfile,文件句柄。函数会依据当前句柄寻找下一个文件
  • fileinfo,_finddata_t结构体指针。若查找成功,将当前文件信息传入该结构体

输出:查找成功,返回0;失败,返回-1。

4._findclose()函数int _findclose(long);

只有一个参数,文件句柄。若关闭成功返回0,失败返回-1。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
#include<io.h>
#include<string>
#include<iostream>
using namespace std;
int main(){
string tarpath="G:\\pic\\positive"; //遍历文件夹
string p;//字符串,存放路径
long hFile = 0;//文件句柄
struct _finddata_t fileinfo;//文件信息,声明一个存储文件信息的结构体
//若查找成功,则进入
if ((hFile = _findfirst(p.assign(tarpath).append("\\*").c_str(), &fileinfo)) != -1){
do{
//如果是目录,迭代之(即文件夹内还有文件夹)
//cout << hFile << endl;
if ((fileinfo.attrib & _A_SUBDIR)){
//文件名不等于"."&&文件名不等于".."
//.表示当前目录
//..表示当前目录的父目录
//判断时,两者都要忽略,不然就无限递归跳不出去了!
if (strcmp(fileinfo.name, ".") != 0 && strcmp(fileinfo.name, "..") != 0)
getFiles(p.assign(path).append("\\").append(fileinfo.name), files);
}
else{
cout<<fileinfo.name<<endl;
}
}while((_findnext(hFile, &fileinfo) == 0));
——findclose(hFile);
}

linux平台

linux下文件没有创建时间的说法。其读取文件顺序与文件名无关,默认按照文件写在磁盘上的位置。主要用到结构体DIR, stat, dirent, 函数opendir, readdir, closedir

1.使用opendir打开目录a,返回指向目录的目录流dir

2.readdir(dir)读取目录流,更新下一个文件的目录流,将当前文件信息存于dirent结构体中

3.调用stat(d->name,stat *e) 或dirent结构体获取文件信息。

需要包含的库。

1
2
3
4
#include<unistd.h>
#include<sys/types.h>
#include<dirent.h>
#include<sys/stat.h>

1.结构体DIR

DIR结构体类似于FILE,是一个内部结构,主要用于opendir, readdir, closedir等函数。

2.结构体dirent,主要起着一个索引的作用。

1
2
3
4
5
6
7
8
struct dirent
{
  long d_ino; /* inode number 索引节点号 */
off_t d_off; /* offset to this dirent 在目录文件中的偏移,默认排序方式 */
unsigned short d_reclen; /* length of this d_name 文件名长 */
unsigned char d_type; /* the type of d_name 文件类型 */
char d_name [NAME_MAX+1]; /* file name (null-terminated) 文件名,最长255字符 */
}

3.结构体stat

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
struct stat {
mode_t st_mode; //文件访问权限
ino_t st_ino; //索引节点号
dev_t st_dev; //文件使用的设备号
dev_t st_rdev; //设备文件的设备号
nlink_t st_nlink; //文件的硬连接数
uid_t st_uid; //所有者用户识别号
gid_t st_gid; //组识别号
off_t st_size; //以字节为单位的文件容量
time_t st_atime; //最后一次访问该文件的时间
time_t st_mtime; //最后一次修改该文件的时间
time_t st_ctime; //最后一次改变该文件状态的时间
blksize_t st_blksize; //包含该文件的磁盘块的大小
blkcnt_t st_blocks; //该文件所占的磁盘块
};

4.opendir函数DIR * opendir(const char *name);

参数:

  • name,需要打开的文件路径

输出:成功,返回DIR*形态的目录流;失败,返回NULL

5.readdir函数struct dirent * readdir(DIR* dir);

参数:

  • dir,读取目录流

输出: 成功则返回下个目录进入点。有错误发生或读取到目录文件尾则返回NULL 。

6.closedir函数int closedir(DIR* dir);

参数:

  • dir,读取目录流

输出:关闭成功则返回0,失败返回-1,错误原因存于errno 中。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#include<unistd.h>
#include<sys/types.h>
#include<dirent.h>
#include<sys/stat.h>
using namespace std;

int main(){
const char* dir_name = "./sample"; //要读取文件路径
struct dirent * filename; // 存储文件信息结构体
DIR * dir; // 目录流
strtuct stat statbuf; //stat结构体,存储更详细文件信息
dir = opendir( dir_name );
if(dir==NULL){
cout<<"open dir error"<<endl;
}
while( ( filename = readdir(dir) ) != NULL ){
if( strcmp( filename->d_name , "." ) == 0 ||strcmp( filename->d_name , "..") == 0 )//判断"."与".."
continue;
string name=filename->d_name; //利用dirent结构体获取文件名
string path;
path.assign("./sample/").append(name); //当前文件访问路径
stat(filename,&statbuf); //利用dirent获取文件stat结构信息
}
closedir(dir);
return 0;
}

多线程编程

参考文档unix linux多线程编程pthread_create函数的详细讲解 C++ 多线程并发控制——互斥锁 pthread_mutex

linux下需调用依赖库#include<pthread>,该依赖项不是linux默认库。在编译时,需添加-lpthread参数,调用静态链接库

创建线程

1
2
3
4
int pthread_create(pthread_t *restrict thread,
const pthread_attr_t *restrict attr,
void*(*start_routine)(void*),
void *restrict arg);

参数thread指向保存线程ID的pthread_t结构。参数attr表示一个封装了线程属性的对象,用来配置线程的运行,如果为NULL,线程使用默认属性。参数start_routine是线程开始执行时调用的函数名。该函数必须具有以下格式,void* start_routine(void* arg);。参数arg表示传递给函数的参数。如果需要向start_routine函数传递的参数不止一个,那么需要把这些参数放到一个结构中,然后把这个结构的地址作为arg的参数传入。

互斥锁

为了防止不同线程同时操作任务队列,应在一个线程读写队列时,将队列上锁。

初始化

int pthread_mutex_init(pthread_mutex_t *mp, const pthread_mutexattr_t *mattr)

参数:

  • mp,互斥锁地址
  • mattr,锁属性,默认null。

如果互斥锁已初始化,则它会处于未锁定状态 。

锁定互斥锁

int pthread_mutex_lock(pthread_mutex_t *mutex);

参数:

  • mutex,互斥锁地址

函数返回时,互斥锁被锁定。调用线程是该互斥锁的属主。如果该互斥锁已被另一个线程锁定和拥有,则调用线程将阻塞,直到该互斥锁变为可用为止。

解除互斥锁

int pthread_mutex_unlock(pthread_mutex_t *mutex);

参数:

  • mutex,互斥锁地址

程序源码

说明:main函数模拟服务器接收数据,即每间隔1s读取一张图片,并送入队列。新建线程进行人脸匹配识别操作。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
////////////////////////////////////////////////////////////////////////  
// file_server.c --
// /////////////////////////////////////////////////////////////////////
#include<netinet/in.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<queue>
//thread
#include"pthread.h"
#include<unistd.h>//need by read() function

//opencv
#include <opencv2/dnn/dnn.hpp>
#include <opencv2/imgproc/imgproc.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <opencv2/core/utils/trace.hpp>
using namespace cv;
using namespace cv::dnn;
//test
#include<unistd.h>
#include<dirent.h>
#include<sys/stat.h>
#include<iostream>

#define SERVER_PORT 10001
#define LENGTH_OF_LISTEN_QUEUE 20
#define BUFFER_SIZE 96*1024
#define FILE_NAME_MAX_SIZE 512



//task to be transported to queue
struct CTask {
std::string m_name;
Mat m_data;
};
//lock
pthread_mutex_t mutex;

std::queue<CTask> QTask;


Mat extractFeature(const String &imgFile,Net &net) {
Mat img = imread(imgFile.c_str());
printf("\t\tread img file...\n");
if (img.empty())
{
printf("\t\tCan't read image from the file: %s\n" , imgFile.c_str() );

}
Mat inputBlob = blobFromImage(img, 1, Size(96, 96),
Scalar(127, 127, 127)); //Convert Mat to batch of images
printf("\t\tconvert inputBlob successfully\n");
CV_TRACE_REGION("forward");
net.setInput(inputBlob, "data"); //set the network input
Mat prob = net.forward("fc1");
printf("\t\tforward calculate successfully\n");
return prob;
}

void *queWork(void*arg) {
//******************initialize the deep-learning net work and extract prob feature**********
printf("thread create successfully!\tStart initialize dp network\n");
//std::queue<CTask> QTask = *(std::queue<CTask>*)arg;
CV_TRACE_FUNCTION();
String modelTxt = "ResNet11.prototxt";
String modelBin = "ResNet11.caffemodel";
String probfile = "./jpegFile/prob.jpg";
Net net = dnn::readNetFromCaffe(modelTxt, modelBin);
if (net.empty())
{
printf("\tCan't load network by using the following files: \nprototxt: %s\ncaffemodel: %s\n",modelTxt.c_str(),modelBin.c_str()) ;
}
printf("\tload net successfully! Start extract feature\n");
const Mat fprob = extractFeature(probfile, net).clone();
printf("Initialize dp network successfully!\n");
double threshold = 0.6750;
//******************************************************************************************
while (1) {
//std::cout<<"start pop"<<std::endl;
//std::cout<<"queue empty? "<<QTask.empty()<<std::endl;
//sleep(1);
if (!QTask.empty()) {
pthread_mutex_lock(&mutex);
CTask it=QTask.front();
printf("deal %s ", it.m_name.c_str());
std::cout<<" ["<<it.m_data.cols<<", "<<it.m_data.rows<<"]"<<std::endl;
QTask.pop();
pthread_mutex_unlock(&mutex);
int roww = it.m_data.cols/1.6;
int rowh = it.m_data.rows/2.2;


if (roww < 50 || rowh < 50) {
//printf("\timg too small, ignored\n");
//delete it;
//printf("\tdelete old prob");
continue;
}
int wstart = (it.m_data.rows - roww) / 2;
int hstart = (it.m_data.cols - rowh) / 2;
Mat cutdata = it.m_data(Range(wstart, wstart + roww), Range(hstart, hstart + rowh));
Mat inputdata;
resize(cutdata, inputdata, Size(96, 96));
Mat intputBlob = blobFromImage(inputdata, 1, Size(96, 96), Scalar(127, 127, 127));
//*********************************************************************************
//**************************extract feature, compute cosine dist and faceRec*******
CV_TRACE_REGION("forward");
net.setInput(intputBlob, "data");
Mat fcomp = net.forward("fc1");
double upstair = fcomp.dot(fprob);
double downstair1 = sqrt(fprob.dot(fprob));
double downstair2 = sqrt(fcomp.dot(fcomp));
double cosTest = 0.5 + 0.5*upstair / downstair1 / downstair2;
if (cosTest >= threshold) {
printf("\tcosine dist is %.4f", cosTest);
printf("\tsame face\n");
}
//delete it;
/*
CTask *it = &QTask.front();
printf("deal %s\n", it->m_name.c_str());
//***************************filter small img and resize to 96x96*******************
int roww = it->m_data.cols/1.6;
int rowh = it->m_data.rows/2.2;
if (roww < 50 || rowh < 50) {
printf("\timg too small, ignored\n");
QTask.pop();
pthread_mutex_unlock(&mutex);
delete it;
continue;
}
int wstart = (it->m_data.rows - roww) / 2;
int hstart = (it->m_data.cols - rowh) / 2;
Mat cutdata = it->m_data(Range(wstart, wstart + roww), Range(hstart, hstart + rowh));
Mat inputdata;
resize(cutdata, inputdata, Size(96, 96));
Mat intputBlob = blobFromImage(inputdata, 1, Size(96, 96), Scalar(127, 127, 127));
//*********************************************************************************
//**************************extract feature, compute cosine dist and faceRec*******
CV_TRACE_REGION("forward");
net.setInput(intputBlob, "data");
Mat fcomp = net.forward("fc1");
double upstair = fcomp.dot(fprob);
double downstair1 = sqrt(fprob.dot(fprob));
double downstair2 = sqrt(fcomp.dot(fcomp));
double cosTest = 0.5 + 0.5*upstair / downstair1 / downstair2;
printf("\tcosine dist is %.4f", cosTest);
if (cosTest >= threshold) {
printf("\tsame face");
}
QTask.pop();

delete it;
*/
}

}
}

int main(int argc, char **argv)
{
//********create a thread for faceRecognization**********
//new thread
pthread_t thread;
int isthread;
isthread = pthread_create(&thread, NULL, queWork, NULL);
if (isthread != 0) {
printf("create thread fail: %s\n", strerror(isthread));
exit(-1);
}
//********************************************************
pthread_mutex_init (&mutex,NULL);
using namespace std;
const char* dir_name = "./sample";
struct dirent * filename; // return value for readdir()
DIR * dir; // return value for opendir()
dir = opendir( dir_name );
if(dir==NULL){
cout<<"open dir error"<<endl;
}
while( ( filename = readdir(dir) ) != NULL ){
struct CTask task;

if( strcmp( filename->d_name , "." ) == 0 ||strcmp( filename->d_name , "..") == 0 )
continue;
string name=filename->d_name;
string path;
path.assign("./sample/").append(name);
//cout<<path<<endl;
task.m_name = name;
Mat data=imread(path.c_str());
if (data.empty())
{
printf("\t\tCan't read image from the file: %s\n" , path.c_str() );

}
task.m_data = data;
//cout<<data<<endl;
pthread_mutex_lock (&mutex);
QTask.push(task);
pthread_mutex_unlock(&mutex);
cout<<"push "<<name<<" ";
cout<<"["<<QTask.front().m_data.rows<<", "<<QTask.front().m_data.cols<<"]"<<endl;
sleep(1);
}
closedir(dir);
return 0;
}

本文标题:基于deep-learning的人脸识别系统

文章作者:Lumo Wang

发布时间:2018年05月06日 - 19:05

最后更新:2018年11月07日 - 15:11

原始链接:https://luameows.github.io/2018/05/06/程序开发-基于caffe的人脸识别系统/

许可协议: 署名-非商业性使用-禁止演绎 4.0 国际 转载请保留原文链接及作者。

请我喝杯咖啡,我会继续熬夜的~