基于openCV的PCA+SVM的人脸识别程序

利用openCV的PCA+SVM实现人脸识别,即用PCA对数据集进行降维,保留主要的成分,然后送至SVM进行训练,对于当训练数据特别庞大时,可以达到缩小SVM模型提升分类速度的目的。
欢迎一起学习交流!
软件开发平台:visual studio 2013
opencv version:3.3.0
请注意上面两个的版本要匹配才行,opencv3.3.0有VC12的lib库,可以在网上查一下visual studio和VC的对应版本,这里visual studio 2013对应的就是VC12

目录

  1. 准备和读取数据
  2. 数据前处理
  3. 数据分类
  4. PCA降维
  5. SVM分类
  6. 预测
  7. 关于非训练类别预测结果随机的处理办法
  8. 参考链接

1. 准备和读取数据

1.1 目录介绍

	首先给大家看一下我的VS目录结构,DataBase中就是存放的数据集,其中的图像都是未经处理过的含有人脸的图片。

基于openCV的PCA+SVM的人脸识别程序_第1张图片
DataBase文件夹如下图:
基于openCV的PCA+SVM的人脸识别程序_第2张图片

1.2 数据读取

	本文采用读取TXT文件的方式,依次读入所需图片数据:

PathForTrain.txt包含了训练集的路径:
基于openCV的PCA+SVM的人脸识别程序_第3张图片
PathForTest.txt包含了测试机的路径:
基于openCV的PCA+SVM的人脸识别程序_第4张图片

1.3 图像读取程序

	首先实现一个根据TXT文件路径读取所以图片的函数:
	 vector<vector<String> > getImgPath(string s,int& num){
	ifstream path(s);
	vector<string> ipath;//多少个文件夹
	string buf;
	while (path){
		if (getline(path, buf)){
			ipath.push_back(buf);//图像所在的文件夹      
		}
	}
	path.close();
	vector<vector<String> > allpath;//所有文件夹下的图片(*.jpg)文件的路径
	for (size_t i = 0; i < ipath.size(); i++){
		string pattern_jpg = ipath[i];
		vector<String> files;
		cv::glob(pattern_jpg, files);
		if (files.size() == 0) {
			std::cout << "No image files[jpg]" << std::endl;
		}
		num += files.size();//每个文件夹下的图片数量之和
		allpath.push_back(files);
	}
	return allpath;
}
	然后调用这个函数,举个例子:
	//1. 通过path.txt确定需要读取的图片文件夹和类别
	vector<vector<String> > imgByDir;//按文件夹存储图片路径的向量,每个文件夹代表不同的label
	int	nImgNum = 0; //nImgNum是样本数量 
	imgByDir = getImgPath("PathForTrain.txt", nImgNum);
	cout << "共有样本个数为:" << nImgNum << 

2. 数据前处理

	本文的目的是实现人脸识别,所以首先需要进行人脸检测,把人脸数据送去降维和分类,这里用opencv自带的人脸检测模型,这里分两步:
	第一步加载人脸检测分类器
	//0. 加载人脸检测模型
	String face_cascade_name = "src/haarcascade_frontalface_alt.xml";
	CascadeClassifier face_cascade;
	if (!face_cascade.load(face_cascade_name)){ printf("--(!)Error loading\n"); return -1; };
	第二步对人脸区域图像裁剪
