1、Creating the CSV File
照片需要在程序中读取它,我决定使用CSV文件读取它。一个CSV文件包含文件名,紧跟一个标签。
/path/to/image.ext;0
假设/path/to/image.ext是图像,就像你在windows下的c:/faces/person0/image0.jpg。最后我们给它一个标签0。这个标签类似代表这个照片的名字,所以同一张的照片的标签都一样。
打开DOS命令即打开命令控制台,进入相对路径下去,然后进入储存照片的路径下去输入f:\opencv_example\pic>dir /b/s *.jpg >at.txt
即可生成at.txt文件
2、读取CSV文件函数
void read_csv(const string& filename, vector<Mat>& images, vector<int>& labels, char separator = ';')
{
std::ifstream file(filename.c_str(), ifstream::in);//c_str()函数可用可不用,无需返回一个标准C类型的字符串
if (!file)
{
string error_message = "No valid input file was given, please check the given filename.";
CV_Error(CV_StsBadArg, error_message);
}
string line, path, classlabel;
while (getline(file, line))//从文本文件中读取一行字符,未指定限定符默认限定符为“/n”
{
stringstream liness(line);//这里采用stringstream主要作用是做字符串的分割
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()));
}
}
}
3、开始训练模型
(1)创建一个图像容器和标签容器(createEigenFaceRecognizer()函数来创建分类器时是可以人为指定训练结果的维数以及判别阈值,这里我们采用系统默认的参数)来存储训练图像以及对应人脸标签,然后调用void read_csv()填充这两个容器;
(2)然后创建一个PCA人脸分类器,暂时命名为model吧,创建完成后,调用其中的成员函数train()(train()函数的执行时间与训练样本图片数目有关)来完成分类器的训练。
(3)训练得到的分类器model用save()函数保存成XML文件存储下来,下次用的时候直接用laod()加载就行,
#include<iostream>
#include <fstream>
#include <sstream>
#include <opencv2\opencv.hpp>
#include <opencv2\face.hpp>
#include <windows.h>
#include <direct.h>
using namespace cv;
using namespace std;
using namespace face;
CascadeClassifier face_cascades;
//读取CSV文件函数
void read_csv(const string& filename, vector<Mat>& images, vector<int>& labels, char separator = ';')
{
std::ifstream file(filename.c_str(), ifstream::in);//c_str()函数可用可不用,无需返回一个标准C类型的字符串
if (!file)
{
string error_message = "No valid input file was given, please check the given filename.";
CV_Error(CV_StsBadArg, error_message);
}
string line, path, classlabel;
while (getline(file, line))//从文本文件中读取一行字符,未指定限定符默认限定符为“/n”
{
stringstream liness(line);//这里采用stringstream主要作用是做字符串的分割
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()
{
char buffer[MAX_PATH];
_getcwd(buffer, MAX_PATH);
printf("The buffer is: %s ", buffer);
string fn_csv;
fn_csv = buffer;
fn_csv = fn_csv + "F:\\opencv_example\\pic\\at.txt";
//string fn_csv = "./pic/at.txt";//读取你的CSV文件路径. 相对路径读取失败linux
vector<Mat> images;// 2个容器来存放图像数据和对应的标签
vector<int> labels;
read_csv(fn_csv, images, labels);//从csv文件中批量读取训练数据
Ptr<FaceRecognizer> model = createEigenFaceRecognizer();
model->train(images, labels);
model->save("F:\\opencv_example\\pic\\MyFaceRecognizer.xml");//保存路径
return 0;
}
4、程序
还可以输出一些与训练集相关的结构,比如训练集的均值特征脸、重构特征脸等
//获得特征值,特征向量,均值 平均脸
Mat eigenvalues = model->getEigenValues();
Mat eigenvectors = model->getEigenVectors();
Mat mean = model->getMean();
Mat meanFace = mean.reshape(1, height);
Mat dst;
dst = normal(meanFace, dst);
imshow(“Mean Face”, dst);
//特征脸
for (int i = 0; i < min(10, eigenvectors.cols); i++)
{
Mat ev = eigenvectors.col(i).clone();
Mat eigenFace = ev.reshape(1, height);
Mat grayscale;
grayscale = normal(eigenFace, grayscale);
Mat colorface;
applyColorMap(grayscale, colorface, COLORMAP_BONE);
char* winTitle = new char[128];
sprintf(winTitle, "eigenface_%d", i);
imshow(winTitle, colorface);
}
//重建人脸
for (int num = min(10, eigenvectors.cols); num < min(300, eigenvectors.cols); num += 15)
{
Mat evs = Mat(eigenvectors, Range::all(), Range(0, num));
Mat projection = LDA::subspaceProject(evs, mean, image[0].reshape(1, 1));
Mat reconstruction = LDA::subspaceReconstruct(evs, mean, projection);
Mat result = reconstruction.reshape(1, height);
reconstruction = normal(result, reconstruction);
char* winTitle = new char[128];
sprintf(winTitle, "recon_face_%d", num);
imshow(winTitle, reconstruction);
}