利用opencv實現人臉識別
introduction
- 使用弱聯級分類器主要包含兩部分:訓練和檢測,檢測通常使用HAAR或LBP模型,這部分的描述在另外一個文件中,這部分主要介紹弱聯級加速分類器的訓練,主要包括:收集訓練數據,準備或者處理訓練數據,以及進行訓練;
- 使用的工具 opencv_traincascade tool.這個是C++利用opencv2.x和opencv3.x寫的,這個訓練工具支持HAAP/LBP特征,對于訓練的效果,這個主要取決于所用的數據和參數的選擇
- 實現的工具:VS2015+opencv(plus contrib);
question
- 這篇文章是跟一位大神學得大神博客地址和opencv官網,按照步驟寫的,我自己也實踐了;所以就按照自己的理解再寫一點,將自己填的坑記錄一下;
- 做人臉識別需要清楚一些opencv的一些基礎的概念以及圖像處理的一些基礎知識,比如一些很簡單的區別人臉識別和人臉檢測等;
- opencv官方版沒有將一些算法開放,還有一些在contrib中,所以我們想用的一些opencv 的高級一點的算法均在contrib中,這個就需要自己去編譯了,具體的編譯網上有很多的個版本,但一定要找到合適自己電腦的那一個,我將自己的編譯的坑也總結出來了,用得到的可以去看看;建議你剛開始用就自己編譯的加了contrib的opencv,否則訓練模型的時候就會各種問題,各種API不支持的奇葩問題出現;
- 生成標簽的部分用到了python,由于以前沒有接觸過,語法也很不熟悉,需要熟悉這部分來實現標簽的區分;
practice
數據的收集和預處理
- 數據的收集,使用到的數據庫是opencv官方給出的 The AT&T Facedatabase ,又稱之為ORL數據庫,但你下載下來之后發現是由10個文件夾包含的400張pgm的照片,但是在window 上你還打不開看不了這個到底張什么樣,只能通過opencv自己寫個小程序imread()來看看了..
- 收集到別人的數據庫也不可啊,你得需求是讓計算機得認得我啊,所以還需要你自己的照片,拿這些照片和你的照片一起來訓練模型,既然學了opencv那么就自己寫個小程序來實現,在我的前兩篇文章中寫了具體的實現代碼,具體的邏輯就是打開電腦的攝像頭,當按下空格鍵的時候保存當前幀并顯示出來,感興趣的可以關注我的簡書,此片博文的鏈接opencv實現簡單的拍照程序及照片的裁切;
- 這樣收集到自己的圖片了,發現自己的圖片太大,所以需要處理一下,利用opencv自己的模型檢測并分割出人臉,這個地方需要注意的是下載的圖片是92X112,那么你在檢測之后對得到的ROI做一次reSize()即可;這樣就可以得到想要的大小的圖片了;
- 將處理之后的圖片放置在第41文件夾中;這個時候就需要處理at.txt了,這個相當于一個標簽,標注每個圖片代表的是誰,那幾張圖片表示的是同一個人;需要處理這個;
- 對于數據的收集和預處理的總結:
- 這部分沒有什么代碼,但是要深刻理解這里面的一些詞的含義,一些細節性的東西,前期的準備一定要做足,理論知識,工具,數據資料等;我的時間主要浪費在自己編譯opencv+contrib上面了,下載了多個不同版本的vs,安裝且試用了,也利用不同版本的編譯了,下載時間+安裝時間+卸載時間的耗費超大,所以給大家建議,一定要看清楚vs版本自己電腦的配置以及opencv的版本這幾個的搭配再搞;
- 這部分的理論知識:
- 要做人臉識別,首先要做的就是收集數據訓練模型,這個模型就是含有你特征的模型,在識別的時候加載模型看相似度,在一定值范圍之內則判定為同一個人;
- 然后理解cv的每一個API的利用場景,比如:若是要訓練模型接受的圖像必須是灰度圖,且為了減少光照干擾灰度圖必須實現歸一化,若是再做的好點可以在把圖片resize一下;
訓練模型
CSV文件的生成,記錄每張圖片的位置和是誰,這個也就相當于一個標簽,這個csv文件的生成比較麻煩,使用python腳本自動生成;代碼在下面貼出來;
-
訓練模型;csv文件和圖片已經準備好了,接下來就是訓練模型,這個要用到opencv里面的Facerecognizer類,opencv里面的所有的人臉識別模型都來自于這個類,這個類為所有的人臉識別算法提供了通用的借口,這個類包含了接下來的幾個函數:
Moreover every FaceRecognizer supports the: * Training of a FaceRecognizer with FaceRecognizer::train() on a given set of images (your face database!). * Prediction of a given sample image, that means a face. The image is given as a Mat. * Loading/Saving the model state from/to a given XML or YAML. * Setting/Getting labels info, that is storaged as a string. String labels info is useful for keeping names of the recognized people
-
使用facerecoginzer類來訓練模型
Ptr<FaceRecognizer> model = createEigenFaceRecognizer(); model->train(images, labels); model->save("MyFacePCAModel.xml"); Ptr<FaceRecognizer> model1 = createFisherFaceRecognizer(); model1->train(images, labels); model1->save("MyFaceFisherModel.xml"); Ptr<FaceRecognizer> model2 = createLBPHFaceRecognizer(); model2->train(images, labels); model2->save("MyFaceLBPHModel.xml");
-
要訓練模型就需要image和lable了,也就是把剛剛用python生成的at.txt讀出來了,讀取這個文件在cv中使用stringstream和getline;
std::ifstream file(filename.c_str(), ifstream::in); if (!file) { string error_message = "No valid input file was given, please check the given filename."; 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())); } }
-
訓練完了還有比較重要的一部prediction
int predictedLabel = model->predict(testSample); int predictedLabel1 = model1->predict(testSample); int predictedLabel2 = model2->predict(testSample); // 還有一種調用方式,可以獲取結果同時得到閾值: // int predictedLabel = -1; // double confidence = 0.0; // model->predict(testSample, predictedLabel, confidence); string result_message = format("Predicted class = %d / Actual class = %d.", predictedLabel, testLabel); string result_message1 = format("Predicted class = %d / Actual class = %d.", predictedLabel1, testLabel); string result_message2 = format("Predicted class = %d / Actual class = %d.", predictedLabel2, testLabel); cout << result_message << endl; cout << result_message1 << endl; cout << result_message2 << endl;
-
模型訓練的總結
- 主要分為準備數據做csv文件,讀取文件,訓練模型,做預測,這個是主要的步驟,但里面需要注意的點很多;我在上面也分別做了說明
- 下面的代碼分為兩部分,一部分是訓練,另一部分則是一個生成csv文件的python腳本;
源代碼
#include<opencv2\face\facerec.hpp>
#include<opencv2\core.hpp>
#include<opencv2\face.hpp>
#include<opencv2\highgui.hpp>
#include<opencv2\imgproc.hpp>
#include <iostream>
#include <fstream>
#include <sstream>
#include <math.h>
using namespace cv;
using namespace cv::face;
using namespace std;
static Mat norm_0_255(InputArray _src) {
Mat src = _src.getMat();
// 按照不同通道創建和返回一個歸一化后的圖像矩陣:
Mat dst;
switch (src.channels()) {
case 1:
cv::normalize(_src, dst, 0, 255, NORM_MINMAX, CV_8UC1);
break;
case 3:
cv::normalize(_src, dst, 0, 255, NORM_MINMAX, CV_8UC3);
break;
default:
src.copyTo(dst);
break;
}
return dst;
}
//使用CSV文件去讀圖像和標簽,主要使用stringstream和getline方法,這里面涉及到一些常見的C++語法
static 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, please check the given filename.";
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()));
}
}
}
int main()
{
//讀取你的CSV文件路徑.
string fn_csv = "at.txt";
// 2個容器來存放圖像數據和對應的標簽
vector<Mat> images;
vector<int> labels;
// 讀取數據. 如果文件不合法就會出錯
// 輸入的文件名已經有了.
try
{
read_csv(fn_csv, images, labels);
}
catch (cv::Exception& e)
{
cerr << "Error opening file \"" << fn_csv << "\". Reason: " << e.msg << endl;
// 文件有問題,我們啥也做不了了,退出了
exit(1);
}
// 如果沒有讀取到足夠圖片,也退出.
if (images.size() <= 1) {
string error_message = "This demo needs at least 2 images to work. Please add more images to your data set!";
CV_Error(CV_StsError, error_message);
}
// 下面的幾行代碼僅僅是從你的數據集中移除最后一張圖片
//[gm:自然這里需要根據自己的需要修改,他這里簡化了很多問題]
Mat testSample = images[images.size() - 1];
int testLabel = labels[labels.size() - 1];
images.pop_back();
labels.pop_back();
// 下面幾行創建了一個特征臉模型用于人臉識別,
// 通過CSV文件讀取的圖像和標簽訓練它。
//如果你只想保留10個主成分,使用如下代碼
// cv::createEigenFaceRecognizer(10);
//
// 如果你還希望使用置信度閾值來初始化,使用以下語句:
// cv::createEigenFaceRecognizer(10, 123.0);
//
// 如果你使用所有特征并且使用一個閾值,使用以下語句:
// cv::createEigenFaceRecognizer(0, 123.0);
Ptr<BasicFaceRecognizer> model = createEigenFaceRecognizer();
model->train(images, labels);
model->save("MyFacePCAModel.xml");
Ptr<BasicFaceRecognizer> model1 = createFisherFaceRecognizer();
model1->train(images, labels);
model1->save("MyFaceFisherModel.xml");
Ptr<LBPHFaceRecognizer> model2 = createLBPHFaceRecognizer();
model2->train(images, labels);
model2->save("MyFaceLBPHModel.xml");
// 下面對測試圖像進行預測,predictedLabel是預測標簽結果
int predictedLabel = model->predict(testSample);
int predictedLabel1 = model1->predict(testSample);
int predictedLabel2 = model2->predict(testSample);
// 還有一種調用方式,可以獲取結果同時得到閾值:
// int predictedLabel = -1;
// double confidence = 0.0;
// model->predict(testSample, predictedLabel, confidence);
string result_message = format("Predicted class = %d / Actual class = %d.", predictedLabel, testLabel);
string result_message1 = format("Predicted class = %d / Actual class = %d.", predictedLabel1, testLabel);
string result_message2 = format("Predicted class = %d / Actual class = %d.", predictedLabel2, testLabel);
cout << result_message << endl;
cout << result_message1 << endl;
cout << result_message2 << endl;
getchar();
waitKey(0);
return 0;
}
人臉識別
實現理論:這個部分就是做檢測了,打開攝像頭,加載人臉檢測器,檢測出人臉,然后拿這個檢測出來的人臉和模型里面的對比,看看是誰的;原理很簡單但實現出來的結果差強人意;
-
問題點:
- 的確cv實現的比較慢而且卡頓現象比較明顯,python的確是機器學習的利器,而且上手容易
- 使用cv最好使用自己編譯的,官方的這個版本很多的比較好的算法都沒有,自己編譯的時候有很多的坑,我自己整理了一些opencv+contrib+vs編譯的一些問題;
- cv用起來不難,但是理解起來比較難,里面涉及到的算法有點多;很多都很不好理解;
-
代碼實現
#include<opencv2\opencv.hpp> #include<opencv2\face.hpp> #include<iostream> using namespace std; using namespace cv; using namespace cv::face; int main() { VideoCapture cap(0); if (!cap.isOpened()) { return -1; } Mat frame; Mat edges; Mat gray; CascadeClassifier cascade; bool stop = false; //訓練好的文件名稱,放置在可執行文件同目錄下 cascade.load("lbpcascade_frontalface.xml"); Ptr<FaceRecognizer> modelPCA = createEigenFaceRecognizer(); modelPCA->load("MyFacePCAModel.xml"); while (1) { cap >> frame; //建立用于存放人臉的向量容器 vector<Rect> faces(0); cvtColor(frame, gray, CV_BGR2GRAY); //改變圖像大小,使用雙線性差值 //Mat smallImg(cvRound(frame.rows / 1.3), cvRound(frame.cols / 1.3), CV_8UC1); //resize(gray, smallImg, smallImg.size(), 0, 0, INTER_LINEAR); //變換后的圖像進行直方圖均值化處理 equalizeHist(gray, gray); cascade.detectMultiScale(gray, faces, 1.1, 2, 0 //|CV_HAAR_FIND_BIGGEST_OBJECT //|CV_HAAR_DO_ROUGH_SEARCH | CV_HAAR_SCALE_IMAGE, Size(30, 30)); Mat face; Point text_lb; for (size_t i = 0; i < faces.size(); i++) { if (faces[i].height > 0 && faces[i].width > 0) { face = gray(faces[i]); text_lb = Point(faces[i].x, faces[i].y); rectangle(frame, faces[i], Scalar(255, 0, 0), 1, 8, 0); } } Mat face_test; int predictPCA = 0; if (face.rows >= 120) { resize(face, face_test, Size(92, 112)); } //Mat face_test_gray; //cvtColor(face_test, face_test_gray, CV_BGR2GRAY); if (!face_test.empty()) { //測試圖像應該是灰度圖 predictPCA = modelPCA->predict(face_test); } cout << predictPCA << endl; if (predictPCA == 41) { string name = "Lemon"; putText(frame, name, text_lb, FONT_HERSHEY_COMPLEX, 1, Scalar(0, 0, 255)); } else { putText(frame, "unknow", text_lb, FONT_HERSHEY_COMPLEX, 1, Scalar(0, 0, 255)); } imshow("face", frame); waitKey(200); } return 0; }