/********************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;
}