【opencv3】 svm實現手寫體與人臉識別

Rebecca(swust)發表於2018-11-30

主要 內容 opencv3 除錯2分類svm 和多分類svm。
引數除錯參考地址
官方文件說明地址
實現內容:1 opencv +svm實現首先體二分類
2 opencv+svm實現人臉分類
3 opencv+pca+svm實現人臉識別

1.opencv3除錯二分類SVM

第5小點有完整程式參考。

<1>資料準備 區分手寫字型0,1

首先在專案資料夾下面建立兩個子資料夾,分別為train,test。train,test資料夾下面分別再建立子資料夾0,1。具體如圖所示:

在這裡插入圖片描述在這裡插入圖片描述
在這裡插入圖片描述在這裡插入圖片描述

<2> 給訓練集資料加上標籤

Mat& trainingImages, vector& trainingLabels:這裡採用了引用&,所以不清空前會一直累加變化

void getFiles(string path, vector<string>& files)//開啟資料夾訪問裡面所有內容
{
	long   hFile = 0;
	struct _finddata_t fileinfo;
	string p;
	if ((hFile = _findfirst(p.assign(path).append("\\*").c_str(), &fileinfo)) != -1)
	{
		do
		{
			if ((fileinfo.attrib &  _A_SUBDIR))
			{
				if (strcmp(fileinfo.name, ".") != 0 && strcmp(fileinfo.name, "..") != 0)
					getFiles(p.assign(path).append("\\").append(fileinfo.name), files);
			}
			else
			{
				files.push_back(p.assign(path).append("\\").append(fileinfo.name));
			}
		} while (_findnext(hFile, &fileinfo) == 0);

		_findclose(hFile);
	}
}
void get_1(Mat& trainingImages, vector<int>& trainingLabels)
{
	char * filePath = "train\\1";
	vector<string> files;
	getFiles(filePath, files);
	int number = files.size();
	for (int i = 0; i < number; i++)
	{
		Mat  SrcImage = imread(files[i].c_str());
		SrcImage = SrcImage.reshape(1, 1);
		trainingImages.push_back(SrcImage);
		trainingLabels.push_back(1);
	}
}
void get_0(Mat& trainingImages, vector<int>& trainingLabels)
{
	char * filePath = "train\\0";
	vector<string> files;
	getFiles(filePath, files);
	int number = files.size();
	for (int i = 0; i < number; i++)
	{
		Mat  SrcImage = imread(files[i].c_str());
		SrcImage = SrcImage.reshape(1, 1);
		trainingImages.push_back(SrcImage);
		trainingLabels.push_back(0);
	}
}

<2> SVM的建立與引數設定以及訓練

	cv::Ptr<cv::ml::SVM> svm = cv::ml::SVM::create();
	svm->setType(cv::ml::SVM::Types::C_SVC);
	svm->setKernel(cv::ml::SVM::KernelTypes::LINEAR);
	svm->setTermCriteria(cv::TermCriteria(cv::TermCriteria::MAX_ITER, 1000,0.01));
	// train operation
	svm->train(trainingData,SampleTypes::ROW_SAMPLE, trainingLabels);
	//儲存模型
	svm->save("svm.xml");

<3>測試 載入訓練模型 測試0

	string modelpath = "svm.xml";
	Ptr<ml::SVM> svm = Algorithm::load<ml::SVM>(modelpath);
	char * filePath = "test\\0";
	vector<string> files;
	getFiles(filePath, files);
	int number = files.size();
	for (int i = 0; i < number; i++)
	{
		Mat inMat = imread(files[i].c_str());
		Mat p = inMat.reshape(1, 1);
		p.convertTo(p, CV_32FC1);
		int response = (int)svm->predict(p);
		if (response == 0)//因為現在測試為0所以標籤為0 預測值出來為零
		{
			std::cout<<"預測為0 預測正確"<<endl;
		}
	}

<5>程式

digits檔案如下,在opencv庫中有!路徑為==/opencv/sources/samples/data/==
在這裡插入圖片描述
總共三部分:從digits中分割出所需訓練集與測試集;訓練;測試。

