1. 程式人生 > >【opencv】基於opencv2的人臉識別系統

【opencv】基於opencv2的人臉識別系統

    之前,曾寫過一個較為完整的人臉識別小系統。開發環境為opencv2.4.9和VS2012,並加入了一個新模組cvui.h,用此模組為人臉識別系統寫了一個簡單介面。介面如下:

                    

                    

  此介面用到的元素比較簡單,包含按鈕、文字框、圖片及文字。

本文章寫作框架如下:

1. 人臉識別流程

                        

2.各部分功能:詳見下文“各模組講解”。

3.各部分中遇到的細節問題

     例如:

       CascadeClassifier cascade;//建立級聯分類器

cascade.load("haarcascade_frontalface_alt2.xml");// 載入訓練好的 人臉檢測器(.xml)

        cascade.detectMultiScale(frameGray,faces,1.2, 2,0 |  CV_HAAR_FIND_BIGGEST_OBJECT );

        haarcascade_frontalface_alt2.xml到底是什麼東西?

4.本系統的缺陷,是否可提升

  (1)  人臉採集問題:人臉旋轉-》矯正

(2)訓練模型問題:更換更好的模型來訓練-》深度學習

(3)樣本格式問題:直接利用彩色影象會不會更好

(4)訓練樣本問題:人臉資料庫數量不足,以及ORL是西方面孔,與我們東方面孔的差異。-》採集更多東方人臉,進行訓練

(5)人臉陣列:現在是隻識別一個人,能否識別多個,可以

(6)自己訓練人臉檢測器haar_cascade

5.所涉及的演算法及原理,詳見下文各模組講解。

6.具體程式碼請戳:http://blog.csdn.net/u012679707/article/details/79520299 基於opencv2的人臉識別系統(二)具體程式碼

各模組講解

第一部分:主函式

main.c

系統主函式,包含引數初始化、ui介面的設定以及整體流程控制。

第二部分:人臉採集

capture.cpp

人臉採集模組,功能是從攝像頭畫面中檢測出人臉,並將人臉影象(矩形)擷取下來,儲存到訓練檔案中。其中人臉檢測的詳細過程是,

第一步,建立級聯分類器

CascadeClassifier cascade;//建立級聯分類器

第二步,載入Haar級聯分類器模型.xml

cascade.load("haarcascade_frontalface_alt2.xml");	// 載入訓練好的 人臉檢測器(.xml)

第三步,用載入好的級聯分類器進行人臉檢測,返回檢測到的人臉陣列faces

cascade.detectMultiScale(frameGray,faces,1.2, 2,0 |  CV_HAAR_FIND_BIGGEST_OBJECT );

流程圖如下:

                    

這一模組中,有一個問題,haar是什麼?haar.xml為什麼可以做人臉檢測模型?如何檢測出人臉的?

首先,haar特徵是一種特徵提取的方法。其實,特徵提取方法有很多種,比如說Haar特徵,edgelet特徵,shapelet特徵,HOG特徵,HOF特徵,小波特徵,邊緣模板等等。

摘錄自:http://blog.csdn.net/yang6464158/article/details/25103703(特徵提取之——Haar特徵

Haar分類器 = Haar-like特徵 + 積分圖方法 + AdaBoost +級聯

Haar分類器演算法的要點如下:

① 使用Haar-like特徵做檢測。

② 使用積分圖(Integral Image)對Haar-like特徵求值進行加速。

③ 使用AdaBoost演算法訓練區分人臉和非人臉的強分類器。

④ 使用篩選式級聯把強分類器級聯到一起,提高準確率。


大神貼在此,非常詳細的演算法過程講解。

http://blog.csdn.net/beizhengren/article/details/77095724  (haar特徵介紹與分析)

http://blog.csdn.net/beizhengren/article/details/77095759  (積分圖,快速計算影象中任意位置的haar特徵值)

http://blog.csdn.net/beizhengren/article/details/77095841  (強弱級聯分類器與xml檔案引數含義)

http://blog.csdn.net/beizhengren/article/details/77095883   (利用並查集合並檢測視窗)

