完整人脸识别系统(源码+教程+环境):
开源毕业设计:基于嵌入式ARM-Linux的应用OpenCV和QT实现的人脸识别系统(源码+论文)
完全毕设教程:Linux上Opencv与Qt实现的人脸识别的考勤点名/门禁系统(PC与嵌入式ARM版本)
在人脸识别之一,已经做好人脸库了。
在这一篇,进行人脸识别模型的训练。
一、数据准备即生成csv文件
有了人脸库数据,我们需要在程序中读取它,这里需借助csv文件去读取人脸库中的图像数据。
一个csv文件格式:图片路径名+标签,如/path/to/image.jpg;1
假设人脸图像路径:/path/to/image.jpg
我们给这个人脸图像一个标签“1”,这个标签代表这个人的名字,同一个人的人脸图像标签须相同。
你可以手动创建文件并一个一个去敲:
/mnt/hgfs/share_ubuntu/BS-Project/creat_csv/faces/s1/1.jpg;1
/mnt/hgfs/share_ubuntu/BS-Project/creat_csv/faces/s1/2.jpg;1
/mnt/hgfs/share_ubuntu/BS-Project/creat_csv/faces/s1/3.jpg;1
/mnt/hgfs/share_ubuntu/BS-Project/creat_csv/faces/s1/4.jpg;1
/mnt/hgfs/share_ubuntu/BS-Project/creat_csv/faces/s1/5.jpg;1
/mnt/hgfs/share_ubuntu/BS-Project/creat_csv/faces/s1/6.jpg;1
/mnt/hgfs/share_ubuntu/BS-Project/creat_csv/faces/s1/7.jpg;1
/mnt/hgfs/share_ubuntu/BS-Project/creat_csv/faces/s1/8.jpg;1
/mnt/hgfs/share_ubuntu/BS-Project/creat_csv/faces/s1/9.jpg;1
/mnt/hgfs/share_ubuntu/BS-Project/creat_csv/faces/s1/10.jpg;1
/mnt/hgfs/share_ubuntu/BS-Project/creat_csv/faces/s2/1.jpg;2
/mnt/hgfs/share_ubuntu/BS-Project/creat_csv/faces/s2/2.jpg;2
/mnt/hgfs/share_ubuntu/BS-Project/creat_csv/faces/s2/3.jpg;2
...
为了方便,我写了个 Linux-C 版本的创建csv文件的小程序,
代码:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <dirent.h>
#include <fcntl.h>
#include <unistd.h>
int Create_CSV(char *dir_path);
int main(int argc,char* argv[])
{
if(argc != 2)
{
printf("usage: %s <path>\n", argv[0]);
return -1;
}
Create_CSV(argv[1]);
return 0;
}
/*
创建csv文件,参数 dir_path:目录路径(绝对路径)
仅针对两级目录,如: picture/s1,picture为第1级,s1为第2级
应在s1下放置图片文件
备注:
为方便起见,2级目录命名规则:
字符标识1位 + 数字编号(唯一)
如 s1 s2 x3 a4 d5 ...
数字编号代表特定的个体,不可重复
当然亦可取其他,标签会从文件夹名的第2字符开始取至末尾
*/
int Create_CSV(char *dir_path)
{
struct stat statbuf;
DIR *dir;
DIR *dirFile;
struct dirent *dirp;
struct dirent *direntFile;
char dir_path2[64] = {0};
char fileName[128] = {0};
char witeBuf[128] = {0};
int fd;
if(dir_path[strlen(dir_path)-1] == '/') // 统一输入不以'/'结束,如 "/mnt/" 改为 "/mnt"
dir_path[strlen(dir_path)-1] = 0;
// 获取文件属性
if(lstat(dir_path, &statbuf) < 0)
{
printf("lstat(%s) failed !\n", dir_path);
return -1;
}
// 判断是否为目录
if(S_ISDIR(statbuf.st_mode) != 1)
{
printf("%s is not dir !\n", dir_path);
return -1;
}
// 打开目录
dir = opendir(dir_path);
if( dir ==NULL)
{
printf("opendir failed.\n");
return -1;
}
// 创建或打开csv文件
fd = open("orl_faces.csv", O_RDWR | O_CREAT, 0777);
if(fd < 0)
{
printf("open file faile.\n");
return -1;
}
// 定位读写位置
lseek(fd, 0, SEEK_SET);
// 遍历一级目录
while((dirp = readdir(dir)) != NULL)
{
// 忽略 '.' '..'文件(linux)
if( strncmp(dirp->d_name, ".", strlen(dirp->d_name))==0 ||
strncmp(dirp->d_name, "..", strlen(dirp->d_name))==0 )
continue;
// 将1级目录与2级目录组合
memset(dir_path2, 0, sizeof(dir_path2));
strcat(dir_path2, dir_path);
strcat(dir_path2, "/");
strcat(dir_path2, dirp->d_name);
if(lstat(dir_path2, &statbuf) < 0)
{
printf("lstat(%s) failed !\n", dir_path2);
continue;
}
if(S_ISDIR(statbuf.st_mode) != 1)
{
printf("%s is not dir !\n", dir_path2);
continue;
}
dirFile = opendir(dir_path2);
if( dirFile ==NULL)
{
printf("opendir failed.\n");
return -1;
}
// 遍历二级目录
while((direntFile = readdir(dirFile)) != NULL)
{
if( strncmp(direntFile->d_name, ".", strlen(direntFile->d_name))==0 ||
strncmp(direntFile->d_name, "..", strlen(direntFile->d_name))==0 )
continue;
// 获取完整文件路径名
memset(fileName, 0, sizeof(fileName));
strcat(fileName, dir_path2);
strcat(fileName, "/");
strcat(fileName, direntFile->d_name);
if(lstat(fileName, &statbuf) < 0)
{
printf("lstat(%s) failed !\n", fileName);
continue;
}
if(S_ISREG(statbuf.st_mode) != 1) // 不是普通文件
{
printf("%s is not reg file !\n", fileName);
continue;
}
memset(witeBuf, 0, sizeof(witeBuf));
memcpy(witeBuf, fileName, strlen(fileName));
strcat(witeBuf, ";");
strcat(witeBuf, dirp->d_name+1); // 标签: 从文件夹名的第2个字符开始
strcat(witeBuf, "\n");
printf("%s", witeBuf);
// 写入信息
write(fd, witeBuf, strlen(witeBuf));
}
}
closedir(dirFile);
closedir(dir);
close(fd);
return 0;
}
执行参数:人脸库的一级目录路径(绝对路径),如:./a.out /mnt/hgfs/share_ubuntu/BS-Project/creat_csv/orl_faces
执行结果: 可见当前目录下多了一个 faces.csv 文件,其内容如下(未显示完整):
至此,创建 csv 文件成功!
二、人脸识别模型训练
首先,官方示例:
https://docs.opencv.org/3.2.0/da/d60/tutorial_face_main.html
官方给出三个方法:特征脸EigenFace、LDA线性差别分析Fisherface、局部二值模式直方图LBPH
能力有限,暂不详解。
结合三个示例,综合成一个含有三种方法的程序:
#include "opencv2/core.hpp"
#include "opencv2/face.hpp"
#include "opencv2/highgui.hpp"
#include "opencv2/imgproc.hpp"
#include <iostream>
#include <fstream>
#include <sstream>
using namespace cv;
using namespace cv::face;
using namespace std;
static Mat norm_0_255(InputArray _src) {
Mat src = _src.getMat();
// 创建和返回一个归一化后的图像矩阵:
Mat dst;
switch(src.channels()) {
case 1:
cv::normalize(_src, dst, 0, 255, NORM_MINMAX, CV_8UC1);
break;
case 3:
cv::normalize(_src, dst, 0, 255, NORM_MINMAX, CV_8UC3);
break;
default:
src.copyTo(dst);
break;
}
return dst;
}
//使用CSV文件去读图像和标签,主要使用stringstream和getline方法
static void read_csv(const string& filename, vector<Mat>& images, vector<int>& labels, char separator = ';') {
std::ifstream file(filename.c_str(), ifstream::in);
if (!file) {
string error_message = "No valid input file was given, please check the given filename.";
CV_Error(Error::StsBadArg, error_message);
}
string line, path, classlabel;
while (getline(file, line)) {
stringstream liness(line);
getline(liness, path, separator);
getline(liness, classlabel);
if(!path.empty() && !classlabel.empty()) {
images.push_back(imread(path, 0));
labels.push_back(atoi(classlabel.c_str()));
}
}
}
int main(int argc, const char *argv[])
{
if(argc != 2)
{
printf("usage: %s <csv_file>\n", argv[0]);
return -1;
}
//读取你的CSV文件路径.
string fn_csv = string(argv[1]);
// string fn_csv = "at.csv";
// 2个容器来存放图像数据和对应的标签
vector<Mat> images;
vector<int> labels;
// 读取数据. 如果文件不合法就会出错
// 输入的文件名已经有了.
try
{
read_csv(fn_csv, images, labels);
}
catch (cv::Exception& e)
{
cerr << "Error opening file \"" << fn_csv << "\". Reason: " << e.msg << endl;
// 文件有问题,我们啥也做不了了,退出了
exit(1);
}
// 如果没有读取到足够图片,也退出.
if (images.size() <= 1) {
string error_message = "This demo needs at least 2 images to work. Please add more images to your data set!";
CV_Error(CV_StsError, error_message);
}
// 下面的几行代码仅仅是从你的数据集中移除最后一张图片
//[gm:自然这里需要根据自己的需要修改,他这里简化了很多问题]
Mat testSample = images[images.size() - 1];
int testLabel = labels[labels.size() - 1];
images.pop_back();
labels.pop_back();
// 下面几行创建了一个特征脸模型用于人脸识别,
// 通过CSV文件读取的图像和标签训练它。
// T这里是一个完整的PCA变换
//如果你只想保留10个主成分,使用如下代码
// cv::createEigenFaceRecognizer(10);
// cv::createFisherFaceRecognizer(10);
//
// 如果你还希望使用置信度阈值来初始化,使用以下语句:
// cv::createEigenFaceRecognizer(10, 123.0);
//
// 如果你使用所有特征并且使用一个阈值,使用以下语句:
// cv::createEigenFaceRecognizer(0, 123.0);
// cv::createFisherFaceRecognizer(0, 123.0);
Ptr<BasicFaceRecognizer> model0 = createEigenFaceRecognizer();
model0->train(images, labels);
model0->save("MyFacePCAModel.xml");
Ptr<BasicFaceRecognizer> model1 = createFisherFaceRecognizer();
model1->train(images, labels);
model1->save("MyFaceFisherModel.xml");
Ptr<LBPHFaceRecognizer> model2 = createLBPHFaceRecognizer();
model2->train(images, labels);
model2->save("MyFaceLBPHModel.xml");
// 下面对测试图像进行预测,predictedLabel是预测标签结果
int predictedLabel0 = model0->predict(testSample);
int predictedLabel1 = model1->predict(testSample);
int predictedLabel2 = model2->predict(testSample);
// 还有一种调用方式,可以获取结果同时得到阈值:
// int predictedLabel = -1;
// double confidence = 0.0;
// model->predict(testSample, predictedLabel, confidence);
string result_message0 = format("Predicted class = %d / Actual class = %d.", predictedLabel0, testLabel);
string result_message1 = format("Predicted class = %d / Actual class = %d.", predictedLabel1, testLabel);
string result_message2 = format("Predicted class = %d / Actual class = %d.", predictedLabel2, testLabel);
cout << result_message0 << endl;
cout << result_message1 << endl;
cout << result_message2 << endl;
waitKey(0);
return 0;
}
编译、运行:(备注:faces.csv是第一步得到的csv文件)
$ cmake .
$ make
$ ./train faces.csv
输出: 可看到,检测最后一张人脸图,识别的都准确。
root@qihua-virtual-machine:/mnt/hgfs/Project/opencvPro/train# ./train faces.csv
Predicted class = 4 / Actual class = 4.
Predicted class = 4 / Actual class = 4.
Predicted class = 4 / Actual class = 4.
同时,可见到当前目录下多了3个文件:
MyFaceFisherModel.xml、MyFaceLBPHModel.xml、MyFacePCAModel.xml
这几个就是用不同方法训练的人脸模型了。
留着后续用到。
人脸模型训练至此!