在之前的博客中已經攻克了人臉檢測的問題,我們計划在這篇博客中介紹人臉識別、性別識別方面的相關實現方法。
事實上性別識別和人臉識別本質上是相似的,由於這里僅僅是一個簡單的MFC開發,主要工作並不在算法研究上,因此我們直接將性別識別視為一種特殊的人臉識別模式。
人臉識別可能須要分為幾十甚至上百個類(由於有幾十甚至上百個人)。而性別識別則是一種特殊的人臉識別——僅僅有兩個類。
一、基本工具
通過OpenCv進行性別識別的基本工具是FaceRecognizer。這是OpenCv2.x版本號中的一個主要的人臉識別類,它封裝了三種基本但也是經典的人臉識別算法:基於PCA變換的人臉識別(EigenFaceRecognizer)、基於Fisher變換的人臉識別(FisherFaceRecognizer)、基於局部二值模式的人臉識別(LBPHFaceRecognizer)。這些算法幾乎相同都是十年曾經的人臉識別方法了,因此在今天看來正確率應該不會太讓人愜意。只是我們這里重在實踐。而非算法研究(盡管本人就是搞圖像識別算法研究的),因此我們不會在算法創新方面下太多功夫,所以選擇了這三個主要的識別算法。
關於FaceRecognizer類人臉識別的具體操作,這里為大家推薦兩篇博客:FaceRecognizer幫助文檔以及FaceRecognizer。
這里我們直接使用FaceRecognizer類的相關操作方法。對於其基本使用方法就不再贅述。
二、數據集准備
進行性別識別理所應當須要先准備一些性別識別方面的訓練樣本。須要強調的一點是。數據集的准備過程中也須要一些小的技巧,在之后我會專門寫一篇博文來解釋怎樣制作一個簡易的性別識別訓練集。這里我們直接用我已經做好的訓練集。下載地址:性別識別數據集
1、概況
我這里整理的性別識別訓練集是取自中科院的人臉數據庫CAS-PEAL的光照子集,包括400張男性人臉圖片和400張女性人臉圖片,剩余人臉圖片作為測試樣本
2、訓練集基本結構
訓練集包括三部分:男性樣本、女性樣本、測試樣本:
這里我們通過CSV文件方法來批量讀取訓練樣本。因此這里提前制作了一個txt文件來存儲每個訓練樣本圖片的路徑:
注意這里at.txt文件里的路徑實際上是由兩部分內容組成,即“路徑。性別標號”。性別標號“1”代表男性,“2”代表女性。至於怎樣通過csv文件方法來批量讀取文件,參見:一種批量讀取文件的方法—CSV文件。
同理,在測試樣本中相同須要用txt文件來記錄樣本路徑和標簽:
三、識別算法的訓練與測試
1、新建一個控制台project。配置OpenCv
這里不再贅述。建議加上預編譯頭就可以。這里project名暫定為GenderRecognition
2、編寫批量讀取文件函數read_csv()
首先。批量txt文件是典型的io操作。須要包括下面頭文件:
#include <iostream> #include <sstream> #include <fstream>
然后開始編寫read_csv函數。函數相對照較簡單,這里直接給出代碼:
void read_csv(string& fileName,vector<Mat>& images,vector<int>& labels,char separator = ';') { ifstream file(fileName.c_str(),ifstream::in); //以讀入的方式打開文件 String line,path,label; while (getline(file,line)) //從文本文件里讀取一行字符。未指定限定符默認限定符為“/n” { stringstream lines(line); getline(lines,path,separator); //依據指定切割符進行切割,分為“路徑+標號” getline(lines,label); if (!path.empty()&&!label.empty()) //假設讀取成功,則將圖片和相應標簽壓入相應容器中 { images.push_back(imread(path,1)); //讀取訓練樣本 labels.push_back(atoi(label.c_str())); //讀取訓練樣本標號 } } }
read_csv()函數的主要功能就是讀取指定文件夾下的路徑文件(比如這里的at.txt),然后依據路徑文件里的記錄,逐行讀入相應路徑的訓練樣本路徑及其標號,並放入相應容器(vector)中。至於為什么採用vector數據結構來存儲訓練樣本。一是由於這樣做簡單直觀。二是由於OpenCv的訓練函數提供的是vector接口。當然這樣做也存在一定弊端,就是必須一次性將訓練樣本所有讀入到內存中,當訓練樣本數量龐大時這樣的方法不但會消耗掉巨額內存,並且效率低下。
很多其它關於read_csv()批量讀取的知識參見一種批量讀取文件的方法—CSV文件。
3、讀入訓練樣本
接下來在主函數中調用read_csv()函數,讀取訓練樣本及標簽。並放入相應容器中:
int _tmain(int argc, _TCHAR* argv[]) { String csvPath = "E:\\性別識別數據庫—CAS-PEAL\\at.txt"; vector<Mat> images; vector<int> labels; read_csv(csvPath,images,labels); return 0; }
讀取成功,images和labels兩個容器都包括800個樣本:
4、訓練分類器
OpenCv中的FaceRecognizer類提供的分類器訓練API函數很easy。僅僅需三句話,以EigenFaceRecognizer為例:
Ptr<FaceRecognizer> modelPCA = createEigenFaceRecognizer(); modelPCA->train(images,labels); modelPCA->save("E:\\性別識別數據庫—CAS-PEAL\\PCA_Model.xml");
訓練完畢后(大約五分鍾左右),訓練好的分類器已經以XML文件的形式保存在了指定路徑下:
同理,訓練FisherFaceRecognizer、LBPHFaceRecognizer兩個分類器並保存:
Ptr<FaceRecognizer> modelFisher = createFisherFaceRecognizer(); modelFisher->train(images,labels); modelFisher->save("E:\\性別識別數據庫—CAS-PEAL\\Fisher_Model.xml"); Ptr<FaceRecognizer> modelLBP = createLBPHFaceRecognizer(); modelLBP->train(images,labels); modelLBP->save("E:\\性別識別數據庫—CAS-PEAL\\LBP_Model.xml");
得到另外兩個分類器:
4、測試分類器
訓練完分類器后,接下來我們介紹怎樣使用這些訓練好的分類器對測試樣本進行分類。
首先載入三個分類器
Ptr<FaceRecognizer> modelPCA = createEigenFaceRecognizer(); Ptr<FaceRecognizer> modelFisher = createFisherFaceRecognizer(); Ptr<FaceRecognizer> modelLBP = createLBPHFaceRecognizer(); modelPCA->load("E:\\性別識別數據庫—CAS-PEAL\\PCA_Model.xml"); modelFisher->load("E:\\性別識別數據庫—CAS-PEAL\\Fisher_Model.xml"); modelLBP->load("E:\\性別識別數據庫—CAS-PEAL\\LBP_Model.xml");
然后讀入一張測試樣本,通過三個分類器對其進行預測:
Mat testImage = imread("E:\\性別識別數據庫—CAS-PEAL\\測試樣本\\男性測試樣本\\face_480.bmp",0); int predictPCA = modelPCA->predict(testImage); int predictLBP = modelLBP->predict(testImage); int predictFisher = modelFisher->predict(testImage);
預測結果如圖:
可見對於這張測試圖片,三個分類器均給出了正確預測(數字“1”代表男性),正確率能夠接受。
四、代碼
這部分博客所涉及的代碼相同較為簡潔,因此在這里給出總體代碼:
// GenderRecognition.cpp : 定義控制台應用程序的入口點。 // #include "stdafx.h" #include <opencv2\opencv.hpp> #include <iostream> #include <sstream> #include <fstream> using namespace std; using namespace cv; void read_csv(string& fileName,vector<Mat>& images,vector<int>& labels,char separator = ';') { ifstream file(fileName.c_str(),ifstream::in); //以讀入的方式打開文件 String line,path,label; while (getline(file,line)) //從文本文件里讀取一行字符,未指定限定符默認限定符為“/n” { stringstream lines(line); getline(lines,path,separator); //依據指定切割符進行切割。分為“路徑+標號” getline(lines,label); if (!path.empty()&&!label.empty()) //假設讀取成功。則將圖片和相應標簽壓入相應容器中 { images.push_back(imread(path,0)); //讀取訓練樣本 labels.push_back(atoi(label.c_str())); //讀取訓練樣本標號 } } } int _tmain(int argc, _TCHAR* argv[]) { String csvPath = "E:\\性別識別數據庫—CAS-PEAL\\at.txt"; vector<Mat> images; vector<int> labels; read_csv(csvPath,images,labels); Ptr<FaceRecognizer> modelPCA = createEigenFaceRecognizer(); modelPCA->train(images,labels); modelPCA->save("E:\\性別識別數據庫—CAS-PEAL\\PCA_Model.xml"); Ptr<FaceRecognizer> modelFisher = createFisherFaceRecognizer(); modelFisher->train(images,labels); modelFisher->save("E:\\性別識別數據庫—CAS-PEAL\\Fisher_Model.xml"); Ptr<FaceRecognizer> modelLBP = createLBPHFaceRecognizer(); modelLBP->train(images,labels); modelLBP->save("E:\\性別識別數據庫—CAS-PEAL\\LBP_Model.xml"); //Ptr<FaceRecognizer> modelPCA = createEigenFaceRecognizer(); //Ptr<FaceRecognizer> modelFisher = createFisherFaceRecognizer(); //Ptr<FaceRecognizer> modelLBP = createLBPHFaceRecognizer(); modelPCA->load("E:\\性別識別數據庫—CAS-PEAL\\PCA_Model.xml"); modelFisher->load("E:\\性別識別數據庫—CAS-PEAL\\Fisher_Model.xml"); modelLBP->load("E:\\性別識別數據庫—CAS-PEAL\\LBP_Model.xml"); Mat testImage = imread("E:\\性別識別數據庫—CAS-PEAL\\測試樣本\\男性測試樣本\\face_480.bmp",0); int predictPCA = modelPCA->predict(testImage); int predictLBP = modelLBP->predict(testImage); int predictFisher = modelFisher->predict(testImage); return 0; }
四、總結
這篇博客主要介紹了怎樣使用OpenCv提供的人臉識別類FaceRecognizer來進行性別識別,並提供了一段win32控制台project下的簡潔代碼,同一時候,有下面幾個方面須要特別注意一下。
1、人臉識別和性別識別的關系
在這篇博客的開始部分曾提到過性別識別和人臉識別的關系。在這里須要再次強調一下。性別識別本質上屬於人臉識別。可是和人臉識別還是有非常多方面的差別。
性別識別是二分類問題,人臉識別是多分類問題,二者在算法上也有非常大差異。我們這里之所以簡單的將性別識別看做簡化的人臉識別。是由於在這套教程中我們主要注重實踐,注重OpenCv的使用以及MFC框架編程方法。因此在算法方面會顯得不夠嚴謹。因此希望大家不要被這些簡化的觀點所誤導。真正的性別識別算法也遠比這些復雜,也和人臉識別方法大不同樣,作為圖像處理的行內人。我認為非常有必要把這點說清楚。
2、read_csv函數
這里對read_csv()批量讀取函數介紹得相對簡潔,大家能夠參照我提供的博客來進行具體學習。同一時候考慮到這個函數相對簡潔,能夠凡在main()函數之前,從而避免提前聲明。
3、數據集原始路徑問題
這篇博文中並沒有具體介紹怎樣制作性別識別訓練數據集。因此大家在使用網上下載的數據集時一定要注意路徑的問題。
下載后數據集必須放在E盤根文件夾下,否則的話則須要又一次制作路徑文件(at.txt)。只是這一步也並不復雜,參見一種批量讀取文件的方法—CSV文件。
同一時候,這里在向路徑文件后邊加入類別標號時,當初我採用的是手動加入的方式,只是我相信大家可以找到更為簡便的加入方式。
這里之所以沒有介紹數據集的制作,是由於我計划將這部分內容作為程序的一個附加功能來單獨進行介紹(也就是所謂的“人臉批量切割”),在之后進入到MFC編程部分時會進行專門的介紹。
4、關於性別識別的其它方法
在接下來的博文中我會介紹性別識別中的第二種基礎方法——SVM方法。