http://blog.csdn.net/beizhengren/article/details/77095969  (利用opencv_traincascade.exe訓練自己的分類器)

http://blog.csdn.net/beizhengren/article/details/77095988     (具體訓練過程分析)

opencv 用opencv_traincascade.exe訓練haar分類器

第三部分:模型訓練 train.cpp

流程圖如下:

                                  

1.前期準備工作,將所有的人臉樣本和類別標籤生成一個.csv檔案。

        生成csv檔案方法:http://blog.csdn.net/u012679707/article/details/79519143    (gogo小Sa)

2.訓練時可直接讀取csv檔案,實現樣本和類別標籤的獲取。

讀取csv文件方法: http://blog.csdn.net/u012679707/article/details/78711365 (gogo小Sa)

3.建立特徵臉模型,選擇20個主成分   (faceRecognizer 為cv2中的contrib模組)

Ptr<FaceRecognizer> model=createEigenFaceRecognizer(20); // 建立特徵臉模型 20張主成分臉

4.通過樣本和類別標籤進行訓練,最終得到訓練好的主成分臉模型。

model->train(images,labels); //訓練人臉模型,通過images和labels來訓練人臉模型

5.將模型儲存為.xml檔案

model->save("MyFacePcaModel.xml"); //將訓練模型儲存到MyFacePcaModel.xml

注意:contrib模組中的人臉識別模型有三種,PCA、fisher、LBP。本系統選擇的是主成分臉模型(PCA)

最終生成的MyFacePcaModel.xml檔案內容如下圖所示,其中

<num_components>20</num_components>   20是主特徵臉的個數
<rows>1</rows>
<cols>10304</cols>       1*10304 這表示每個特徵臉的大小,一行表示一張臉的資料,維度為10304(92*112)

                       圖3.1 MyFacePcaModel.xml

其中,faceRecognizer原始碼解析如下:詳細解析可參見大神貼                                                                                                                                  http://www.cnblogs.com/guoming0000/archive/2012/09/27/2706019.html

 class CV_EXPORTS_W FaceRecognizer : public Algorithm
    {
    public:
        //! virtual destructor
        virtual ~FaceRecognizer() {}

        // Trains a FaceRecognizer.
        CV_WRAP virtual void train(InputArrayOfArrays src, InputArray labels) = 0;

        // Updates a FaceRecognizer.
        CV_WRAP void update(InputArrayOfArrays src, InputArray labels);

        // Gets a prediction from a FaceRecognizer.
        virtual int predict(InputArray src) const = 0;

        // Predicts the label and confidence for a given sample.
        CV_WRAP virtual void predict(InputArray src, CV_OUT int &label, CV_OUT double &confidence) const = 0;

        // Serializes this object to a given filename.
        CV_WRAP virtual void save(const string& filename) const;

        // Deserializes this object from a given filename.
        CV_WRAP virtual void load(const string& filename);

        // Serializes this object to a given cv::FileStorage.
        virtual void save(FileStorage& fs) const = 0;

        // Deserializes this object from a given cv::FileStorage.
        virtual void load(const FileStorage& fs) = 0;

    };

    CV_EXPORTS_W Ptr<FaceRecognizer> createEigenFaceRecognizer(int num_components = 0, double threshold = DBL_MAX);
    CV_EXPORTS_W Ptr<FaceRecognizer> createFisherFaceRecognizer(int num_components = 0, double threshold = DBL_MAX);
    CV_EXPORTS_W Ptr<FaceRecognizer> createLBPHFaceRecognizer(int radius=1, int neighbors=8,
                                                            int grid_x=8, int grid_y=8, double threshold = DBL_MAX);

第四部分 :人臉識別(預測)  predict.cpp

1.建立人臉識別模型,為和訓練相對應,仍選擇特徵臉模型。

Ptr<FaceRecognizer> modelPCA=createEigenFaceRecognizer();// 建立特徵臉模型
2. 載入 特徵臉模型器

modelPCA->load("MyFacePcaModel.xml");

