- Introduction
網上存在很多人臉識別的文章,這篇文章是我的一個作業,重在通過攝像頭實時采集人臉信息,進行人臉檢測和人臉識別,並將識別結果顯示在左上角。
利用 OpenCV 實現一個實時的人臉識別系統,人臉庫采用 ORL FaceDatabase (網上下載) ,另外在數據庫中增加了作業中自帶的20張照片和自己利用攝像頭采集到的10張照片,系統利用攝像頭實時的采集到場景圖像,從中檢測出人臉用方框標出,並利用提供的數據庫進行人臉識別,並在圖像左上角顯示相匹配的數據庫圖片。
- Method
算法流程分兩步,分別是人臉檢測和人臉識別。人臉檢測使用的是 ViolaJones 人臉檢測方法,利用樣本的 Haar-like 特征進行分類器訓練,得到級聯boosted 分類器,加載訓練好的人臉分類器,利用分類器在視頻幀中查找人臉區域;人臉識別利用了局部二進制模式直方圖。
- Haar-like 特征
Haar-like 特征如下圖所示
圖1 Haar-like 特征
- LBPH
人臉識別常用的方法有三種,Eigenfaces、Fisherfaces 和 LBPH;對於高維的圖像空間,我們首先應該進行降維操作。LBP 不把圖像看做高維的矢量,而是通過物體的局部特征來描述。將每個像素和其相鄰像素對比形成局部的結構,把該像素看做中心,並以該值對鄰接像素做閾值處理,如果臨界像素的亮度大於該像素則為 1 否則為 0,這樣每個像素點都可以用一個二進制數來表示,比如一個使用 3*3 臨界點的 LBP 操作如下圖所示:
圖2 LBP
- Implementation
- 識別訓練
利用准備好的數據庫進行識別訓練:首先我們利用Opencv安裝文件中的python腳本create_csv.py建立CSV文件,文件中每條記錄如:orl/s13/2.pgm;12,分號之前是圖片所存路徑,而分號之后是圖片的標簽號,每一組圖片對應着唯一的標簽號;之后利用代碼中的train_data和read_csv函數對數據集進行訓練。使用到的 OpenCV 類和函數有:FaceRecognizer,createLBPHFaceRecognizer
- 人臉檢測
運用Opencv安裝文件中的haarcascade_frontalface_alt.xml文件,使用分類器在視頻幀中查找人臉區域,並用綠色方框標出。用到的 OpenCV 類和函數有:CascadeClassifier,detectMultiScale。
- 人臉識別
讀取訓練好的 yaml文件,對每個監測到的區域的圖像分類,並在視頻幀人臉區域上方顯示分類結果(分類結果顯示為標簽和可信度),在左上角顯示縮略圖。用到的 OpenCV 函數主要有:predict.
- Code
看到評論,大家需要config.h,抱歉事情多添加有些晚,我放在下面了,有什么問題歡迎交流~
#include "opencv2/core/core.hpp" #include "opencv2/contrib/contrib.hpp" #include "opencv2/highgui/highgui.hpp" #include "opencv2/imgproc/imgproc.hpp" #include "opencv2/objdetect/objdetect.hpp" #include <iostream> #include <fstream> #include <sstream> #include <string.h> char *FACES_TXT_PATH = "face.txt"; char *HARR_XML_PATH = "haarcascade_frontalface_alt.xml"; char *FACES_MODEL = "face.yaml"; char *POTRAITS ="potraits.jpg"; int DEVICE_ID = 0;
主文件內容:
1 /*頭文件:*/ 2 #include "opencv2/core/core.hpp" 3 #include "opencv2/contrib/contrib.hpp" 4 #include "opencv2/highgui/highgui.hpp" 5 #include "opencv2/imgproc/imgproc.hpp" 6 #include "opencv2/objdetect/objdetect.hpp" 7 8 #include <iostream> 9 #include <fstream> 10 #include <sstream> 11 #include <string.h> 12 13 char *FACES_TXT_PATH = "face.txt"; 14 char *HARR_XML_PATH = "haarcascade_frontalface_alt.xml"; 15 char *FACES_MODEL = "face.yaml"; 16 char *POTRAITS ="potraits.jpg"; 17 int DEVICE_ID = 0; 18 19 /*主文件*/ 20 #include "config.h" 21 22 using namespace cv; 23 using namespace std; 24 int FACE_WIDHT=92; 25 int FACE_HEIGHT=112; 26 int POTRITE_WIDTH = 100; 27 int POTRITE_HEIGHT = 100; 28 29 static void read_csv(const string& filename, vector<Mat>& images, vector<int>& labels, char separator = ';') { 30 std::ifstream file(filename.c_str(), ifstream::in); 31 if (!file) { 32 string error_message = "找不到文件,請核對路徑"; 33 CV_Error(CV_StsBadArg, error_message); 34 } 35 string line, path, classlabel; 36 while (getline(file, line)) { 37 stringstream liness(line); 38 getline(liness, path, separator); 39 getline(liness, classlabel); 40 if(!path.empty() && !classlabel.empty()) { 41 images.push_back(imread(path, 0)); 42 labels.push_back(atoi(classlabel.c_str())); 43 } 44 } 45 46 } 47 48 /*利用csv文件讀取數據集並訓練對應模型*/ 49 void train_data(String fn_csv) 50 { 51 vector<Mat> images; 52 vector<int> labels; 53 //獲取數據集,如果出錯拋出異常 54 try { 55 read_csv(fn_csv, images, labels); 56 } 57 catch (cv::Exception& e) { 58 cerr << "打開文件失敗 \"" << fn_csv << "\". 原因: " << e.msg << endl; 59 exit(1); 60 } 61 62 // 如果訓練集數量不夠退出 63 if(images.size() <= 1) { 64 string error_message = "訓練集圖片少於2"; 65 CV_Error(CV_StsError, error_message); 66 } 67 68 //訓練模型 69 Ptr<FaceRecognizer> model = createLBPHFaceRecognizer(); 70 model->train(images, labels); 71 model->save(FACES_MODEL); 72 } 73 74 void show_portrait(Mat &potrait, Mat &frame) { 75 int channels = potrait.channels(); 76 int nRows = potrait.rows; 77 int nCols = potrait.cols*channels; 78 79 uchar *p_p, *p_f; 80 for(auto i=0; i<nRows; i++) { 81 p_p = potrait.ptr<uchar>(i); 82 p_f = frame.ptr<uchar>(i); 83 for(auto j=0; j<nCols; j++) { 84 p_f[j*3] = p_p[j]; 85 p_f[j*3+1] = p_p[j+1]; 86 p_f[j*3+2] = p_p[j+2]; 87 } 88 } 89 90 } 91 92 void makePotraitImages(vector<Mat> potraits) { 93 int rows = potraits.size()/6; 94 if(potraits.size()-rows *6>0)rows++; 95 rows *= POTRITE_HEIGHT; 96 int cols = 6*POTRITE_HEIGHT; 97 Mat potrait_s = Mat(rows,cols,CV_8UC3); 98 rows = POTRITE_HEIGHT; 99 cols = POTRITE_WIDTH; 100 uchar *p_ps, *p_p; 101 for(auto i=0; i<potraits.size(); i++) { 102 for(auto j=0; j<rows; j++) { 103 p_ps = potrait_s.ptr<uchar>(i/6*POTRITE_HEIGHT+j)+3*(i%6)*POTRITE_WIDTH; 104 p_p = potraits[i].ptr<uchar>(j); 105 for(auto k=0; k<cols; k++) { 106 p_ps[k*3] = p_p[k]; 107 p_ps[k*3+1] = p_p[k+1]; 108 p_ps[k*3+2] = p_p[k+2]; 109 } 110 } 111 } 112 imwrite(POTRAITS, potrait_s); 113 } 114 115 void loadPortraits(const string& filename, vector<Mat>& images, char separator = ';') { 116 string fn_csv = string(FACES_TXT_PATH); 117 std::ifstream file(fn_csv.c_str(), ifstream::in); 118 if (!file) { 119 string error_message = "找不到文件,請核對路徑."; 120 CV_Error(CV_StsBadArg, error_message); 121 } 122 string line, path, classlabel; 123 int label(0); 124 while (getline(file, line)) { 125 stringstream liness(line); 126 getline(liness, path, separator); 127 getline(liness, classlabel); 128 if(!path.empty() && !classlabel.empty()) { 129 if(atoi(classlabel.c_str()) != label) { 130 Mat potrait = imread(path, 0); 131 resize(potrait, potrait,Size(POTRITE_WIDTH, POTRITE_HEIGHT)); 132 images.push_back(potrait); 133 label = atoi(classlabel.c_str()); 134 } 135 } 136 } 137 } 138 139 int main(int argc, const char *argv[]) { 140 // 保存圖像和對應標簽的向量,要求同一個人的圖像必須對應相同的標簽 141 string fn_csv = string(FACES_TXT_PATH); 142 string fn_haar = string(HARR_XML_PATH); 143 144 Ptr<FaceRecognizer> model = createLBPHFaceRecognizer(); 145 FileStorage model_file(FACES_MODEL, FileStorage::READ); 146 if(!model_file.isOpened()){ 147 cout<<"無法找到模型,訓練中..."<<endl; 148 train_data(fn_csv);//訓練數據集,1表示EigenFace 2表示FisherFace 3表示LBPHFace 149 } 150 model->load(model_file); 151 model_file.release(); 152 vector<Mat> potraits; 153 loadPortraits(FACES_MODEL,potraits); 154 makePotraitImages(potraits); 155 CascadeClassifier haar_cascade; 156 haar_cascade.load(fn_haar); 157 158 VideoCapture cap(DEVICE_ID); 159 if(!cap.isOpened()) { 160 cerr << "設備 " << DEVICE_ID << "無法打開" << endl; 161 return -1; 162 } 163 164 Mat frame; 165 for(;;) { 166 cap >> frame; 167 if(!frame.data)continue; 168 // 拷貝現有frame 169 Mat original = frame.clone(); 170 // 灰度化 171 Mat gray; 172 cvtColor(original, gray, CV_BGR2GRAY); 173 // 識別frame中的人臉 174 vector< Rect_<int> > faces; 175 haar_cascade.detectMultiScale(gray, faces); 176 177 if(faces.size() != 0) 178 { 179 int max_area_rect=0; 180 for(int i = 0; i < 1; i++) { 181 if(faces[i].area() > faces[max_area_rect].area()){ 182 max_area_rect = i; 183 } 184 185 } 186 187 // 順序處理 188 Rect face_i = faces[max_area_rect]; 189 190 Mat face = gray(face_i); 191 rectangle(original, face_i, CV_RGB(0, 255,0), 1); 192 int pridicted_label = -1; 193 double predicted_confidence = 0.0; 194 model->predict(face, pridicted_label, predicted_confidence); 195 string result_text = format("Prediction = %d confidence=%f", pridicted_label, predicted_confidence); 196 int text_x = std::max(face_i.tl().x - 10, 0); 197 int text_y = std::max(face_i.tl().y - 10, 0); 198 putText(original,result_text, Point(text_x, text_y),FONT_HERSHEY_PLAIN, 1.0, CV_RGB(0,255,0), 2.0); 199 if(pridicted_label >0) 200 show_portrait(potraits[pridicted_label], original); 201 } 202 // 顯示結果: 203 imshow("face_recognizer", original); 204 205 char key = (char) waitKey(20); 206 if(key == 32) 207 exit(0);; 208 } 209 return 0; 210 }
- Experiment
圖3 結果展示
圖4 人臉庫拼圖