/
部分1  將一張巨大的圖片中的元素切割出來  digits.png
///
//#include <opencv2/opencv.hpp>
//#include <iostream>
//#include <ml.hpp>
//
//using namespace std;
//using namespace cv;
//
//int main()
//{
//	char ad[128] = { 0 };
//	int  filename = 0, filenum = 0;
//	Mat img = imread("digits.png");
//	Mat gray;
//	cvtColor(img, gray, CV_BGR2GRAY);
//	int b = 20;
//	int m = gray.rows / b;   //原圖為1000*2000
//	int n = gray.cols / b;   //裁剪為5000個20*20的小圖塊
//
//	for (int i = 0; i < m; i++)
//	{
//		int offsetRow = i*b;  //行上的偏移量
//		if (i % 5 == 0 && i != 0)
//		{
//			filename++;
//			filenum = 0;
//		}
//		for (int j = 0; j < n; j++)
//		{
//			int offsetCol = j*b; //列上的偏移量
//			sprintf_s(ad, "E:\\data\\%d\\%d.jpg", filename, filenum++);
//			//擷取20*20的小塊
//			Mat tmp;
//			gray(Range(offsetRow, offsetRow + b), Range(offsetCol, offsetCol + b)).copyTo(tmp);
//			imwrite(ad, tmp);
//		}
//	}
//	return 0;
//}
//
//
/
部分2  訓練
///
//#include <stdio.h>  
//#include <time.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 "opencv2/ml.hpp"
//using namespace std;
//using namespace cv;
//using namespace cv::ml;
//void getFiles(string path, vector<string>& files);
//void get_1(Mat& trainingImages, vector<int>& trainingLabels);
//void get_0(Mat& trainingImages, vector<int>& trainingLabels);
//
//int main()
//{
//	//獲取訓練資料
//	Mat classes;
//	Mat trainingData;
//	Mat trainingImages;
//	vector<int> trainingLabels;
//	get_1(trainingImages, trainingLabels);
//	get_0(trainingImages, trainingLabels);
//	Mat(trainingImages).copyTo(trainingData);
//	trainingData.convertTo(trainingData, CV_32FC1);
//	Mat(trainingLabels).copyTo(classes);
//	//配置SVM訓練器引數
//	// initial SVM
//	cv::Ptr<cv::ml::SVM> svm = cv::ml::SVM::create();
//	svm->setType(cv::ml::SVM::Types::C_SVC);
//	svm->setKernel(cv::ml::SVM::KernelTypes::LINEAR);
//	svm->setTermCriteria(cv::TermCriteria(cv::TermCriteria::MAX_ITER, 1000,0.01));
//
//
//	// train operation
//	svm->train(trainingData,SampleTypes::ROW_SAMPLE, trainingLabels);
//
//
//
//	//儲存模型
//	svm->save("svm.xml");
//	cout << "訓練好了!!!" << endl;
//	getchar();
//	return 0;
//}
//void getFiles(string path, vector<string>& files)
//{
//	long   hFile = 0;
//	struct _finddata_t fileinfo;
//	string p;
//	if ((hFile = _findfirst(p.assign(path).append("\\*").c_str(), &fileinfo)) != -1)
//	{
//		do
//		{
//			if ((fileinfo.attrib &  _A_SUBDIR))
//			{
//				if (strcmp(fileinfo.name, ".") != 0 && strcmp(fileinfo.name, "..") != 0)
//					getFiles(p.assign(path).append("\\").append(fileinfo.name), files);
//			}
//			else
//			{
//				files.push_back(p.assign(path).append("\\").append(fileinfo.name));
//			}
//		} while (_findnext(hFile, &fileinfo) == 0);
//
//		_findclose(hFile);
//	}
//}
//void get_1(Mat& trainingImages, vector<int>& trainingLabels)
//{
//	char * filePath = "testdata\\train\\1";
//	vector<string> files;
//	getFiles(filePath, files);
//	int number = files.size();
//	for (int i = 0; i < number; i++)
//	{
//		Mat  SrcImage = imread(files[i].c_str());
//		SrcImage = SrcImage.reshape(1, 1);
//		trainingImages.push_back(SrcImage);
//		trainingLabels.push_back(1);
//	}
//}
//void get_0(Mat& trainingImages, vector<int>& trainingLabels)
//{
//	char * filePath = "testdata\\train\\0";
//	vector<string> files;
//	getFiles(filePath, files);
//	int number = files.size();
//	for (int i = 0; i < number; i++)
//	{
//		Mat  SrcImage = imread(files[i].c_str());
//		SrcImage = SrcImage.reshape(1, 1);
//		trainingImages.push_back(SrcImage);
//		trainingLabels.push_back(0);
//	}
//}

