利用opencv實(shí)現(xiàn)人臉識(shí)別
introduction
- 使用弱聯(lián)級(jí)分類器主要包含兩部分:訓(xùn)練和檢測,檢測通常使用HAAR或LBP模型,這部分的描述在另外一個(gè)文件中,這部分主要介紹弱聯(lián)級(jí)加速分類器的訓(xùn)練,主要包括:收集訓(xùn)練數(shù)據(jù),準(zhǔn)備或者處理訓(xùn)練數(shù)據(jù),以及進(jìn)行訓(xùn)練;
- 使用的工具 opencv_traincascade tool.這個(gè)是C++利用opencv2.x和opencv3.x寫的,這個(gè)訓(xùn)練工具支持HAAP/LBP特征,對于訓(xùn)練的效果,這個(gè)主要取決于所用的數(shù)據(jù)和參數(shù)的選擇
- 實(shí)現(xiàn)的工具:VS2015+opencv(plus contrib);
question
- 這篇文章是跟一位大神學(xué)得大神博客地址和opencv官網(wǎng),按照步驟寫的,我自己也實(shí)踐了;所以就按照自己的理解再寫一點(diǎn),將自己填的坑記錄一下;
- 做人臉識(shí)別需要清楚一些opencv的一些基礎(chǔ)的概念以及圖像處理的一些基礎(chǔ)知識(shí),比如一些很簡單的區(qū)別人臉識(shí)別和人臉檢測等;
- opencv官方版沒有將一些算法開放,還有一些在contrib中,所以我們想用的一些opencv 的高級(jí)一點(diǎn)的算法均在contrib中,這個(gè)就需要自己去編譯了,具體的編譯網(wǎng)上有很多的個(gè)版本,但一定要找到合適自己電腦的那一個(gè),我將自己的編譯的坑也總結(jié)出來了,用得到的可以去看看;建議你剛開始用就自己編譯的加了contrib的opencv,否則訓(xùn)練模型的時(shí)候就會(huì)各種問題,各種API不支持的奇葩問題出現(xiàn);
- 生成標(biāo)簽的部分用到了python,由于以前沒有接觸過,語法也很不熟悉,需要熟悉這部分來實(shí)現(xiàn)標(biāo)簽的區(qū)分;
practice
數(shù)據(jù)的收集和預(yù)處理
- 數(shù)據(jù)的收集,使用到的數(shù)據(jù)庫是opencv官方給出的 The AT&T Facedatabase ,又稱之為ORL數(shù)據(jù)庫,但你下載下來之后發(fā)現(xiàn)是由10個(gè)文件夾包含的400張pgm的照片,但是在window 上你還打不開看不了這個(gè)到底張什么樣,只能通過opencv自己寫個(gè)小程序imread()來看看了..
- 收集到別人的數(shù)據(jù)庫也不可啊,你得需求是讓計(jì)算機(jī)得認(rèn)得我啊,所以還需要你自己的照片,拿這些照片和你的照片一起來訓(xùn)練模型,既然學(xué)了opencv那么就自己寫個(gè)小程序來實(shí)現(xiàn),在我的前兩篇文章中寫了具體的實(shí)現(xiàn)代碼,具體的邏輯就是打開電腦的攝像頭,當(dāng)按下空格鍵的時(shí)候保存當(dāng)前幀并顯示出來,感興趣的可以關(guān)注我的簡書,此片博文的鏈接opencv實(shí)現(xiàn)簡單的拍照程序及照片的裁切;
- 這樣收集到自己的圖片了,發(fā)現(xiàn)自己的圖片太大,所以需要處理一下,利用opencv自己的模型檢測并分割出人臉,這個(gè)地方需要注意的是下載的圖片是92X112,那么你在檢測之后對得到的ROI做一次reSize()即可;這樣就可以得到想要的大小的圖片了;
- 將處理之后的圖片放置在第41文件夾中;這個(gè)時(shí)候就需要處理at.txt了,這個(gè)相當(dāng)于一個(gè)標(biāo)簽,標(biāo)注每個(gè)圖片代表的是誰,那幾張圖片表示的是同一個(gè)人;需要處理這個(gè);
- 對于數(shù)據(jù)的收集和預(yù)處理的總結(jié):
- 這部分沒有什么代碼,但是要深刻理解這里面的一些詞的含義,一些細(xì)節(jié)性的東西,前期的準(zhǔn)備一定要做足,理論知識(shí),工具,數(shù)據(jù)資料等;我的時(shí)間主要浪費(fèi)在自己編譯opencv+contrib上面了,下載了多個(gè)不同版本的vs,安裝且試用了,也利用不同版本的編譯了,下載時(shí)間+安裝時(shí)間+卸載時(shí)間的耗費(fèi)超大,所以給大家建議,一定要看清楚vs版本自己電腦的配置以及opencv的版本這幾個(gè)的搭配再搞;
- 這部分的理論知識(shí):
- 要做人臉識(shí)別,首先要做的就是收集數(shù)據(jù)訓(xùn)練模型,這個(gè)模型就是含有你特征的模型,在識(shí)別的時(shí)候加載模型看相似度,在一定值范圍之內(nèi)則判定為同一個(gè)人;
- 然后理解cv的每一個(gè)API的利用場景,比如:若是要訓(xùn)練模型接受的圖像必須是灰度圖,且為了減少光照干擾灰度圖必須實(shí)現(xiàn)歸一化,若是再做的好點(diǎn)可以在把圖片resize一下;
訓(xùn)練模型
CSV文件的生成,記錄每張圖片的位置和是誰,這個(gè)也就相當(dāng)于一個(gè)標(biāo)簽,這個(gè)csv文件的生成比較麻煩,使用python腳本自動(dòng)生成;代碼在下面貼出來;
-
訓(xùn)練模型;csv文件和圖片已經(jīng)準(zhǔn)備好了,接下來就是訓(xùn)練模型,這個(gè)要用到opencv里面的Facerecognizer類,opencv里面的所有的人臉識(shí)別模型都來自于這個(gè)類,這個(gè)類為所有的人臉識(shí)別算法提供了通用的借口,這個(gè)類包含了接下來的幾個(gè)函數(shù):
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類來訓(xùn)練模型
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"); -
要訓(xùn)練模型就需要image和lable了,也就是把剛剛用python生成的at.txt讀出來了,讀取這個(gè)文件在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())); } } -
訓(xùn)練完了還有比較重要的一部prediction
int predictedLabel = model->predict(testSample); int predictedLabel1 = model1->predict(testSample); int predictedLabel2 = model2->predict(testSample); // 還有一種調(diào)用方式,可以獲取結(jié)果同時(shí)得到閾值: // 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; -
模型訓(xùn)練的總結(jié)
- 主要分為準(zhǔn)備數(shù)據(jù)做csv文件,讀取文件,訓(xùn)練模型,做預(yù)測,這個(gè)是主要的步驟,但里面需要注意的點(diǎn)很多;我在上面也分別做了說明
- 下面的代碼分為兩部分,一部分是訓(xùn)練,另一部分則是一個(gè)生成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();
// 按照不同通道創(chuàng)建和返回一個(gè)歸一化后的圖像矩陣:
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文件去讀圖像和標(biāo)簽,主要使用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個(gè)容器來存放圖像數(shù)據(jù)和對應(yīng)的標(biāo)簽
vector<Mat> images;
vector<int> labels;
// 讀取數(shù)據(jù). 如果文件不合法就會(huì)出錯(cuò)
// 輸入的文件名已經(jīng)有了.
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);
}
// 下面的幾行代碼僅僅是從你的數(shù)據(jù)集中移除最后一張圖片
//[gm:自然這里需要根據(jù)自己的需要修改,他這里簡化了很多問題]
Mat testSample = images[images.size() - 1];
int testLabel = labels[labels.size() - 1];
images.pop_back();
labels.pop_back();
// 下面幾行創(chuàng)建了一個(gè)特征臉模型用于人臉識(shí)別,
// 通過CSV文件讀取的圖像和標(biāo)簽訓(xùn)練它。
//如果你只想保留10個(gè)主成分,使用如下代碼
// cv::createEigenFaceRecognizer(10);
//
// 如果你還希望使用置信度閾值來初始化,使用以下語句:
// cv::createEigenFaceRecognizer(10, 123.0);
//
// 如果你使用所有特征并且使用一個(gè)閾值,使用以下語句:
// 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");
// 下面對測試圖像進(jìn)行預(yù)測,predictedLabel是預(yù)測標(biāo)簽結(jié)果
int predictedLabel = model->predict(testSample);
int predictedLabel1 = model1->predict(testSample);
int predictedLabel2 = model2->predict(testSample);
// 還有一種調(diào)用方式,可以獲取結(jié)果同時(shí)得到閾值:
// 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;
}
人臉識(shí)別
實(shí)現(xiàn)理論:這個(gè)部分就是做檢測了,打開攝像頭,加載人臉檢測器,檢測出人臉,然后拿這個(gè)檢測出來的人臉和模型里面的對比,看看是誰的;原理很簡單但實(shí)現(xiàn)出來的結(jié)果差強(qiáng)人意;
-
問題點(diǎn):
- 的確cv實(shí)現(xiàn)的比較慢而且卡頓現(xiàn)象比較明顯,python的確是機(jī)器學(xué)習(xí)的利器,而且上手容易
- 使用cv最好使用自己編譯的,官方的這個(gè)版本很多的比較好的算法都沒有,自己編譯的時(shí)候有很多的坑,我自己整理了一些opencv+contrib+vs編譯的一些問題;
- cv用起來不難,但是理解起來比較難,里面涉及到的算法有點(diǎn)多;很多都很不好理解;
-
代碼實(shí)現(xiàn)
#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; //訓(xùn)練好的文件名稱,放置在可執(zhí)行文件同目錄下 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); //變換后的圖像進(jìn)行直方圖均值化處理 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()) { //測試圖像應(yīng)該是灰度圖 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; }