前段時間使用
OpenCV的庫函數實現了人臉檢測和人臉識別,筆者的實驗環境為VS2010+OpenCV2.4.4,
opencv的環境配置網上有很多,不再贅述。檢測的代碼網上很多,記不清楚從哪兒copy的了,識別的代碼是從OpenCV官網上找到的:http://docs.opencv.org/trunk/modules/contrib/doc/facerec/facerec_api.html
需要注意的是,opencv的FaceRecogizer目前有三個類實現了它,特征臉和fisherface方法最少訓練圖像為兩張,而LBP可以單張圖像訓練。本人的實驗采用的圖片是100x100大小的,所以如果要添加自己的圖像進行識別的話務必調整為100x100,不然會報錯。當然在recog_and_draw這個函數里,筆者也將每次檢測到的人臉進行了保存,拖出來重命名就可以,路徑自己找吧。使用不同的方法識別時,其閾值設置也不同,LBP大概在100,其他兩種方法大概在1000。本人的代碼已共享,下載鏈接:http://download.csdn.net/detail/u010944555/6749725
ps:有人說代碼的檢測率不高,其實可以歸結為兩方面的原因,第一人臉檢測率不高,這個可以通過嵌套檢測嘴角、眼睛等來降低,或者背景、光照固定的話可以通過圖像差分來解決;第二是識別方法本身的問題,如果想提高識別率,可以添加多張不同姿態、光照下的人臉作為訓練的樣本,如果有時間的話可以在采集圖像時給出一個人臉框,引導用戶對齊人臉進行采集,三星手機解除鎖屏就有這么一個功能。
效果圖:
廢話不多說,上傳代碼。
main:
- #include "stdafx.h"
- #include "cv.h"
- #include "highgui.h"
- #include <stdio.h>
- #include <stdlib.h>
- #include <string.h>
- #include <assert.h>
- #include <math.h>
- #include <float.h>
- #include <limits.h>
- #include <time.h>
- #include <ctype.h>
- #include <opencv2\contrib\contrib.hpp>
- #include <opencv2\core\core.hpp>
- #include <opencv2\highgui\highgui.hpp>
- #include <iostream>
- #include <fstream>
- #include <sstream>
- #include "detect_recog.h"
- using namespace std;
- using namespace cv;
- #ifdef _EiC
- #define WIN32
- #endif
- CvMemStorage* storage = 0;
- CvHaarClassifierCascade* cascade = 0;
- CvHaarClassifierCascade* nested_cascade = 0;
- int use_nested_cascade = 0;
- const char* cascade_name =
- "./data/haarcascade_frontalface_alt.xml";//別人已經訓練好的人臉檢測xml數據
- const char* nested_cascade_name =
- "./data/haarcascade_eye_tree_eyeglasses.xml";
- CvCapture* capture = 0;
- IplImage *frame, *frame_copy = 0;
- IplImage *image = 0;
- const char* scale_opt = "--scale="; // 分類器選項指示符號
- int scale_opt_len = (int)strlen(scale_opt);
- const char* cascade_opt = "--cascade=";
- int cascade_opt_len = (int)strlen(cascade_opt);
- const char* nested_cascade_opt = "--nested-cascade";
- int nested_cascade_opt_len = (int)strlen(nested_cascade_opt);
- double scale = 1;
- int num_components = 9;
- double facethreshold = 9.0;
- //opencv的FaceRecogizer目前有三個類實現了他,特征臉和fisherface方法最少訓練圖像為兩張,而LBP可以單張圖像訓練
- //cv::Ptr<cv::FaceRecognizer> model = cv::createEigenFaceRecognizer();
- //cv::Ptr<cv::FaceRecognizer> model = cv::createFisherFaceRecognizer();
- cv::Ptr<cv::FaceRecognizer> model = cv::createLBPHFaceRecognizer();//LBP的這個方法在單個人臉驗證方面效果最好
- vector<Mat> images;//兩個容器images,labels來存放圖像數據和對應的標簽
- vector<int> labels;
- int main( int argc, char** argv )
- {
- cascade = (CvHaarClassifierCascade*)cvLoad(cascade_name, 0, 0, 0); //加載分類器
- if(!cascade)
- {
- fprintf( stderr, "ERROR: Could not load classifier cascade\n" );
- getchar();
- return -1;
- }
- model->set("threshold", 2100.0);
- string output_folder;
- output_folder = string("./einfacedata");
- //讀取你的CSV文件路徑
- string fn_csv = string("./einfacedata/at.txt");
- try
- {
- //通過./einfacedata/at.txt這個文件讀取里面的訓練圖像和類別標簽
- read_csv(fn_csv, images, labels);
- }
- catch(cv::Exception &e)
- {
- cerr<<"Error opening file "<<fn_csv<<". Reason: "<<e.msg<<endl;
- exit(1);
- }
- /*
- //read_img這個函數直接從einfacedata/trainingdata目錄下讀取圖像數據並默認將圖像置為0
- //所以如果用這個函數只能用來單個人臉驗證
- if(!read_img(images, labels))
- {
- cout<< "Error in reading images!";
- images.clear();
- labels.clear();
- return 0;
- }
- */
- cout << images.size() << ":" << labels.size()<<endl;
- //如果沒有讀到足夠的圖片,就退出
- if(images.size() <= 2)
- {
- string error_message = "This demo needs at least 2 images to work.";
- CV_Error(CV_StsError, error_message);
- }
- //得到第一張照片的高度,在下面對圖像變形到他們原始大小時需要
- //int height = images[0].rows;
- //移除最后一張圖片,用於做測試
- //Mat testSample = images[images.size() - 1];
- //cv::imshow("testSample", testSample);
- //int testLabel = labels[labels.size() - 1];
- //images.pop_back();
- //labels.pop_back();
- //下面創建一個特征臉模型用於人臉識別,
- // 通過CSV文件讀取的圖像和標簽訓練它。
- //進行訓練
- model->train(images, labels);
- storage = cvCreateMemStorage(0); // 創建內存存儲器
- capture = cvCaptureFromCAM(0); // 創建視頻讀取結構
- cvNamedWindow( "result", 1 );
- if( capture ) // 如過是視頻或攝像頭采集圖像,則循環處理每一幀
- {
- for(;;)
- {
- if( !cvGrabFrame( capture ))
- break;
- frame = cvRetrieveFrame( capture );
- if( !frame )
- break;
- if( !frame_copy )
- frame_copy = cvCreateImage( cvSize(640,480),IPL_DEPTH_8U, frame->nChannels );
- if( frame->origin == IPL_ORIGIN_TL )
- cvCopy( frame, frame_copy, 0 );
- else
- cvFlip( frame, frame_copy, 0 );
- //detect_and_draw( frame_copy ); // 如果調用這個函數,只是實現人臉檢測
- //cout << frame_copy->width << "x" << frame_copy->height << endl;
- recog_and_draw( frame_copy );//該函數實現人臉檢測和識別
- if( cvWaitKey( 100 ) >= 0 )//esc鍵值好像是100
- goto _cleanup_;
- }
- cvWaitKey(0);
- _cleanup_: // 標記使用,在匯編里用過,C語言,我還沒見用過
- cvReleaseImage( &frame_copy );
- cvReleaseCapture( &capture );
- }
- cvDestroyWindow("result");
- return 0;
- }
detect_recog.cpp:
- #include "stdafx.h"
- #include "cv.h"
- #include "highgui.h"
- #include <stdio.h>
- #include <stdlib.h>
- #include <string.h>
- #include <assert.h>
- #include <math.h>
- #include <float.h>
- #include <limits.h>
- #include <time.h>
- #include <ctype.h>
- #include "detect_recog.h"
- #include <opencv2\contrib\contrib.hpp>
- #include <opencv2\core\core.hpp>
- #include <opencv2\highgui\highgui.hpp>
- #include <iostream>
- #include <fstream>
- #include <sstream>
- #include <stdio.h>
- #include <io.h>
- #include <direct.h>
- using namespace std;
- using namespace cv;
- //檢測並圈出人臉,並將檢測到的人臉進行判斷屬於訓練圖像中的哪一類
- void recog_and_draw( IplImage* img )
- {
- static CvScalar colors[] =
- {
- {{0,0,255}},
- {{0,128,255}},
- {{0,255,255}},
- {{0,255,0}},
- {{255,128,0}},
- {{255,255,0}},
- {{255,0,0}},
- {{255,0,255}}
- };
- IplImage *gray, *small_img;
- int i, j;
- gray = cvCreateImage( cvSize(img->width,img->height), 8, 1 );
- small_img = cvCreateImage( cvSize( cvRound (img->width/scale),
- cvRound (img->height/scale)), 8, 1 );
- cvCvtColor( img, gray, CV_BGR2GRAY ); // 彩色RGB圖像轉為灰度圖像
- cvResize( gray, small_img, CV_INTER_LINEAR );
- cvEqualizeHist( small_img, small_img ); // 直方圖均衡化
- cvClearMemStorage( storage );
- if( cascade )
- {
- double t = (double)cvGetTickCount();
- CvSeq* faces = cvHaarDetectObjects( small_img, cascade, storage,
- 1.1, 2, 0
- //|CV_HAAR_FIND_BIGGEST_OBJECT
- //|CV_HAAR_DO_ROUGH_SEARCH
- |CV_HAAR_DO_CANNY_PRUNING
- //|CV_HAAR_SCALE_IMAGE
- ,
- cvSize(30, 30) );
- t = (double)cvGetTickCount() - t; // 統計檢測使用時間
- //printf( "detection time = %gms\n", t/((double)cvGetTickFrequency()*1000.) );
- for( i = 0; i < (faces ? faces->total : 0); i++ )
- {
- CvRect* r = (CvRect*)cvGetSeqElem( faces, i ); // 將faces數據從CvSeq轉為CvRect
- CvMat small_img_roi;
- CvSeq* nested_objects;
- CvPoint center;
- CvScalar color = colors[i%8]; // 使用不同顏色繪制各個face,共八種色
- int radius;
- center.x = cvRound((r->x + r->width*0.5)*scale); // 找出faces中心
- center.y = cvRound((r->y + r->height*0.5)*scale);
- radius = cvRound((r->width + r->height)*0.25*scale);
- cvGetSubRect( small_img, &small_img_roi, *r );
- //截取檢測到的人臉區域作為識別的圖像
- IplImage *result;
- CvRect roi;
- roi = *r;
- result = cvCreateImage( cvSize(r->width, r->height), img->depth, img->nChannels );
- cvSetImageROI(img,roi);
- // 創建子圖像
- cvCopy(img,result);
- cvResetImageROI(img);
- IplImage *resizeRes;
- CvSize dst_cvsize;
- dst_cvsize.width=(int)(100);
- dst_cvsize.height=(int)(100);
- resizeRes=cvCreateImage(dst_cvsize,result->depth,result->nChannels);
- //檢測到的區域可能不是100x100大小,所以需要插值處理到統一大小,圖像的大小可以自己指定的
- cvResize(result,resizeRes,CV_INTER_NN);
- IplImage* img1 = cvCreateImage(cvGetSize(resizeRes), IPL_DEPTH_8U, 1);//創建目標圖像
- cvCvtColor(resizeRes,img1,CV_BGR2GRAY);//cvCvtColor(src,des,CV_BGR2GRAY)
- cvShowImage( "resize", resizeRes );
- cvCircle( img, center, radius, color, 3, 8, 0 ); // 從中心位置畫圓,圈出臉部區域
- int predictedLabel = -1;
- Mat test = img1;
- //images[images.size() - 1] = test;
- model->train(images, labels);
- //如果調用read_img函數時 chdir將默認目錄做了更改,所以output.jpg自己找一下吧
- imwrite("../ouput.jpg",test);
- //在這里對人臉進行判別
- double predicted_confidence = 0.0;
- model->predict(test,predictedLabel,predicted_confidence);
- if(predictedLabel == 0)
- cvText(img, "yes", r->x+r->width*0.5, r->y);
- else
- cvText(img, "no", r->x+r->width*0.5, r->y);
- //cout << "predict:"<<model->predict(test) << endl;
- cout << "predict:"<< predictedLabel << "\nconfidence:" << predicted_confidence << endl;
- if( !nested_cascade )
- continue;
- nested_objects = cvHaarDetectObjects( &small_img_roi, nested_cascade, storage,
- 1.1, 2, 0
- //|CV_HAAR_FIND_BIGGEST_OBJECT
- //|CV_HAAR_DO_ROUGH_SEARCH
- //|CV_HAAR_DO_CANNY_PRUNING
- //|CV_HAAR_SCALE_IMAGE
- ,
- cvSize(0, 0) );
- for( j = 0; j < (nested_objects ? nested_objects->total : 0); j++ )
- {
- CvRect* nr = (CvRect*)cvGetSeqElem( nested_objects, j );
- center.x = cvRound((r->x + nr->x + nr->width*0.5)*scale);
- center.y = cvRound((r->y + nr->y + nr->height*0.5)*scale);
- radius = cvRound((nr->width + nr->height)*0.25*scale);
- cvCircle( img, center, radius, color, 3, 8, 0 );
- }
- }
- }
- cvShowImage( "result", img );
- cvReleaseImage( &gray );
- cvReleaseImage( &small_img );
- }
- void cvText(IplImage* img, const char* text, int x, int y)
- {
- CvFont font;
- double hscale = 1.0;
- double vscale = 1.0;
- int linewidth = 2;
- cvInitFont(&font,CV_FONT_HERSHEY_SIMPLEX | CV_FONT_ITALIC,hscale,vscale,0,linewidth);
- CvScalar textColor =cvScalar(0,255,255);
- CvPoint textPos =cvPoint(x, y);
- cvPutText(img, text, textPos, &font,textColor);
- }
- Mat norm_0_255(cv::InputArray _src)
- {
- Mat src = _src.getMat();
- Mat dst;
- switch(src.channels())
- {
- case 1:
- cv::normalize(_src, dst, 0, 255, cv::NORM_MINMAX, CV_8UC1);
- break;
- case 3:
- cv::normalize(_src, dst, 0, 255, cv::NORM_MINMAX, CV_8UC3);
- break;
- default:
- src.copyTo(dst);
- break;
- }
- return dst;
- }
- //讀取文件中的圖像數據和類別,存入images和labels這兩個容器
- void read_csv(const string &filename, vector<Mat> &images, vector<int> &labels, char separator)
- {
- std::ifstream file(filename.c_str(), ifstream::in);
- if(!file)
- {
- string error_message = "No valid input file was given.";
- CV_Error(CV_StsBadArg, error_message);
- }
- string line, path, classlabel;
- while(getline(file, line))
- {
- stringstream liness(line);
- getline(liness, path, separator); //遇到分號就結束
- getline(liness, classlabel); //繼續從分號后面開始,遇到換行結束
- if(!path.empty() && !classlabel.empty())
- {
- images.push_back(imread(path, 0));
- labels.push_back(atoi(classlabel.c_str()));
- }
- }
- }
- bool read_img(vector<Mat> &images, vector<int> &labels)
- {
- long file;
- struct _finddata_t find;
- _chdir("./einfacedata/trainingdata/");
- if((file=_findfirst("*.*", &find))==-1L) {
- //printf("空白!/n");
- return false;
- }
- //fileNum = 0;
- //strcpy(fileName[fileNum], find.name);
- int i = 0;
- while(_findnext(file, &find)==0)
- {
- if(i == 0)
- {
- i++;
- continue;
- }
- images.push_back(imread(find.name, 0));
- labels.push_back(0);
- cout << find.name << endl;
- }
- _findclose(file);
- return true;
- }
- // 只是檢測人臉,並將人臉圈出
- void detect_and_draw( IplImage* img )
- {
- static CvScalar colors[] =
- {
- {{0,0,255}},
- {{0,128,255}},
- {{0,255,255}},
- {{0,255,0}},
- {{255,128,0}},
- {{255,255,0}},
- {{255,0,0}},
- {{255,0,255}}
- };
- IplImage *gray, *small_img;
- int i, j;
- gray = cvCreateImage( cvSize(img->width,img->height), 8, 1 );
- small_img = cvCreateImage( cvSize( cvRound (img->width/scale),
- cvRound (img->height/scale)), 8, 1 );
- cvCvtColor( img, gray, CV_BGR2GRAY ); // 彩色RGB圖像轉為灰度圖像
- cvResize( gray, small_img, CV_INTER_LINEAR );
- cvEqualizeHist( small_img, small_img ); // 直方圖均衡化
- cvClearMemStorage( storage );
- if( cascade )
- {
- double t = (double)cvGetTickCount();
- CvSeq* faces = cvHaarDetectObjects( small_img, cascade, storage,
- 1.1, 2, 0
- //|CV_HAAR_FIND_BIGGEST_OBJECT
- //|CV_HAAR_DO_ROUGH_SEARCH
- |CV_HAAR_DO_CANNY_PRUNING
- //|CV_HAAR_SCALE_IMAGE
- ,
- cvSize(30, 30) );
- t = (double)cvGetTickCount() - t; // 統計檢測使用時間
- printf( "detection time = %gms\n", t/((double)cvGetTickFrequency()*1000.) );
- for( i = 0; i < (faces ? faces->total : 0); i++ )
- {
- CvRect* r = (CvRect*)cvGetSeqElem( faces, i ); // 將faces數據從CvSeq轉為CvRect
- CvMat small_img_roi;
- CvSeq* nested_objects;
- CvPoint center;
- CvScalar color = colors[i%8]; // 使用不同顏色繪制各個face,共八種色
- int radius;
- center.x = cvRound((r->x + r->width*0.5)*scale); // 找出faces中心
- center.y = cvRound((r->y + r->height*0.5)*scale);
- radius = cvRound((r->width + r->height)*0.25*scale);
- cvCircle( img, center, radius, color, 3, 8, 0 ); // 從中心位置畫圓,圈出臉部區域
- if( !nested_cascade )
- continue;
- cvGetSubRect( small_img, &small_img_roi, *r );
- nested_objects = cvHaarDetectObjects( &small_img_roi, nested_cascade, storage,
- 1.1, 2, 0
- //|CV_HAAR_FIND_BIGGEST_OBJECT
- //|CV_HAAR_DO_ROUGH_SEARCH
- //|CV_HAAR_DO_CANNY_PRUNING
- //|CV_HAAR_SCALE_IMAGE
- ,cvSize(0, 0) );
- for( j = 0; j < (nested_objects ? nested_objects->total : 0); j++ )
- {
- CvRect* nr = (CvRect*)cvGetSeqElem( nested_objects, j );
- center.x = cvRound((r->x + nr->x + nr->width*0.5)*scale);
- center.y = cvRound((r->y + nr->y + nr->height*0.5)*scale);
- radius = cvRound((nr->width + nr->height)*0.25*scale);
- cvCircle( img, center, radius, color, 3, 8, 0 );
- }
- }
- }
- cvShowImage( "result", img );
- cvReleaseImage( &gray );
- cvReleaseImage( &small_img );
- }
detect_recog.h:
- #include "stdafx.h"
- #include "cv.h"
- #include "highgui.h"
- #include <stdio.h>
- #include <stdlib.h>
- #include <string.h>
- #include <assert.h>
- #include <math.h>
- #include <float.h>
- #include <limits.h>
- #include <time.h>
- #include <ctype.h>
- //////////////////////////////////s///////////////////////////////////
- #include <opencv2\contrib\contrib.hpp>
- #include <opencv2\core\core.hpp>
- #include <opencv2\highgui\highgui.hpp>
- #include <iostream>
- #include <fstream>
- #include <sstream>
- using namespace std;
- using namespace cv;
- #ifndef DETECT_RECOG_H
- #define DETECT_RECOG_H
- extern CvMemStorage* storage;
- extern CvHaarClassifierCascade* cascade;
- extern CvHaarClassifierCascade* nested_cascade;
- extern int use_nested_cascade;
- extern const char* cascade_name;
- extern const char* nested_cascade_name;
- extern double scale;
- extern cv::Ptr<cv::FaceRecognizer> model;
- extern vector<Mat> images;
- extern vector<int> labels;
- void detect_and_draw( IplImage* img ); // 檢測和繪畫
- void recog_and_draw( IplImage* img ); // 檢測和繪畫
- void read_csv(const string &filename, vector<Mat> &images, vector<int> &labels, char separator = ';');
- bool read_img(vector<Mat> &images, vector<int> &labels);
- Mat norm_0_255(cv::InputArray _src);
- void cvText(IplImage* img, const char* text, int x, int y);
- #endif
