Code 0002: Face Recognition

/********************2016.4.18人脸识别程序**********************/
#include "opencv2/core/core.hpp"    
#include "opencv2/objdetect/objdetect.hpp"    
#include "opencv2/highgui/highgui.hpp"    
#include "opencv2/imgproc/imgproc.hpp"    
#include "opencv2/opencv.hpp"
#include <opencv2/features2d/features2d.hpp>
#include <opencv2/nonfree/features2d.hpp>

#include <iostream>    
#include <stdio.h>    
 
using namespace std;   
using namespace cv;   

/*************KEYBORAD值定义*************/
#define ESC_KEYBOARD			27				//"ESC"键
#define ENTER_KEYBOARD			13				//"ENTER"键
#define SPACE_KEYBOARD			32				//"SPACE"键
#define TAKE_KEYBOARD			SPACE_KEYBOARD  //"SPACE"键作为拍照键
#define TAKEPICMODE_KEYBOARD   '1'				//"1"键作为拍照模式选择
#define FACEMODE_KEYBOARD      '2'				//"2"键作为人脸识别模式选择

/*************HELPCHOICE值定义*************/
#define MAIN_HELPCHOICE		0
#define TAKE_HELPCHOICE		1
#define FACE_HELPCHOICE		2

/*************样本次数定义*************/
#define FACE_NUM			20

/*************匹配率定义*************/
#define FACE_MATCH			5.0		//匹配率为(100/FACE_MATCH)%

/*************匹配结果定义*************/
#define FACE_SUCCESS	    1
#define FACE_FAILED			0

/*************匹配结果定义*************/
#define COUNT_START	        1
#define COUNT_END			0

/*************函数定义*************/
Mat detectAndDisplay( Mat frame );		//人脸检测与显示函数  函数返回提取后的人脸
int takePicture( Mat frame);			//按键处理函数        包括样本提取操作及人脸匹配操作
void showHelpText(int choice);			//提示信息函数
int faceCompare1(Mat myface,Mat face);  //人脸匹配函数1
int faceCompare2(Mat myface,Mat face);  //人脸匹配函数2
int faceCompare3(Mat myface,Mat face);  //人脸匹配函数3
float showTime(int state);			    //帧率信息函数

/*************训练库及分类器定义*************/
string face_cascade_name = "haarcascade_frontalface_alt.xml"; //该文件存在于OpenCV安装目录下的\sources\data\haarcascades内,需要将该xml文件复制到当前工程目录下   
CascadeClassifier face_cascade;								  //分类器定义

/*************全局变量定义*************/
int facenum = 0;						//当前比较的样本标号,当facenum为0时,意味着一次匹配完成,这时下一副采集的图像继续匹配
int modeChoice = -1;					//模式选择变量			

/*************main函数*************/
int main( int argc, char** argv )
{  
	//显示提示信息
	showHelpText(MAIN_HELPCHOICE);

	//判断有没有找到训练库
    if( !face_cascade.load( face_cascade_name ))
	{    
		printf("级联分类器错误,可能未找到文件,拷贝该文件到工程目录下!\n");   
        return -1;    
    }  

	//打开摄像头
	VideoCapture capture(0);
	capture.set(CV_CAP_PROP_FRAME_WIDTH,360);	//设置采集视频的宽度
	capture.set(CV_CAP_PROP_FRAME_HEIGHT,900);	//设置采集视频的高度
	//定义存放视频每帧图像的frame变量及用于存放detectAndDisplay返回的人脸图像的pictureface变量
	Mat frame,frameface,pictureface;
	//while循环,提取每帧图像并进行人脸识别处理,初始模式下按【ESC】键退出循环
	while(1){
		capture>>frame;							//capture将每帧图像传给frame
		imshow("人脸识别",frame);				//显示
		frameface = detectAndDisplay(frame);	//调用人脸检测函数,函数检测到人脸后返回提取后的人脸图像 
		
		if(facenum == 0  || modeChoice != FACE_HELPCHOICE)	//匹配模式下,一次匹配完成,下一副匹配图像传入
			frameface.copyTo(pictureface);
		if(takePicture(pictureface) < 0)		//按键处理及样本提取操作函数,匹配操作也在此完成
			break;								//takePicture函数返回退出指令则退出while循环
	}
	return 0;
}   