Mat getFaceImg(Mat src, CascadeClassifier cascade){
	//人脸检测
	std::vector<Rect> faces;
	Mat face = src;
	//equalizeHist(src,src);//直方图均衡化
	cascade.detectMultiScale(src, faces, 1.1, 2, 0 | CASCADE_SCALE_IMAGE, Size(30, 30));
	//裁剪出人脸
	for (size_t f = 0; f < faces.size(); f++){
		if (faces[f].width > 90){
			face = src(faces[f]);
			break;
		}
	}
	//统一图片大小
	Mat resized;
	resize(face, resized, Size(Width, Height));
	normalize(resized, resized, 0, 255, NORM_MINMAX);//归一化
	return resized;
}
//2. 加载文件夹下的图片并进行前期图像处理和特征提取
	//data_mat为所有训练样本的特征向量组成的矩阵,行数等于所有样本的个数,列数等于训练图片的维数
	Mat data_mat = Mat(nImgNum, Height*Width, CV_8UC1); 
	//labels_mat为训练样本的类别向量,行等于样本个数
	Mat labels_mat = Mat(nImgNum, 1, CV_32SC1);
	int index = 0;//data_mat、labels_mat的下标
	for (unsigned int i = 0; i < imgByDir.size(); i++){//遍历每个文件夹下的jpg文件,imgByDir.size()就是样本的类别,一个文件夹下放相同标签的照片
		for (size_t j = 0; j < imgByDir[i].size(); j++){
			//读取灰度图片
			Mat image0 = cv::imread(imgByDir[i][j], 0);
			if (image0.empty()){
				cout << " can not load the image: " << imgByDir[i][j].c_str() << endl;
				continue;
			}
			Mat faceImg = getFaceImg(image0, face_cascade);
			//imshow("face", faceImg);
			//waitKey(1);
			Mat reshaped = Mat(1, Height*Width, CV_32SC1);
			reshaped = faceImg.reshape(0, 1);//转换成一行N列的矩阵
			reshaped.row(0).copyTo(data_mat.row(index));
			labels_mat.at<int>(index, 0) = i -1;
			index++;//更新data_mat、labels_mat的行号索引
		}
	}

3. 数据分类

	我这里比较懒,直接用文件夹分类:
vector<vector<String> > imgByDir;//按文件夹存储图片路径的向量,每个文件夹代表不同的label
......
labels_mat.at<int>(index, 0) = i -1;

基于openCV的PCA+SVM的人脸识别程序_第5张图片

4. PCA降维

	PCA的原理可以参考这一篇文章:

PCA的数学原理

	opencv的pca实现代码:
	//3. PCA降维:1. PCA之前先做归一化处理 2.PCA模型可以保存 
	int K = nImgNum*0.5;//PCA主成分维数,需要小于样本数,大于样本数时等于样本数
	PCA pca(data_mat, Mat(), PCA::DATA_AS_ROW, K);//
	//TODO:把PCA模型保存
	FileStorage fs("PCA.xml", FileStorage::WRITE);
	pca.write(fs);
	fs.release(); // flush

	Mat projectedMat = pca.project(data_mat);//映射 降维,降维后的矩阵传给SVM训练
	//Mat back = pca.backProject(projectedMat);//从K维矩阵反映射到原来的维数

5. SVM分类

	//4. 创建分类器并设置参数
	Ptr<SVM> SVM_params = SVM::create();
	SVM_params->setType(SVM::C_SVC);
	SVM_params->setKernel(SVM::LINEAR);//核函数
	//SVM_params->setDegree(10.0);
	//SVM_params->setGamma(0.09);
	//SVM_params->setCoef0(1.0);
	SVM_params->setC(2.0);//惩罚系数,不能太小:欠拟合,不能太多:过拟合,
	//SVM_params->setNu(0.5);
	//SVM_params->setP(1.0);
	SVM_params->setTermCriteria(TermCriteria(TermCriteria::MAX_ITER + TermCriteria::EPS, 1000, 0.01));
	// 训练分类器
	SVM_params->train(projectedMat, ROW_SAMPLE, labels_mat);
	// 保存模型
	SVM_params->save("PCA_SVM.xml");

6. 预测

	在上面的代码中直接预测:
//5. 模型预测     
	char result[512];
	vector<vector<String> > img_tst_path;
	int testNum = 0;//测试样本数量
	int wrongNum = 0;//预测错误数量 计算准确率
	img_tst_path = getImgPath("PathForTest.txt", testNum);
	cout << "共有测试样本个数为:" << testNum << endl;
	ofstream predict_txt("SVM_PREDICT.txt");//把预测结果存储在这个文本中     
	for (string::size_type j = 0; j != img_tst_path.size(); j++){
		for (size_t i = 0; i < img_tst_path[j].size(); i++)
		{
		    Mat	img = imread(img_tst_path[j][i].c_str(), 0);
			if (img.empty()){
				cout << " can not load the image: " << img_tst_path[j][i].c_str() << endl;
				continue;
			}
			Mat faceImg = getFaceImg(img, face_cascade);
			//imshow("测试图片", faceImg);
			//waitKey(1);
			Mat test = Mat(1, K, CV_32FC1);
			Mat test1 = faceImg.reshape(0, 1);
			pca.project(test1, test);
			int ret = SVM_params->predict(test);//检测结果
			if (ret != j-1)wrongNum++;//我这里训练和测试的文件顺序是一样的,第j个文件夹就代表它的类别是j,可以依据此来判断是不是预测正确
			sprintf_s(result, "%s  %d\r", img_tst_path[j][i].c_str(), ret);
			predict_txt << result;  //输出检测结果到文本 
		}
	}
	float accuracyRate = (float)(testNum - wrongNum) / (float)testNum;
	cout << "预测正确率:" << accuracyRate << endl;
	predict_txt.close();
	system("pause");
	从xml文件中加载模型预测并从摄像头实时识别:
#include   
#include   
#include   
#include   
#include   
#include  
#include   
#include   
#include   
#include 
#include "windows.h"  
#include "fstream"  

using namespace std;
using namespace cv;
using namespace cv::ml;
#define Height 88
#define Width  88

Mat getFaceImg(Mat src, CascadeClassifier cascade, Rect& face){
	//人脸检测
	Mat faceImg = src;
	vector<Rect> faces;
	//equalizeHist(src,src);//直方图均衡化
	cascade.detectMultiScale(src, faces, 1.1, 2, 0 | CASCADE_SCALE_IMAGE, Size(30, 30));
	//裁剪出人脸
	for (size_t f = 0; f < faces.size(); f++){
		if (faces[f].width > 90){
			faceImg = src(faces[f]);
			face = faces[f];
			break;
		}
	}
	//统一图片大小
	Mat resized;
	resize(faceImg, resized, Size(Width, Height));
	normalize(resized, resized, 0, 255, NORM_MINMAX);//归一化
	return resized;
}
int main()
{
	//【0】加载PCA、SVM、人脸检测xml
	String face_cascade_name = "src/haarcascade_frontalface_alt.xml";
	CascadeClassifier face_cascade;
	if (!face_cascade.load(face_cascade_name)){ printf("--(!)Error loading\n"); return -1; };
	cout << "load  src/haarcascade_frontalface_alt.xml pass!" << std::endl;
	Ptr<ml::SVM>svm = ml::SVM::load("src/PCA_SVM.xml");//加载训练好的xml文件,
	cout << "load  src/PCA_SVM.xml pass!" << std::endl;
	PCA pca;
	FileStorage fs("src/PCA.xml", FileStorage::READ);
	pca.read(fs.root());
	fs.release();
	cout << "load  src/PCA.xml pass!" << std::endl;

	//【1】从摄像头读入视频
	VideoCapture capture(0);
	//【2】循环显示每一帧
	cout << "press C to exit " << std::endl;
	char name[100];
	while (1)
	{
		Mat frame;  //定义一个Mat变量,用于存储每一帧的图像
		capture >> frame;  //读取当前帧
		Mat gray;
		cvtColor(frame,gray,CV_RGB2GRAY);
		Rect faces;
		Mat face = getFaceImg(gray, face_cascade, faces);
		Mat reshaped = face.reshape(0, 1);//转换成一行N列的矩阵
		Mat project = pca.project(reshaped);
		int ret = svm->predict(project);
		cout << "The predict result is :  " << ret << endl;

			Point center(faces.x + faces.width / 2, faces.y + faces.height / 2);
			ellipse(frame, center, Size(faces.width / 2, faces.height / 2 + 16), 0, 0, 360, Scalar(200, 10, 10), 2, 8, 0);
			Point org(faces.x+10,faces.y-35 );
			sprintf_s(name, "person:%d", ret);
			putText(frame, name, org, FONT_HERSHEY_SIMPLEX, 0.8,Scalar(10, 10, 200),2);

		imshow("读取视频", frame);  //显示当前帧
		int c = waitKey(1);
		if ((char)c == 'c') { break; }
	}
	return 0;
}

7. 关于非训练类别预测结果随机的处理办法

	当输入一个没有经过训练的类别去预测时,svm始终会返回一个预测值,所以当我们想实现输入一张陌生人的脸时,识别为-1,就需要训练一个负样本类别。本文将负样本数据加入分类训练后输入一张陌生人的脸时,能正确识别为-1,但是还存在问题,有可能是我的负样本数据的问题

8. 参考链接

  1. 东城青年:基于PCA和SVM的人脸识别
  2. 迈克老狼2012:OpenCV学习(35) OpenCV中的PCA算法
  3. 朱铭德:PCA降维(Opencv,C++)
  4. 张洋:PCA的数学原理

作者:yymbyc 于 2020/03/03

你可能感兴趣的:(OPENCV,opencv,pca降维,svm,人脸识别)