3.對測試影象進行分類
modelPCA->predict(face_test,predictPCA,confidence);  //confidence為置信度

----------------------------------------【全部例項程式碼】----------------------------------------------

第一部分:main.cpp

/*
Project Name:FaceRecognition
Author:Lisa
Data:2017_12
Version:V1

Abstract:
		the faceRecognition system includes 3 modules:
		1.capture.cpp ->capture face Image and  Detect face
		2.train.cpp ->train face module
		3.predict.cpp ->capture face image,face detection,face  recognition
 

Statement:
		You are free to use, change, or redistribute the code in any way you wish for
         non-commercial purposes, but please maintain the name of the original author.
		This code comes with no warranty(保證) of any kind.
*/ 
//#include"stdafx.h"
#include<opencv2/opencv.hpp>
#include<opencv2/highgui/highgui.hpp>
#include<opencv2/core/core.hpp>
#include<opencv2/imgproc/imgproc.hpp>
#include<iostream>
#include<fstream> //包含ifstream
#include"opencv2/cvui/cvui.h"

#include"capture.hpp"
#include"train.hpp"
#include"predict.hpp"
#include<windows.h>

using namespace cv;
using namespace std;
//using namespace cvui;

#define WINDOW_NAME "Face Recognition System ByLISA"
// 採集到的圖片儲存地址
const string savePath="F:\\opencv_project\\faceRecognition\\ORL\\prePhoto\\1.pgm";
/******************************* main()  ***************************************************************/
Mat Frame;
int main(int argc,char *argv[])
{
	system("color 5E");
	//Mat img=imread("lanyangyang.jpg");

	namedWindow(WINDOW_NAME);
	cvui::init(WINDOW_NAME);
	Frame=Mat(320,500,CV_8UC3);
	Frame = cv::Scalar(200,20,200);   //顏色填充49, 52, 49

	cvui::window(Frame,350,30,100,132,"predict photo");
	cvui::window(Frame,350,165,100,132,"predict result");
	
	while(1)
  {
	
    bool buttonCapture=cvui::button(Frame,50,100,"Capture"); 
    bool buttonTrain=cvui::button(Frame,50,130,"Train");
    bool buttonPredict=cvui::button(Frame,50,160,"Predict");
	
	
    if(buttonCapture) { 
		Mat capPhoto;
		if(photoCapture(capPhoto)) cvui::text(Frame, 150, 100, "capture is sucessful!"); 
		else  cvui::text(Frame, 150, 100, "capture failed!");

	}
	if(buttonTrain)  {
		if(train()) cvui::text(Frame, 150, 130, "train is sucessful!");
		else              cvui::text(Frame, 150, 100, "train failed!");
	}
	if(buttonPredict) { 
		  cvui::printf(Frame, 150, 160,0.4, 0x00ff00, "predict result is    ");

		Mat predictPhoto;//待識別照片
		int predictResult;//預測結果
		predict(predictPhoto,predictResult);
		imwrite(savePath,predictPhoto);
		cvui::printf(Frame, 150, 160,0.4, 0x00ff00, "predict result is %d",predictResult);
        	
		string fileName="F:\\opencv_project\\faceRecognition\\ORL\\s";
		fileName+=to_string(predictResult+1); //number To string
		fileName+="\\1.pgm";
		cout<<fileName<<endl;
		Mat img1=imread(fileName);
		fileName="0";//檔案路徑清零(要在獲取完以後再清零)
		
		// 在preROI區顯示照片
		Mat prePhoto=imread(savePath);//讀取採集圖 
		Mat preROI=Frame(Rect(353,50,predictPhoto.cols,predictPhoto.rows)); //250 200
		resize(prePhoto,preROI,Size(92,112) );
		// 在resultROI區顯示照片
		Mat resultROI=Frame(Rect(353,185,img1.cols,img1.rows)); //250 200
		resize(img1,resultROI,Size(92,112) );  	//另一種方法:img1.copyTo(resultROI,img1);
		//imshow("Result",img1);	
	}
	cvui::update();
	imshow(WINDOW_NAME,Frame);
	if (cv::waitKey(20) == 27) break;  
   }
	return 0;
}