/******************人脸检测与显示函数*******************/
//函数名:detectAndDisplay
//参  数:参数一  face
//						待人脸检测Mat类型图像	   
//返回值:检测提取后的人脸图像
//功  能:对输入图像进行人脸检测,检测到后将人脸圈出显示,并将人脸部分提取后返回
Mat detectAndDisplay( Mat face )
{  
    std::vector<Rect> faces;  //作detectMultiScale用来存放被检测物体的矩形框向量参数
    Mat face_gray;			  //存放face灰度图
    Mat myface;				  //存放检测到并提取出的人脸图像

	//detectMultiScale函数image参数预处理
    cvtColor( face, face_gray, CV_BGR2GRAY );	//RGB类型转换为灰度类型   
    equalizeHist( face_gray, face_gray );		//直方图均衡化   
	
	showTime(COUNT_START);
	//人脸检测操作
    //detectMultiScale函数中face_gray表示的是要检测的输入图像为face_gray
	//faces表示检测到的人脸目标序列,
    //1.1表示每次图像尺寸减小的比例为1.1,
	//2表示每一个目标至少要被检测到3次才算是真的目标(因为周围的像素和不同的窗口大小都可以检测到人脸),
    //CV_HAAR_SCALE_IMAGE表示不是缩放分类器来检测,而是缩放图像,CV_HAAR_FIND_BIGGEST_OBJECT只检测最大对象
    //Size(100, 100)为目标的最小   Size(600, 600) 最大尺寸
    face_cascade.detectMultiScale( face_gray, faces, 1.1, 2, 0|CV_HAAR_FIND_BIGGEST_OBJECT, Size(100, 100));   
	//检测时间过长直接退出函数
	float fps = showTime(COUNT_END);
	//cout<<"当前帧率为"<<fps<<endl;
	if(fps < 10)
	{
		imshow("人脸识别",face);
		return Mat::zeros(100,100,CV_8UC1);
	}
	//检测到人脸则圈脸显示及人脸提取返回,否则返回face_gray,注意不要返回face,faceCompare未做转灰度处理
	if(faces.size() > 0){
		myface = face_gray(Range(faces[0].y,faces[0].y+faces[0].height),Range(faces[0].x,faces[0].x+faces[0].width));  //提取人脸
        //圈脸显示
		Point center( faces[0].x + faces[0].width*0.5, faces[0].y + faces[0].height*0.5 );							   //圆心
        ellipse( face, center, Size( faces[0].width*0.5, faces[0].height*0.5), 0, 0, 360, Scalar( 255, 0, 0), 2,7, 0 );//画圆 颜色蓝色(B255 G0 R0)旋转0度 扩展弧度从0度到360度
		imshow("人脸识别",face); //显示
		//返回人脸图像
		return myface;
	}   
	imshow("人脸识别",face); //显示
	return Mat::zeros(100,100,CV_8UC1);
}   