/
部分3  測試
///

#include <stdio.h>  
#include <time.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>

using namespace std;
using namespace cv;
using namespace ml;

void getFiles(string path, vector<string>& files);

int main()
{
	int result = 0;
	char * filePath = "testdata\\test\\0";
	vector<string> files;
	getFiles(filePath, files);
	int number = files.size();
	cout << number << endl;
	//cv::Ptr<cv::ml::SVM> svm = cv::ml::SVM::create();
	//svm->clear();
	string modelpath = "svm.xml";
	Ptr<ml::SVM> svm = Algorithm::load<ml::SVM>(modelpath);
	//FileStorage svm_fs(modelpath, FileStorage::READ);
	//if (svm_fs.isOpened())
	//{
		svm->load(modelpath.c_str());
	//	Ptr<ml::SVM> svm = Algorithm::load<ml::SVM>("svm.xml");
	//}
	for (int i = 0; i < number; i++)
	{
		Mat inMat = imread(files[i].c_str());
		Mat p = inMat.reshape(1, 1);
		p.convertTo(p, CV_32FC1);
		int response = (int)svm->predict(p);
		if (response == 0)
		{
			result++;
		}
	}
	cout << result << endl;
	getchar();
	return  0;
}
void getFiles(string path, vector<string>& files)
{
	long   hFile = 0;
	struct _finddata_t fileinfo;
	string p;
	if ((hFile = _findfirst(p.assign(path).append("\\*").c_str(), &fileinfo)) != -1)
	{
		do
		{
			if ((fileinfo.attrib &  _A_SUBDIR))
			{
				if (strcmp(fileinfo.name, ".") != 0 && strcmp(fileinfo.name, "..") != 0)
					getFiles(p.assign(path).append("\\").append(fileinfo.name), files);
			}
			else
			{
				files.push_back(p.assign(path).append("\\").append(fileinfo.name));
			}
		} while (_findnext(hFile, &fileinfo) == 0);
		_findclose(hFile);
	}
}

2 多分類svm 人臉識別

因為一個svm模型只能分為兩類,當有N個人作區分時,產生N個模型即可。
opencv參考地址https://docs.opencv.org/3.3.0/d1/d2d/classcv_1_1ml_1_1SVM.html#aad7f1aaccced3c33bb256640910a0e56 我發現svm中(C_SVM)支援多分類 所以只需要類別標籤分為貼為1~15,predict出來就是1-15。
在這裡插入圖片描述

修改的程式如下,只是在標籤貼的時候不同!!!!
檢測標準, cout << response;輸出為1,就是第一個人;輸出為2,就是第二個人!
資料格式:
訓練集:
在這裡插入圖片描述
在這裡插入圖片描述
測試集:
在這裡插入圖片描述
在這裡插入圖片描述

程式直接可用如下:

#include <stdio.h>  
#include <time.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 <vector>
#include <string>
#include <fstream>
using namespace std;
using namespace cv;
using namespace cv::ml;
//宣告
void getFiles(string path, vector<string>& files);
void getBubble(Mat& trainingImages, vector<int>& trainingLabels, int num, int count);//,int num
void train2modle(int classesNum);
void Test_result(int classesNum);