#if 0
	// 在指定區域顯示圖片
	Mat mask=imread("lanyangyang.jpg");
	Mat winROI=Frame(Rect(50,120,mask.cols,mask.rows));
	img.copyTo(winROI,mask);
#endif


	


第二部分:capture.cpp

// capture.cpp

#include"capture.hpp"
#include<windows.h>



bool photoCapture(Mat &capPhoto )
{
		
/***************************************** 1.開啟預設攝像頭 ********************************************************/
	VideoCapture cap(0); //開啟預設攝像頭
	if(! cap.isOpened())
	{
		cout<<"camera open fail"<<endl;
		exit(-1);
	}

	int i=1;
	Mat frame; // 關聯視訊流
	Mat frameGray;// frame的灰度圖
	Mat frameROI; // frameGray的ROI區
	Mat face;
	vector<Rect> faces;

	/*********************************** 2.載入人臉檢測器,載入人臉模型器******************************/
	CascadeClassifier cascade;//建立級聯分類器
	cascade.load("haarcascade_frontalface_alt2.xml");	// 載入訓練好的 人臉檢測器(.xml)
	while(1)
	{
		cap>>frame;
		namedWindow("frame");
		imshow("frame",frame);  // 顯示每一幀影象
		//cvui::update();
	    //imshow(WINDOW_NAME,Frame);
		string filename=format("F:\\opencv_project\\faceRecognition\\ORL\\s42\\%d.pgm",i);

		char key=waitKey(30);;
		switch(key) // 按下采集按鈕
		{
	case 'p':
		
		cvtColor(frame,frameGray,CV_BGR2GRAY);
		//imshow("frameGray",frameGray);
		

		/*********************************** 3.人臉檢測 ******************************/
	    cascade.detectMultiScale(frameGray,faces,1.2, 2,0 |  CV_HAAR_FIND_BIGGEST_OBJECT );
		if(faces.size()>0)
		{
		  for(size_t ii=0;ii<faces.size();ii++)
		  {   // setImgROI
			frameROI=frameGray(faces[ii]); // 為frame_temp設定了ROI區域 -> Mat imgROI=img( Rect);
		  }	
	      resize(frameROI,face,Size(92,112));
		  imwrite(filename,face);
		  imshow("photo",face);
		  face.copyTo(capPhoto); // 將採集到的圖片copy給capPhoto
		  i++;
		  waitKey(500);
		  destroyWindow("photo");
		}
		else 
		{
			//專案屬性的常規項修改字符集,選擇為多字符集 ,原為Unicode
			MessageBox(GetForegroundWindow(),"valid capture!please retry!","Warning",1); //MessageBox 
             //printf("%d\n",x); 
		}
		break;

	case 'P':
		cvtColor(frame,frameGray,CV_BGR2GRAY);
		/*********************************** 3.人臉檢測 ******************************/
	    cascade.detectMultiScale(frameGray,faces,1.2, 2,0 |  CV_HAAR_FIND_BIGGEST_OBJECT );
		if(faces.size()>0)
		{
		for(size_t ii=0;ii<faces.size();ii++)
		{   // setImgROI
			frameROI=frameGray(faces[ii]); // 為frame_temp設定了ROI區域 -> Mat imgROI=img( Rect);
		}
		 resize(frameROI,face,Size(92,112));
		 imwrite(filename,face);
		 imshow("photo",face);
		 i++;
		 waitKey(500);
		 destroyWindow("photo");
		}
		break;
		
	default: break;
		}
		if(i>=(captureCount+1)) 
		{ 
			cout<<"capture is successful!"<<endl; 
			MessageBox(GetForegroundWindow(),"capture is successful!",NULL,1); //MessageBox
			break;
		}
	}
	
		
	destroyWindow("frame");
	if(i>=(captureCount+1)) return true;
	else                return false;
}

第三部分:train.cpp

// train.cpp

#include"train.hpp"
#include<windows.h>

