人脸识别之二模型训练

在人脸识别之一,已经做好人脸库了。

在这一篇,进行人脸识别模型的训练。



一、数据准备即生成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

这几个就是用不同方法训练的人脸模型了。

留着后续用到。


人脸模型训练至此!


猜你喜欢

转载自blog.csdn.net/qq_30155503/article/details/79794120