void getFiles(string path, vector<string>& files)
{
	intptr_t   hFile = 0;
	struct _finddata_t fileinfo;
	string p;
	if ((hFile = _findfirst(p.assign(path).append("\\*").c_str(), &fileinfo)) != -1)
	{

		do
		{
			if ((fileinfo.attrib &  _A_SUBDIR))
			{
				if (strcmp(fileinfo.name, ".") != 0 && strcmp(fileinfo.name, "..") != 0)
					getFiles(p.assign(path).append("\\").append(fileinfo.name), files);
			}
			else
			{
				files.push_back(p.assign(path).append("\\").append(fileinfo.name));
			}

		} while (_findnext(hFile, &fileinfo) == 0);

		_findclose(hFile);
	}
}
//獲取訓練樣本本
void getBubble(Mat& trainingImages, vector<int>& trainingLabels, int num, int count)//count第幾個訓練器  num第幾個資料夾
{

	string tem;
	std::stringstream StrStm;
	StrStm.clear();								//std::stringstream變數在使用(轉化)前都應先呼叫.cleat()函式
	tem.clear();
	StrStm << num;
	StrStm >> tem;
	string filePath = "faces\\train\\" + tem; //正樣本路徑
	//char *  filePath = "faces\\train\\1";
	vector<string> files;
	getFiles(filePath, files);
	int number = files.size();
	for (int i = 0; i < number; i++)
	{
		Mat  SrcImage = imread(files[i].c_str());
		SrcImage = SrcImage.reshape(1, 1);
		trainingImages.push_back(SrcImage);
	//	if (count == num)
			trainingLabels.push_back(num);//該樣本為數字
	//	else
	//		trainingLabels.push_back(0);//該樣本為數字
	}

}
int main()
{
	train2modle(15);
	Test_result(15);
	return 0;
}

void train2modle(int classesNum)
{
	int count = 1;
	//獲取訓練資料
	Mat classes;
	Mat trainingData;
	Mat trainingImages;
	vector<int> trainingLabels;
//	for (int count = 1; count <= classesNum; count++)//測試資料夾 有15個人臉資料夾
	{
		trainingLabels.clear();
		trainingImages.release();
		for (int num = 1; num <= classesNum; num++)
			getBubble(trainingImages, trainingLabels, num, count);
		//getNoBubble(trainingImages, trainingLabels);
		//在主函式中,將getBubble()與getNoBubble()寫好的包含特徵的矩陣拷貝給trainingData,將包含標籤的vector容器進行類
		//型轉換後拷貝到trainingLabels裡,至此,資料準備工作完成,trainingData與trainingLabels就是我們要訓練的資料。
		Mat(trainingImages).copyTo(trainingData);
		trainingData.convertTo(trainingData, CV_32FC1);
		Mat(trainingLabels).copyTo(classes);
		classes.convertTo(classes, CV_32SC1);
		// 建立分類器並設定引數
		Ptr<SVM> SVM_params = SVM::create();
		SVM_params->clear();
		SVM_params->setType(SVM::C_SVC);
		SVM_params->setKernel(SVM::LINEAR);  //核函式
		SVM_params->setDegree(0);
		SVM_params->setGamma(1);
		SVM_params->setCoef0(0);
		SVM_params->setC(1);
		SVM_params->setNu(0);
		SVM_params->setP(0);
		SVM_params->setTermCriteria(TermCriteria(TermCriteria::MAX_ITER + TermCriteria::EPS, 1000, 0.01));
		Ptr<TrainData> tData = TrainData::create(trainingData, ROW_SAMPLE, classes);
		// 訓練分類器
		SVM_params->train(tData);
		string tem;
		std::stringstream StrStm;
		StrStm.clear();								//std::stringstream變數在使用(轉化)前都應先呼叫.cleat()函式
		tem.clear();
		StrStm << count;
		StrStm >> tem;
		string savexml = "svm.xml";
		//儲存模型
		SVM_params->save(savexml);
		std::cout << "訓練好了!!!" << endl;
	}
}
void Test_result(int classesNum)
{
	std::stringstream StrStm;
	int result = 0;	int count = 1; 	string tem; int sum = 0;
	vector <int> responseSave;
	for (int j = 1; j <= classesNum; j++) //資料集測試圖片載入
	{
		StrStm.clear();
		tem.clear();
		StrStm << j;
		StrStm >> tem;
		string filePath = "faces//test//" + tem;
		vector<string> files;
		getFiles(filePath, files);
		int number = files.size();
		for (int i = 0; i < number; i++)//測試資料夾下子資料夾內的圖片
		{
		//	for (int count = 1; count <= classesNum; count++)//生成的模型載入
			{
				StrStm.clear();
				tem.clear();
				StrStm << count;
				StrStm >> tem;
				string modelpath = "svm.xml";
				sum = number + sum;
				Ptr<ml::SVM> svm = Algorithm::load<ml::SVM>(modelpath);
				Mat inMat = imread(files[i].c_str());
				Mat p = inMat.reshape(1, 1);
				p.convertTo(p, CV_32FC1);
				int response = (int)svm->predict(p);
				cout << response;
				responseSave.push_back(response);
				if (response== j )
				{
					result = result + 1;
						std::cout << "識別為S" << count << "實際為S" << j << endl;
					//	//break;
				}

			}
			cout << endl;
		}

	}


	std::cout << result << endl;
	std::cout << "識別率" << endl;
	std::cout << result / sum * 100 << "%" << endl;
	getchar();
}