bool train()
	{
	string csvFile="at.txt";
	vector<Mat> images;
	vector<int> labels;
	
	// [1]讀取csv檔案
	try
	{
	read_csv(csvFile,images,labels,CountMax,CountMin,';'); //讀取csvFile中所有的img和label
	}
	catch(cv::Exception& e) // ???????????????????????????????????????????????????
	{                  // cerr:輸出到標準錯誤的ostream物件,常用於程式錯誤資訊
	cerr<<"Error opening file\" "<<csvFile <<"\".reason: "<<e.msg<<endl; //異常 發生的原因
	exit(-1);
	}
	
	
	// 若未讀取到足夠圖片,也退出
	if(images.size() <=1)
	{
		string errMsg="THis demo needs at least 2 images to work.please add images!";
		CV_Error(CV_StsError,errMsg);
	
	}
	cout<<"train1.讀取ok"<<endl;
	
	// 訓練資料,並將訓練好的人臉模型儲存到.xml中
	/*
	Ptr<>為模板類,定義model為指向FaceRecognizer類的指標。model為指標!
	*/
	Ptr<FaceRecognizer> model=createEigenFaceRecognizer(20); // 建立特徵臉模型 20張主成分臉
	model->train(images,labels); //訓練
	model->save("MyFacePcaModel.xml"); //將訓練模型儲存到MyFacePcaModel.xml

	cout<<"train2.建立臉模型ok"<<endl;
	MessageBox(GetForegroundWindow(),"train is sucessful!",NULL,1); //MessageBox 
	return true;
	
	}


	/*
函式:static void read_csv(const string& filename,vector<Mat>images, vector<int> labels,int CountMax,int CountMin, char separator=';')
功能:讀取csv檔案的影象路徑和標籤。主要使用stringstream和getline()
引數說明:filename--要讀取的csv檔案
		  images----讀取的圖片(向量)
		  labels----讀取的圖片對應標籤 (向量)
		  CountMax,int CountMin--讀取的每一類別的圖片下標的最大值和最小值(預設每個類別共10張照片)
		  separator-分隔符,起控制讀取的作用。可自定義為逗號空格等,(此程式中)預設為分號
返回值:空
 */
/*  
備註:(函式內部涉及到的部分類和方法說明)
1. stringstream:字串流。
	功能:將記憶體中的物件與流繫結。

2. getline():
	函式原型:istream &getline( ifstream &input,string &out,char dielm)
	引數說明:Input--輸入檔案
			  out----輸出字串
			  dielm--讀取到該字元停止(起到控制作用),預設是換行符‘\n’
	功能:	讀取檔案Input中的字串到out中。
	返回值:返回Input,若是檔案末尾會返回檔案尾部標識eof

3. ifstream: 從硬碟開啟檔案(讀取),從磁碟輸入檔案,讀到記憶體中
   ofstream: 從記憶體開啟檔案(讀取),從記憶體輸入檔案,讀到磁碟中)
*/
static	void read_csv(const string& filename,vector<Mat>& images, vector<int>& labels,int max,int min,char separator=';')
{
	std::ifstream  file(filename.c_str(),ifstream::in); // 以in模式(讀取檔案模式)開啟檔案 ,實際是將filename檔案關聯給 流file  !!!!!!!!!!!!!!!!!! filename.c_str()

	if(! file)
	{
		string error_message="No valid input file was given,please check the given filename";
		CV_Error(CV_StsBadArg,error_message);
	
	}
	int ii=0;

	/**********************讀取檔案.txt內容****************************/
	string line,path,label;
	// [1]讀取file檔案中的一行字串給 line
	while( getline(file,line,'\n') )  // 控制:直到讀到file檔案末尾(eof標識),才跳出while
	{
	// [2]將line整行字串讀取到lines(流)中
		stringstream lines(line); //區別->lines是流,讀取字元時,指標會隨流而動;而line是string,固定的,下文中的讀取每次都是從line頭開始
	// [3]讀取檔案中的路徑和標籤
		getline(lines,path,separator); //此時游標已走到path之後的位置(即;分號處)
		getline(lines,label);
    // [4]將圖片和標籤加入imgs 和 labels
		if( (path.empty()==0) && (label.empty() ==0))
		{    
			if(ii%10<=max && ii%10>=min)  //預設每個類別共10張照片
			{
			Mat img=imread(path,0);  //第二個引數為0 !!!
			//Mat img = imread(ImageFileAddress, CV_LOAD_IMAGE_GRAYSCALE),CV_LOAD_IMAGE_GRAYSCALE值為 0,指灰圖(原本為“CV_LOAD_IMAGE_UNCHANGED”)
			if(img.data!=0 )
			{
			images.push_back( img ); // 將圖片 新增到images中
			labels.push_back( atoi(label.c_str() ) );
			}
		    }
			 if(ii<9) ii++;
		      else ii=0;

		}
	}
}

