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