/******************按键处理函数*******************/
//函数名:takePicture
//参  数:参数一  frame
//						detectAndDisplay函数返回的人脸__灰度__图像,用于作为样本或作为匹配的对象,根据当前选择的具体功能而定
//返回值:退出while循环的标志,-1或0,其中返回-1则main函数退出循环
//功  能:对按键进行捕获,并根据按键值进行相关处理,包括样本提取操作及人脸匹配操作
int takePicture( Mat frame) 
{
	//拍照相关变量定义:标志、编号、名字及存放变量
	int Picture_Flag= 0;
	static int Picture_Num = 1;
	char Picture_Name[20];
	Mat Pictures;

	//按键值置-1
	int Value_BOARD = -1;
	//按键值捕获
	Value_BOARD = waitKey(10);
	//按键值判断
	switch(Value_BOARD){
		case ESC_KEYBOARD			:if(modeChoice < 0)				
										 Picture_Flag = -1;		//退出程序
									 else
										modeChoice = -1,showHelpText(MAIN_HELPCHOICE);//退指main模式
									 break;
		case TAKEPICMODE_KEYBOARD	:if(modeChoice < 0)
										modeChoice = TAKE_HELPCHOICE,showHelpText(TAKE_HELPCHOICE);//拍照模式
									 break;
		case FACEMODE_KEYBOARD		:if(modeChoice < 0)
										modeChoice = FACE_HELPCHOICE,showHelpText(FACE_HELPCHOICE);//匹配模式
									 break;
		case TAKE_KEYBOARD			:if(modeChoice == 1)
										Picture_Flag = 1,frame.copyTo(Pictures);	  //拍照指令
									 break;
		default:break;
	}
	/****************指令处理*********************/
	//退出指令
	if(Picture_Flag == -1){
		Picture_Flag = 0;
		printf("\n\n\t退出程序");
		return -1;
	}
	//拍照并存储指令 从第一张开始存储
	else if(Picture_Flag == 1){
		Picture_Flag = 0;
		sprintf(Picture_Name,"拍下的第%d张.jpg",Picture_Num);
		if(imwrite(Picture_Name,Pictures))
			printf("\t%s保存成功\n",Picture_Name);
		else
			printf("\t%s保存失败\n",Picture_Name);
		Picture_Num++;
	}

	/****************模式处理*********************/
	//拍照模式 样本提取相关操作在Picture_Flag == 1分支下
	if(modeChoice == TAKE_HELPCHOICE)
	{
		imshow("face",frame);		//实时显示拍照时最终提取的样本
	}
	//匹配模式 根据样本数量进行多次匹配,以适应不同角度下的人脸
	else if(modeChoice == FACE_HELPCHOICE && frame.data != 0)
	{
		Mat myface[FACE_NUM];	//用于匹配的FACE_NUM张样本
		static int successnum = 0;	//successnum匹配成功次数
		//匹配FACE_NUM张样本
		if(facenum < FACE_NUM)
		{	
			sprintf(Picture_Name,"拍下的第%d张.jpg",facenum+1);
			//样本读取
			myface[facenum] = imread(Picture_Name);
			//样本读取错误,结束样本读取
			if(myface[facenum].data == 0)
				return 0;
			//匹配成功facenum++
			if(faceCompare1(myface[facenum],frame) == FACE_SUCCESS)
				successnum++;
			facenum++;
		}
		else 
		{
			//显示最终匹配结果及样本匹配成功次数
			if(successnum >= 1)
				cout<<"帅哥你好!"<<successnum<<"\n";
			else
				cout<<"丑比走开!"<<successnum<<"\n";
			//匹配计数清零
			facenum = 0;
			successnum = 0;
		}
	}
	//main模式
	else
	{
		destroyWindow("face");			//关闭显示拍照时最终提取的样本
	}
	//正常退出,不退出main函数while循环
	return 0;	
}


/******************人脸匹配函数1*******************/
//函数名:faceCompare1
//参  数:参数一  myface
//						人脸样本Mat类型图像	  
//		  参数二  face
//						待匹配的Mat类型图像	
//返回值:匹配结果,匹配成功返回1,匹配失败返回0
//功  能:将输入myface及face进行匹配,并返回匹配结果
int faceCompare1(Mat myface,Mat face)
{
	Mat trainImage = myface;		//样本存入trainImage
	//如果带匹配图像为空,则退出
	if(face.empty())
		return FACE_FAILED;	

	//检测SURF关键点,提取训练图像描述符
	vector<KeyPoint>train_keyPoint; //样本特征点向量
	Mat trainDesciptor;
	SurfFeatureDetector featureDetector(80);
	featureDetector.detect(trainImage,train_keyPoint);
	SurfDescriptorExtractor featureExtractor;
	featureExtractor.compute(trainImage,train_keyPoint,trainDesciptor);

	//创建基于FLANN的描述符匹配对象
	FlannBasedMatcher matcher;
	vector<Mat> train_desc_collection(1,trainDesciptor);
	matcher.add(train_desc_collection);
	matcher.train();

	//检测SURF关键点,提取测试图像描述符
	vector<KeyPoint>test_keyPoint;
	Mat testDesciptor;
	featureDetector.detect(face,test_keyPoint);
	featureExtractor.compute(face,test_keyPoint,testDesciptor);

	//匹配训练和测试描述符
	vector<vector<DMatch>>matches;
	matcher.knnMatch(testDesciptor,matches,2);

	//根据劳氏算法,得到优秀的匹配点
	vector<DMatch>goodMatches;
	for(unsigned int i=0;i<matches.size();i++){
		if(matches[i][0].distance<0.6*matches[i][1].distance)
			goodMatches.push_back(matches[i][0]);
	}
	//匹配信息显示
	//cout<<"\ntrain_keyPoint ="<<train_keyPoint.size()<<"\n";
	//cout<<"test_keyPoint ="<<test_keyPoint.size()<<"\n";
	//cout<<"matches ="<<matches.size()<<"\n";
	//cout<<"goodMatches ="<<goodMatches.size()<<"\n";
	//样本特征点个数/匹配特征点个数 < FACE_MATCH 即匹配个数超过(100/FACE_MATCH)%则认定为匹配成功,返回1,否则匹配失败,返回0
	if((double)matches.size()/(double)goodMatches.size() < FACE_MATCH)
		return FACE_SUCCESS;
	else
		return FACE_FAILED;
}