第四部分:predict.cpp

// predict.cpp

#include"predict.hpp"
#include<windows.h>

using namespace cv;
using namespace std;


// void predict();
void predict(Mat &predictPhoto,int& predictPCA)
{
	/*********************************** 1.開啟預設攝像頭******************************/
	VideoCapture cap(0); //
	if(! cap.isOpened())
	{
		cout<<"camera open fail"<<endl;
		exit(-1);
	}

	cout<<"predict 1.ok"<<endl;

	Mat frame;
	Mat gray; // 灰度圖
	vector<Rect> faces(0); //矩形向量,存放檢測出的人臉
	/*********************************** 2.載入人臉檢測器,載入人臉模型器******************************/
	//建立級聯分類器
	CascadeClassifier cascade;
	// 載入訓練好的 人臉檢測器(.xml)
	cascade.load("haarcascade_frontalface_alt2.xml");

	Ptr<FaceRecognizer> modelPCA=createEigenFaceRecognizer();// 建立特徵臉模型
    // 載入 特徵臉模型器
	modelPCA->load("MyFacePcaModel.xml");

	cout<<"predict 2.ok"<<endl;
	int key;
	Mat capFace;
	while(1)
	{
		cap>>frame; //將獲取到的每一幀影象 寫入 frame;
		namedWindow("frame");
		imshow("frame",frame);   // 顯示攝像頭
		key=waitKey(50);
		if(key=='p'||key=='P') 
		{
		capFace=frame.clone();
		// rgb To gray
		cvtColor(frame,gray,CV_BGR2GRAY);
		// 直方圖均衡化,提高影象質量
		equalizeHist(gray,gray);
		/*********************************** 3.人臉檢測 ******************************/
		cascade.detectMultiScale(gray,faces,1.2, 2,0 |  CV_HAAR_FIND_BIGGEST_OBJECT );
       
		cout<<"detect face number is :"<<faces.size()<<endl;
		cout<<"predict 3.ok"<<endl;
		if(faces.size()>0)
		{
		/************************************* 4.人臉識別 ******************************/
		Mat face_temp,face_test;
		for(size_t i=0;i<faces.size();i++)
		{   			
			// setImgROI
			face_temp=gray(faces[i]); // 為Gray設定了ROI區域 -> Mat imgROI=img( Rect);
		
		}	
		// 調整大小為112*92
		resize(face_temp,face_test,Size(92,112));
		namedWindow("capFace");
		imshow("capFace",face_test);
		face_test.copyTo(predictPhoto);
				
		// 測試圖應該為灰度圖
		double confidence;
		modelPCA->predict(face_test,predictPCA,confidence);
		cout<<"the predict result is "<< predictPCA<<endl<<"confidence is "<<confidence<<endl;
		waitKey(2000);
		cout<<"predict 4.ok"<<endl;
		break;
		}
		else
		{
			//專案屬性的常規項修改字符集,選擇為多字符集 ,原為Unicode
			MessageBox(GetForegroundWindow(),"valid capture!please retry!","Warning",1); //MessageBox 
		}
		
		}
	}
    destroyWindow("frame");
	destroyWindow("capFace");


}

                                       -----感謝欣賞,歡迎下邊留言評論!-----