以下是一開始沒有好好閱讀文件,以為svm是二分類,因此15類就傻瓜式的生成15個svm模型來作為多分類

<1>資料準備

同樣在專案工程下建立兩個資料夾 train、test但是測試的有十五個人所以 train 、test資料夾下各15個資料夾具體如下:
在這裡插入圖片描述在這裡插入圖片描述

<2>新增類別標籤

對於第一個分類器而言,只有第一個人為正樣本 剩餘全為負樣本
對於第二個分類器而言,只有第二個人為正樣本 剩餘全為負樣本
.
.
.

void getFiles(string path, vector<string>& files)
{
	intptr_t   hFile = 0;
	struct _finddata_t fileinfo;
	string p;
	if ((hFile = _findfirst(p.assign(path).append("\\*").c_str(), &fileinfo)) != -1)
	{
		do
		{
			if ((fileinfo.attrib &  _A_SUBDIR))
			{
				if (strcmp(fileinfo.name, ".") != 0 && strcmp(fileinfo.name, "..") != 0)					getFiles(p.assign(path).append("\\").append(fileinfo.name), files);
			}
			else
			{
			files.push_back(p.assign(path).append("\\").append(fileinfo.name));
			}
		} while (_findnext(hFile, &fileinfo) == 0);
		_findclose(hFile);
	}
}
//獲取訓練樣本本
void getBubble(Mat& trainingImages, vector<int>& trainingLabels, int num, int count)//count第幾個訓練器  num第幾個資料夾
{
	string tem;
	std::stringstream StrStm;
	StrStm.clear();								//std::stringstream變數在使用(轉化)前都應先呼叫.cleat()函式
	tem.clear();
	StrStm << num;
	StrStm >> tem;
	string filePath = "faces\\train\\" + tem; //正樣本路徑
	//char *  filePath = "faces\\train\\1";
	vector<string> files;
	getFiles(filePath, files);
	int number = files.size();
	for (int i = 0; i < number; i++)
	{
		Mat  SrcImage = imread(files[i].c_str());
		SrcImage = SrcImage.reshape(1, 1);
		trainingImages.push_back(SrcImage);
		if (count == num)
			trainingLabels.push_back(1);//該樣本為數字
		else
			trainingLabels.push_back(0);//該樣本為數字
	}

}

<2>訓練

每次訓練新的模型前,都需要把trainingLabels,trainingImages釋放!!不然==引用&會一直補充在後!導致維度一直在增會出錯。
== trainingLabels.clear();
trainingImages.release();