/******************人脸匹配函数2*******************/
//函数名:faceCompare2
//参  数:参数一  myface
//						人脸样本Mat类型图像	  
//		  参数二  face
//						待匹配的Mat类型图像	
//返回值:匹配结果,匹配成功返回1,匹配失败返回0
//功  能:将输入myface及face进行匹配,并返回匹配结果
int faceCompare2(Mat myface,Mat face)
{
	Mat trainImage = myface;		//样本存入trainImage
	//如果带匹配图像为空,则退出
	if(face.empty())
		return FACE_FAILED;	

	//使用SURF算子检测关键点,调用detect函数检测出SURF特征点关键点,保存在vector容器中
	SurfFeatureDetector featureDetector(400);	//SURF算法中的hessian阈值为400
	vector<KeyPoint>train_keyPoint,test_keyPoint; //样本特征点向量
	featureDetector.detect(trainImage,train_keyPoint);
	featureDetector.detect(face,test_keyPoint);
	
	//计算描述符
	SurfDescriptorExtractor featureExtractor;
	Mat	trainDesciptor,testDesciptor;
	featureExtractor.compute(trainImage,train_keyPoint,trainDesciptor);
	featureExtractor.compute(face,test_keyPoint,testDesciptor);
	
	//创建基于FLANN的描述符匹配对象
	FlannBasedMatcher matcher;
	vector<DMatch>matches;
	matcher.match(testDesciptor,trainDesciptor,matches);
	double max_dist = 0;double min_dist = 100;		//最小距离和最大距离
	
	//计算出关键点之间的最大值和最小值
	for(int i = 0;i < testDesciptor.rows;i++)
	{
		double dist = matches[i].distance;
		if(dist < min_dist) min_dist = dist;
		if(dist > max_dist) max_dist = dist;
	}
	
	//根据劳氏算法,得到优秀的匹配点
	std::vector<DMatch>goodMatches;
	for(int i=0;i<testDesciptor.rows;i++){
		if(matches[i].distance<3*min_dist)
			goodMatches.push_back(matches[i]);
	}
	//匹配信息显示
	cout<<"\ntrain_keyPoint ="<<train_keyPoint.size()<<"\n";
	cout<<"test_keyPoint ="<<test_keyPoint.size()<<"\n";
	cout<<"matches ="<<matches.size()<<"\n";
	cout<<"goodMatches ="<<goodMatches.size()<<"\n";
	//样本特征点个数/匹配特征点个数 < FACE_MATCH 即匹配个数超过(100/FACE_MATCH)%则认定为匹配成功,返回1,否则匹配失败,返回0
	if((double)matches.size()/(double)goodMatches.size() < FACE_MATCH)
		return FACE_SUCCESS;
	else
		return FACE_FAILED;
}
/******************人脸匹配函数3*******************/
//函数名:faceCompare3
//参  数:参数一  myface
//						人脸样本Mat类型图像	  
//		  参数二  face
//						待匹配的Mat类型图像	
//返回值:匹配结果,匹配成功返回1,匹配失败返回0
//功  能:将输入myface及face进行匹配,并返回匹配结果
int faceCompare3(Mat myface,Mat face)
{
	Mat trainImage = myface;		//样本存入trainImage
	//如果带匹配图像为空,则退出
	if(face.empty())
		return FACE_FAILED;	
	OrbFeatureDetector featureDetector;
	vector<KeyPoint> keyPoints;
	Mat descriptors;

	//调用detect函数检测出特征关键点,保存在vector容器中
	featureDetector.detect(trainImage, keyPoints);

	//计算描述符(特征向量)
	OrbDescriptorExtractor featureExtractor;
	featureExtractor.compute(trainImage, keyPoints, descriptors);

	//基于FLANN的描述符对象匹配
	flann::Index flannIndex(descriptors, flann::LshIndexParams(12, 20, 2), cvflann::FLANN_DIST_HAMMING);

	//检测SIFT关键点并提取测试图像中的描述符
	vector<KeyPoint> captureKeyPoints;
	Mat captureDescription;

	//调用detect函数检测出特征关键点,保存在vector容器中
	featureDetector.detect(face, captureKeyPoints);

	//计算描述符
	featureExtractor.compute(face, captureKeyPoints, captureDescription);

	//匹配和测试描述符,获取两个最邻近的描述符
	Mat matchIndex(captureDescription.rows, 2, CV_32SC1), matchDistance(captureDescription.rows, 2, CV_32FC1);
	flannIndex.knnSearch(captureDescription, matchIndex, matchDistance, 2, flann::SearchParams());//调用K邻近算法

	//根据劳氏算法(Lowe's algorithm)选出优秀的匹配
	vector<DMatch> goodMatches;
	for(int i = 0; i < matchDistance.rows; i++) 
	{
		if(matchDistance.at<float>(i, 0) < 0.6 * matchDistance.at<float>(i, 1)) 
		{
			DMatch dmatches(i, matchIndex.at<int>(i, 0), matchDistance.at<float>(i, 0));
			goodMatches.push_back(dmatches);
		}
	}

	//匹配信息显示
	cout<<"\ntrain_keyPoint ="<<keyPoints.size()<<"\n";
	cout<<"test_keyPoint ="<<captureKeyPoints.size()<<"\n";
	//cout<<"matches ="<<dmatches<<"\n";
	cout<<"goodMatches ="<<goodMatches.size()<<"\n";
	//样本特征点个数/匹配特征点个数 < FACE_MATCH 即匹配个数超过(100/FACE_MATCH)%则认定为匹配成功,返回1,否则匹配失败,返回0
	if((double)captureKeyPoints.size()/(double)goodMatches.size() < FACE_MATCH)
		return FACE_SUCCESS;
	else
		return FACE_FAILED;
}

