在之前的博客中已經解決了人臉檢測的問題,我們計劃在這篇博客中介紹人臉識別、性別識別方面的相關實現方法。
其實性別識別和人臉識別本質上是相似的,因為這裡只是一個簡單的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、新建一個控制台工程,配置OpenCv
這裡不再贅述,建議加上預編譯頭即可,這裡工程名暫定為GenderRecognition
2、編寫批量讀取文件函數read_csv()
首先,批量txt文件是典型的io操作,需要包含以下頭文件:
#include#include #include
然後開始編寫read_csv函數,函數相對比較簡單,這裡直接給出代碼:
void read_csv(string& fileName,vector& images,vector & 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"; vectorimages; vector labels; read_csv(csvPath,images,labels); return 0; }
讀取成功,images和labels兩個容器都包含800個樣本:
4、訓練分類器
OpenCv中的FaceRecognizer類提供的分類器訓練API函數非常簡單,只需三句話,以EigenFaceRecognizer為例:
PtrmodelPCA = createEigenFaceRecognizer(); modelPCA->train(images,labels); modelPCA->save("E:\\性別識別數據庫—CAS-PEAL\\PCA_Model.xml");
訓練完成後(大約五分鐘左右),訓練好的分類器已經以XML文件的形式保存在了指定路徑下:
同理,訓練FisherFaceRecognizer、LBPHFaceRecognizer兩個分類器並保存:
PtrmodelFisher = createFisherFaceRecognizer(); modelFisher->train(images,labels); modelFisher->save("E:\\性別識別數據庫—CAS-PEAL\\Fisher_Model.xml"); Ptr modelLBP = createLBPHFaceRecognizer(); modelLBP->train(images,labels); modelLBP->save("E:\\性別識別數據庫—CAS-PEAL\\LBP_Model.xml");
得到另外兩個分類器:
4、測試分類器
訓練完分類器後,接下來我們介紹如何使用這些訓練好的分類器對測試樣本進行分類。首先加載三個分類器
PtrmodelPCA = createEigenFaceRecognizer(); Ptr modelFisher = createFisherFaceRecognizer(); Ptr 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#include #include #include using namespace std; using namespace cv; void read_csv(string& fileName,vector & images,vector & 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 images; vector labels; read_csv(csvPath,images,labels); Ptr modelPCA = createEigenFaceRecognizer(); modelPCA->train(images,labels); modelPCA->save("E:\\性別識別數據庫—CAS-PEAL\\PCA_Model.xml"); Ptr modelFisher = createFisherFaceRecognizer(); modelFisher->train(images,labels); modelFisher->save("E:\\性別識別數據庫—CAS-PEAL\\Fisher_Model.xml"); Ptr modelLBP = createLBPHFaceRecognizer(); modelLBP->train(images,labels); modelLBP->save("E:\\性別識別數據庫—CAS-PEAL\\LBP_Model.xml"); //Ptr modelPCA = createEigenFaceRecognizer(); //Ptr modelFisher = createFisherFaceRecognizer(); //Ptr 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控制台工程下的簡潔代碼,同時,有以下幾個方面需要特別注意一下。
1、人臉識別和性別識別的關系
在這篇博客的開始部分曾提到過性別識別和人臉識別的關系,在這裡需要再次強調一下。性別識別本質上屬於人臉識別,但是和人臉識別還是有很多方面的區別。性別識別是二分類問題,人臉識別是多分類問題,二者在算法上也有很大差異。我們這裡之所以簡單的將性別識別看做簡化的人臉識別,是因為在這套教程中我們主要注重實踐,注重OpenCv的使用以及MFC框架編程方法,因此在算法方面會顯得不夠嚴謹。因此希望大家不要被這些簡化的觀點所誤導,真正的性別識別算法也遠比這些復雜,也和人臉識別方法大不相同,作為圖像處理的行內人,我覺得很有必要把這點說清楚。
2、read_csv函數
這裡對read_csv()批量讀取函數介紹得相對簡潔,大家可以參照我提供的博客來進行詳細學習,同時考慮到這個函數相對簡潔,可以凡在main()函數之前,從而避免提前聲明。
3、數據集原始路徑問題
這篇博文中並沒有詳細介紹如何制作性別識別訓練數據集,因此大家在使用網上下載的數據集時一定要注意路徑的問題。下載後數據集必須放在E盤根目錄下,否則的話則需要重新制作路徑文件(at.txt),不過這一步也並不復雜,參見一種批量讀取文件的方法—CSV文件。
同時,這裡在向路徑文件後邊添加類別標號時,當初我采用的是手動添加的方式,不過我相信大家能夠找到更為簡便的添加方式。
這裡之所以沒有介紹數據集的制作,是因為我計劃將這部分內容作為程序的一個附加功能來單獨進行介紹(也就是所謂的“人臉批量分割”),在之後進入到MFC編程部分時會進行專門的介紹。
4、關於性別識別的其他方法
在接下來的博文中我會介紹性別識別中的另外一種基礎方法——SVM方法。