前言
1.前面已經演示過使用OpenCV官方分類器實現人臉檢測并拍照下要用來訓練人臉識別的樣本數據,并生成包含有人臉樣本的列表文件(.txt)文件。
2.我的編程環境是Windows 7 64位,IDE是VS2015,配置了OpenCV3.3與OpenCV_Contrib,Boost 1.66,其中Boost是用來操作文件和目錄用的,是于如果配置以上的環境,可以看我之前寫的博文。
3.OpenCV 自帶了三個人臉識別算法:Eigenfaces,Fisherfaces 、LBPH(和局部二值模式直方圖)。如果想知道它們的工作原理及相互之間的區別,請閱讀 OpenCV 的官方文檔。
4.我在這里只演示如何使用Eigenfaces來實現人臉識別的訓練與測試,如果想測試Fisherfaces和LBPH這兩個識別算法,只要把聲明與初始化類的部分改成對應的算法就可以了。
一、資源準備
1、要訓練之前,應該有兩個數據,一個放著人臉樣本的文件夾,可以從這里下載我上傳好的文件夾。一個是內容里面有人臉樣本絕對路徑加標簽的txt文件,人臉樣本的文件夾如下圖,41這個文件夾是放著我從攝像頭拍下來自己的臉,42這個文件夾是我從網上下載下來的蘆田愛菜(Ashida Mana)的人臉圖像,因為她的表情真的很豐富,差異相對大一些。
存放訓練集的文件夾:
2.然后調用buildList()這個函數,生成內容里有人臉樣本絕對路徑加標簽的txt文件,格式如下圖,前是樣本數據的絕對路徑,分號之后該樣本的標簽名,分號是用來隔開樣本與標簽用的,只是方便后面的文件讀取。
二、訓練代碼
這里我給兩個方式的讀寫文件方式的訓練代碼,一個是從txt文件里讀取樣本路徑和標簽代碼,一個是使用boost把樣本讀到到C++ STL容器的訓練代碼。
1.從txt文件讀取樣本和標簽的代碼,使用這個函數的前提是必須在前面生成內容里有人臉樣本絕對路徑加標簽的txt文件,函數的第第一個參數傳入txt文件的路徑,第二參數是保存模型文件的路徑:
trainFacesTxt()函數代碼:
void trainFacesTxt(string faces_list, string save_model)
{//打開人臉列表文件ifstream face_file(faces_list, ifstream::in);if(!face_file){std::cout << "無法打開訓練集的列表文件!" << endl;return;}string line, path, class_label;vector<Mat> faces;vector<int> labels;char separator = ';';while (getline(face_file, line)){stringstream liness(line);//讀一行getline(liness, path, separator);getline(liness, class_label);if (!path.empty() && !class_label.empty()){//把圖像壓入容器faces.push_back(imread(path, 0));//把標簽壓入容器labels.push_back(atoi(class_label.c_str()));}}//判斷是否為空if (faces.size() < 1 || labels.size() < 1){std::cout << "初始化訓練集....." << std::endl;return;}int height = faces[0].rows;int width = faces[0].cols;std::cout << "訓練集的圖像的高:" << height << "訓練集的圖像的寬:" << width << std::endl;Mat test_sample = faces[faces.size() - 1];int test_label = labels[labels.size() - 1];faces.pop_back();labels.pop_back();//判斷圖像類型for (size_t i = 0; i < faces.size(); i++){if (faces.at(i).type() != CV_8UC1){std::cerr << "圖像的類型必須為CV_8UC1!" << endl;return;}}//檢測尺寸等于正樣本尺寸第一張的尺寸Size positive_image_size = faces[0].size();cout << "正樣本的尺寸是:" << positive_image_size << endl;//遍歷所有樣品,檢測尺寸是否相同for (size_t i = 0; i < faces.size(); i++){if (positive_image_size != faces[i].size()){std::cerr << "所有的樣本的尺寸大小不一,請重新調整好樣本大小!" << endl;return;}}//創建一個人臉識別的類Ptr<BasicFaceRecognizer> model = EigenFaceRecognizer::create();//開始訓練model->train(faces, labels);// recognition faceint predicted_label = model->predict(test_sample);std::cout << "樣本標簽類型為:" << test_label << "預測的樣本標簽為:" << predicted_label << endl;//保存訓練好的模型model->write(save_model);std::cout << "訓練完成!" << endl;
}
2.直接用boost庫從存放路徑讀取,這里就不需要提前生成txt,函數的第一個參數是存放人臉樣本的目錄,第二個是是保存模型的路徑和模型的名字。
trainFacesDir()函數代碼:
void trainFacesDir(string faces_list,string save_model)
{vector<int> labels;vector<Mat> faces;multimap<int,Mat> faces_labels;int index = 0;if (!fs::is_directory(faces_list)){std::cerr << "請輸入一個合法的路徑!" << endl;return;}else if (fs::is_empty(faces_list)){std::cerr << "輸入路徑為空路徑!" << endl;return;}//遞歸遍歷當前目錄下的子文件fs::recursive_directory_iterator begin_iter(faces_list);fs::recursive_directory_iterator end_iter;for (; begin_iter != end_iter; begin_iter++){string file_path = begin_iter->path().string();fs::path file_name(file_path);//判斷是否為目錄if (fs::is_directory(file_name)){//標簽index++;}else{// 讀取文件夾下的文件level 1表示這是一副訓練圖,通過multimap容器來建立由類目名稱到訓練圖的一對多的映射string filename = begin_iter->path().string();//圖像的類型為CV_8CU1Mat temp = imread(filename,0);if (temp.empty()){continue;}pair<int, Mat> p(index, temp);//鍵名可以重復faces_labels.insert(p);}}multimap<int, Mat> ::iterator i = faces_labels.begin();//得到要訓練的數據集與標簽for (; i != faces_labels.end(); i++){labels.push_back((*i).first);faces.push_back((*i).second);}//從最后一個人臉樣品集讀出一張圖像用來測試Mat test_sample =faces[faces.size() - 1];//標簽等于最后人臉樣品標簽int test_label = labels[labels.size() - 1];//從容器里刪掉最后一張faces.pop_back();//標簽減1labels.pop_back();//判斷圖像類型for (size_t i = 0; i < faces.size(); i++){if (faces.at(i).type() != CV_8UC1){std::cerr << "圖像的類型必須為CV_8UC1!" << endl;return;}}//檢測尺寸等于正樣本尺寸第一張的尺寸Size positive_image_size = faces[0].size();cout << "正樣本的尺寸是:" << positive_image_size << endl;//遍歷所有樣品,檢測尺寸是否相同for (size_t i = 0; i < faces.size(); i++){if (positive_image_size != faces[i].size()){std::cerr << "所有的樣本的尺寸大小不一,請重新調整好樣本大小!" << endl;return;}}//創建一個人臉識別的類Ptr<BasicFaceRecognizer> model = EigenFaceRecognizer::create();//開始訓練model->train(faces, labels);// recognition faceint predicted_label = model->predict(test_sample);std::cout << "樣本標簽類型為:" << test_label << "預測的樣本標簽為:" << predicted_label << endl;//保存訓練好的模型model->write(save_model);std::cout << "訓練完成!" << endl;
}
3.運行上面的函數,在傳入的路徑下生成一個xml的模型文件,我這里命名為face_model.xml文件,測試時調用這個文件。
三、測試代碼
測試代碼我也寫兩個函數,一個是從攝像頭讀取,然后識別當前人臉。
1、從攝像頭讀取圖像并識別,函數的第一個函數是傳入的攝像頭索引號,第二個是訓練好人臉模型路徑文件名。
testFace()函數代碼:
void testFace(int cap_index,string model_path)
{//加載一個人臉識別器Ptr<BasicFaceRecognizer> model_test = EigenFaceRecognizer::create();//opencv3.3要用read,要不然會出錯model_test->read(model_path);//加載一個人臉檢測分類器CascadeClassifier faceDetector;faceDetector.load(face_path);//檢測傳入的攝像頭VideoCapture capture(cap_index);if (!capture.isOpened()){std::cerr << "無法打開當前攝像頭!" << endl;return;}Mat frame;namedWindow("faceRecognition", CV_WINDOW_AUTOSIZE);vector<Rect> faces;Mat dst;Mat test_sample;string name;while (capture.read(frame)){//鏡像flip(frame, frame, 1);//檢測人臉faceDetector.detectMultiScale(frame, faces, 1.1, 1, 0, Size(80, 100), Size(380, 400));for (int i = 0; i < faces.size(); i++){Mat roi = frame(faces[i]);cvtColor(roi, dst, COLOR_BGR2GRAY);resize(dst, test_sample, Size(92, 112));int label = 0;label = model_test->predict(test_sample);//輸出檢測到的人臉標簽cout << label << endl;//畫出人臉rectangle(frame, faces[i], Scalar(255, 0, 0), 2, 8, 0);switch (label){case 41:name = "matt";break;case 42:name = "Ashida Mana";break;default:name = "Unknown";break;}putText(frame, name, faces[i].tl(), FONT_HERSHEY_PLAIN, 1.0, Scalar(0, 0, 255), 2, 8);}imshow("faceRecognition", frame);char c = waitKey(10);if (c == 27){break;}}
}
運行結果:
從攝像頭拍下的人臉樣本標簽保存為41,但有時因為人臉的擺動,會出現誤識別現象。
2、從文件夾下讀取圖像并識別,函數的第一個函數是傳入的存放包含人臉的圖像,第二個是訓練好人臉模型路徑文件名。
testFace()函數代碼:
void testFace(string image_path, string model_path)
{//加載一個人臉識別器Ptr<BasicFaceRecognizer> model_test = EigenFaceRecognizer::create();//opencv3.3要用read,要不然會出錯model_test->read(model_path);//加載一個人臉檢測分類器CascadeClassifier faceDetector;faceDetector.load(face_path);Mat frame;namedWindow("faceRecognition", CV_WINDOW_AUTOSIZE);vector<Rect> faces;Mat dst;Mat test_sample;string name;if (!fs::is_directory(image_path)){std::cerr << "請傳入一個合法的非空的路徑!" << endl;return;}fs::directory_iterator begin_iter(image_path);fs::directory_iterator end_iter;for(;begin_iter != end_iter; begin_iter ++){frame = imread(begin_iter->path().string());if (frame.empty()){continue;}//檢測人臉faceDetector.detectMultiScale(frame, faces, 1.1, 1, 0, Size(80, 100), Size(380, 400));for (int i = 0; i < faces.size(); i++){Mat roi = frame(faces[i]);cvtColor(roi, dst, COLOR_BGR2GRAY);resize(dst, test_sample, Size(92, 112));int label = 0;label = model_test->predict(test_sample);//輸出檢測到的人臉標簽cout << label << endl;//畫出人臉rectangle(frame, faces[i], Scalar(255, 0, 0), 2, 8, 0);switch (label){case 41:name = "matt";break;case 42:name = "Ashida Mana";break;default:name = "Unknown";break;}putText(frame, name, faces[i].tl(), FONT_HERSHEY_PLAIN, 1.0, Scalar(0, 0, 255), 2, 8);}imshow("faceRecognition", frame);waitKey(0);}
}
運行結果,識別正確的圖像:
識別不到的:
結語
1.以上的所有關于人臉訓練與識別的代碼演示,只是一個通用的代碼框架,識別的準確率并不能達到可以使用在項目上。要真正達到應用級,好多地方都要優化,如樣本的提取,提取樣本的攝像頭的角度與識別時的使用的攝像頭角度,識別時檢測到人臉之后的相關矯正等等。
2.現在好多深度學習的網絡可以實現出更高的準確率,比如caffe的DeepID等,之后有時間會去寫相關的訓練與應用。
3.關于整個工程的源碼,運行程序時的bug,或者有如何優化的想法都可以加這個群(487350510)互相討論學習。
總結
以上是生活随笔為你收集整理的OpenCV3实现人脸识别(三)——训练与识别自己的人脸数据的全部內容,希望文章能夠幫你解決所遇到的問題。
如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。