void train2modle(int classesNum)
{
	int count = 1;
	//獲取訓練資料
	Mat classes;
	Mat trainingData;
	Mat trainingImages;
	vector<int> trainingLabels;
	for (int count = 1; count <= classesNum; count++)//測試資料夾 有15個人臉資料夾
	{
		trainingLabels.clear();
		trainingImages.release();
		for (int num = 1; num <= classesNum; num++)
			getBubble(trainingImages, trainingLabels, num,count);
		//getNoBubble(trainingImages, trainingLabels);
		//在主函式中,將getBubble()與getNoBubble()寫好的包含特徵的矩陣拷貝給trainingData,將包含標籤的vector容器進行類
		//型轉換後拷貝到trainingLabels裡,至此,資料準備工作完成,trainingData與trainingLabels就是我們要訓練的資料。
		Mat(trainingImages).copyTo(trainingData);
		trainingData.convertTo(trainingData, CV_32FC1);
		Mat(trainingLabels).copyTo(classes);
		classes.convertTo(classes, CV_32SC1);
		// 建立分類器並設定引數
		Ptr<SVM> SVM_params = SVM::create();
		SVM_params->clear();
		SVM_params->setType(SVM::C_SVC);
		SVM_params->setKernel(SVM::LINEAR);  //核函式
		SVM_params->setDegree(0);
		SVM_params->setGamma(1);
		SVM_params->setCoef0(0);
		SVM_params->setC(1);
		SVM_params->setNu(0);
		SVM_params->setP(0);
		SVM_params->setTermCriteria(TermCriteria(TermCriteria::MAX_ITER + TermCriteria::EPS, 1000, 0.01));
		Ptr<TrainData> tData = TrainData::create(trainingData, ROW_SAMPLE, classes);
		// 訓練分類器
		SVM_params->train(tData);
		string tem;
		std::stringstream StrStm;
		StrStm.clear();								//std::stringstream變數在使用(轉化)前都應先呼叫.cleat()函式
		tem.clear();
		StrStm << count;
		StrStm >> tem;
		string savexml = "svm" + tem + ".xml";
		//儲存模型
		SVM_params->save(savexml);
		std::cout<< "訓練好了!!!" << endl;
	}
}

<3>測試

輸出的是標籤。cout << response ;對於每一張測試樣本來說會輸出15個數(每一個模型進去就會出來一個預測標籤)其中第幾個1就判為第幾個人;比如100000000000000 =該樣本預測為第一個人
010000000000000 =該樣本預測為第二個人

void Test_result(int classesNum)
{
	std::stringstream StrStm;
	int result = 0;	int count = 1; 	string tem; int sum = 0;
	vector <int> responseSave;
	for (int j = 1; j <= classesNum; j++) //資料集測試圖片載入
	{ 
		StrStm.clear();
		tem.clear();
		StrStm << j;
		StrStm >> tem;
		string filePath = "faces//test//"+tem;
		vector<string> files;
		getFiles(filePath, files);
		int number = files.size();
		for (int i = 0; i < number; i++)//測試資料夾下子資料夾內的圖片
		{
			for (int count = 1; count <= classesNum; count++)//生成的模型載入
			{
				StrStm.clear();
				tem.clear();
				StrStm << count;
				StrStm >> tem;
				string modelpath = "svm" + tem + ".xml";
				sum = number + sum;
				Ptr<ml::SVM> svm = Algorithm::load<ml::SVM>(modelpath);
				Mat inMat = imread(files[i].c_str());
				Mat p = inMat.reshape(1, 1);
				p.convertTo(p, CV_32FC1);
				int response = (int)svm->predict(p);
				cout << response ;
				responseSave.push_back(response);
				if (response == 1 &&(j ==count))
				{
					result = result + 1;
					std::cout << "識別為S" << count << "實際為S" << j << endl;
				//	//break;
				}
			
			}
			cout << endl;
		}
		
	}


	std::cout << result << endl;
	std::cout << "識別率" << endl;
	std::cout << result / sum * 100 << "%" << endl;
	getchar();
}

<5>主函式

int main()
{
	train2modle(15);
	Test_result(15);
	return 0;
}

相關文章