/******************提示信息函数*******************/
//函数名:showHelpText
//参  数:参数一  choice
//						提示信息选择参数  
//返回值:void
//功  能:对当前操作提供提示信息
void showHelpText(int choice)
{
	if(choice == MAIN_HELPCHOICE){
		//输出一些帮助信息
		printf("\n\n\n\t人脸识别电子门锁 \n\n");
		printf("\t当前使用的Opencv版本为"CV_VERSION);
		printf("\n\n\t模式选择:\n\n");
		printf("\t\t键盘按键【1】   拍照模式\n");
		printf("\t\t键盘按键【2】   人脸识别模式\n\n");
		printf("\t\t键盘按键【ESC】 退出程序\n\n");
	}
	else if(choice == TAKE_HELPCHOICE){
		//输出一些帮助信息
		printf("\n\n\n\t拍照模式 \n\n");
		printf("\t当前使用的Opencv版本为"CV_VERSION);
		printf("\n\n\t模式选择:\n\n");
		printf("\t\t键盘按键【ESC】  退出拍照模式\n");
		printf("\t\t键盘按键【TAKE】 拍照\n\n");
	}
	else if(choice == FACE_HELPCHOICE){
		//输出一些帮助信息
		printf("\n\n\n\t人脸识别模式 \n\n");
		printf("\t当前使用的Opencv版本为"CV_VERSION);
		printf("\n\n\t人脸识别中 \n\n");
		printf("\t\t键盘按键【ESC】  退出人脸识别模式\n");
	}
}

/******************帧率信息函数*******************/
//函数名:showTime
//参  数:参数一  state
//						选择计时开始或是结束的参数  
//返回值:返回当前帧率
//功  能:统计运行帧率
float showTime(int state)
{
	static double time0 = 0;
	float fps = 0;
	if(state == COUNT_START)
		time0 = getTickCount();
	else if(state == COUNT_END)
		fps = getTickFrequency()/(getTickCount() - time0);
	return fps;
}

 

posted @ 2016-04-19 22:04  窝窝头HZ  阅读(447)  评论(0)    收藏  举报