基於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中就是存放的數據集,其中的圖像都是未經處理過的含有人臉的圖片。

目錄結構
DataBase文件夾如下圖:
DataBase

1.2 數據讀取

	本文采用讀取TXT文件的方式,依次讀入所需圖片數據:

PathForTrain.txt包含了訓練集的路徑:
在這裏插入圖片描述
PathForTest.txt包含了測試機的路徑:
在這裏插入圖片描述

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;

在這裏插入圖片描述

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 <stdio.h>  
#include <time.h>  
#include <math.h>  
#include <opencv2/opencv.hpp>  
#include <opencv/cv.h>  
#include <iostream> 
#include <opencv2/core/core.hpp>  
#include <opencv2/highgui/highgui.hpp>  
#include <opencv2/ml/ml.hpp>  
#include <io.h>
#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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章