OpenCV人臉識別之二:模型訓練
歡迎訪問http://www.leadai.org
作者:劉瀟龍
在該系列第一篇《OpenCV人臉識別之一:數據收集和預處理》文章中,已經下載了ORL人臉資料庫,並且為了識別自己的人臉寫了一個拍照程序自拍。之後對拍的照片進行人臉識別和提取,最後我們得到了一個包含自己的人臉照片的文件夾s41。在博客的最後我們提到了一個非常重要的文件——at.txt。
1、csv文件的生成
當我們寫人臉模型的訓練程序的時候,我們需要讀取人臉和人臉對應的標籤。直接在資料庫中讀取顯然是低效的。所以我們用csv文件讀取。csv文件中包含兩方面的內容,一是每一張圖片的位置所在,二是每一個人臉對應的標籤,就是為每一個人編號。這個at.txt就是我們需要的csv文件。生成之後它裡面是這個樣子的
前面是圖片的位置,後面是圖片所屬人臉的人的標籤。
要生成這樣一個文件直接用手工的方式一個一個輸入顯然不可取的,畢竟這裡有400多張圖片。而且這種重複性的工作估計也沒人想去做。所以我們可以用命令行的方式簡化工作量;或者用opencv自帶的Python腳本來自動生成。
命令行方式是這樣的。比如我的數據集在C:UsersbingbuyuDownloadsatt_faces文件夾下面,我就用下面兩行命令:
然後數據集文件夾下面就多出了一個at.txt文件,但是現在是只有路徑沒有標籤的。像下面這樣:
標籤需要手動敲上去。。。也挺麻煩的。
好在opencv教程裡面為我們提供了自動生成csv文件的腳本。路徑類似這樣:F:opencvsourcesmodulescontribdocfacerecsrccreate_csv.py。我不知道怎麼用命令行參數的形式運行Python腳本,所以只能把代碼裡面的BASE_PATH手動的改成自己的數據集路徑,改完大致是這樣:
#!/usr/bin/env pythonn import sysnimport os.pathn # This is a tiny script to help you creating a CSV file from a facen# database with a similar hierarchie:n#n# philipp@mango:~/facerec/data/at$ treen# .n# |-- README # |-- s1n# | |-- 1.pgm # | |-- ...n# | |-- 10.pgmn # |-- s2n# | |-- 1.pgmn# | |-- ...n# | |-- 10.pgmn# ...n# |-- s40n # | |-- 1.pgmn# | |-- ...n# | |-- 10.pgmn#n if __name__ == "__main__": n#if len(sys.argv) != 2: n# print "usage: create_csv <base_path>" n# sys.exit(1) n#BASE_PATH=sys.argv[1] nBASE_PATH="C:/Users/bingbuyu/Downloads/att_faces" nSEPARATOR=";" nfh = open("../etc/at.txt",w) nlabel = 0 nfor dirname, dirnames, filenames in os.walk(BASE_PATH): nfor subdirname in dirnames: nsubject_path = os.path.join(dirname, subdirname) nfor filename in os.listdir(subject_path): nabs_path = "%s/%s" % (subject_path, filename) nprint "%s%s%d" % (abs_path, SEPARATOR, label) n fh.write(abs_path) nfh.write(SEPARATOR) n fh.write(str(label)) n fh.write("n") nlabel = label + 1n fh.close()n
然後運行這個腳本就可以生成一個既有路徑又有標籤的at.txt了。
2、訓練模型
現在數據集、csv文件都已經準備好了。接下來要做的就是訓練模型了。
這裡我們用到了opencv的Facerecognizer類。opencv中所有的人臉識別模型都是來源於這個類,這個類為所有人臉識別演算法提供了一種通用的介面。文檔里的一個小段包含了我們接下來要用到的幾個函數:
OpenCV 自帶了三個人臉識別演算法:Eigenfaces,Fisherfaces 和局部二值模式直方圖 (LBPH)。這裡先不去深究這些演算法的具體內容,直接用就是了。如果有興趣可以去看相關論文。接下來就分別訓練這三種人臉模型。這個時候就能體現出Facerecognizer類的強大了。因為每一種模型的訓練只需要三行代碼:
Ptr<FaceRecognizer> model = createEigenFaceRecognizer();n model->train(images, labels);n model->save("MyFacePCAModel.xml");nn Ptr<FaceRecognizer> model1 = createFisherFaceRecognizer();n model1->train(images, labels);n model1->save("MyFaceFisherModel.xml");nn Ptr<FaceRecognizer> model2 = createLBPHFaceRecognizer();n model2->train(images, labels);n model2->save("MyFaceLBPHModel.xml");n
當然在這之前要先把之前圖片和標籤提取出來。這時候就是at.txt派上用場的時候了。
//使用CSV文件去讀圖像和標籤,主要使用stringstream和getline方法nstatic void read_csv(const string& filename, vector<Mat>& images, vector<int>& labels, char separator = { nstd::ifstream file(filename.c_str(), ifstream::in); nif (!file) { nstring error_message = "No valid input file was given, please check the given filename."; CV_Error(CV_StsBadArg, error_message); n} nstring line, path, classlabel; nwhile (getline(file, line)) { nstringstream liness(line); ngetline(liness, path, separator); ngetline(liness, classlabel); n if (!path.empty() && !classlabel.empty()) { nimages.push_back(imread(path, 0)); nlabels.push_back(atoi(classlabel.c_str())); n }n }n}n
在模型訓練好之後我們拿數據集中的最後一張圖片做一個測試,看看結果如何。
Mat testSample = images[images.size() - 1];n int testLabel = labels[labels.size() - 1];nn<span stylex="white-space:pre"> </span>//。。。。這裡省略部分代碼。。。。。。。。n // 下面對測試圖像進行預測,predictedLabel是預測標籤結果n int predictedLabel = model->predict(testSample);n int predictedLabel1 = model1->predict(testSample);n int predictedLabel2 = model2->predict(testSample);nn // 還有一種調用方式,可以獲取結果同時得到閾值:n // int predictedLabel = -1;n // double confidence = 0.0;n // model->predict(testSample, predictedLabel, confidence);nn string result_message = format("Predicted class = %d / Actual class = %d.", predictedLabel, testLabel);n string result_message1 = format("Predicted class = %d / Actual class = %d.", predictedLabel1, testLabel);n string result_message2 = format("Predicted class = %d / Actual class = %d.", predictedLabel2, testLabel);n cout << result_message << endl;n cout << result_message1 << endl;n cout << result_message2 << endl;n
由於本來的數據集中是40個人,加上自己的人臉集就是41個。標籤是從0開始標的,所以在這裡我是第40個人。也即是說Actual class應該40。Predicted class也應該是40才說明預測準確。這裡我們可以看到結果:
結果正確。
模型訓練的全部代碼:
//#include "stdafx.h"n#include <opencv2/opencv.hpp>n #include <iostream>n#include <fstream>n#include <sstream>n#include <math.h>nusing namespace cv;nusing namespace std;nstatic Mat norm_0_255(InputArray _src) { nMat src = _src.getMat(); n// 創建和返回一個歸一化後的圖像矩陣: nMat dst; nswitch (src.channels()) { ncase1: ncv::normalize(_src, dst, 0, 255, NORM_MINMAX, CV_8UC1); nbreak; ncase3: n cv::normalize(_src, dst, 0, 255, NORM_MINMAX, CV_8UC3); nbreak; ndefault: nsrc.copyTo(dst); nbreak; n} n return dst; }n//使用CSV文件去讀圖像和標籤,主要使用stringstream和getline方法nstatic void read_csv(const string& filename, vector<Mat>& images, vector<int>& labels, char separator = { nstd::ifstream file(filename.c_str(), ifstream::in); nif (!file) { nstring error_message = "No valid input file was given, please check the given filename."; CV_Error(CV_StsBadArg, error_message); n } nstring line, path, classlabel; nwhile (getline(file, line)) { nstringstream liness(line); ngetline(liness, path, separator); ngetline(liness, classlabel); nif (!path.empty() && !classlabel.empty()) { nimages.push_back(imread(path, 0)); nlabels.push_back(atoi(classlabel.c_str())); n } n }n }n int main()n { n//讀取你的CSV文件路徑. n//string fn_csv = string(argv[1]); nstring fn_csv = "at.txt"; n// 2個容器來存放圖像數據和對應的標籤 nvector<Mat> images; nvector<int> labels; n// 讀取數據. 如果文件不合法就會出錯 n// 輸入的文件名已經有了. n try n { nread_csv(fn_csv, images, labels); n} n catch (cv::Exception& e) n{ ncerr << "Error opening file "" << fn_csv << "". Reason: " << e.msg << endl; n// 文件有問題,我們啥也做不了了,退出了 nexit(1); n} n// 如果沒有讀取到足夠圖片,也退出. n if (images.size() <= 1) { nstring error_message = "This demo needs at least 2 images to work. Please add more images to your data set!"; nCV_Error(CV_StsError, error_message); n} n// 下面的幾行代碼僅僅是從你的數據集中移除最後一張圖片 n//[gm:自然這裡需要根據自己的需要修改,他這裡簡化了很多問題] nMat testSample = images[images.size() - 1]; nint testLabel = labels[labels.size() - 1]; nimages.pop_back(); nlabels.pop_back(); n// 下面幾行創建了一個特徵臉模型用於人臉識別, n// 通過CSV文件讀取的圖像和標籤訓練它。 n // T這裡是一個完整的PCA變換 n//如果你只想保留10個主成分,使用如下代碼 n // cv::createEigenFaceRecognizer(10); n// n// 如果你還希望使用置信度閾值來初始化,使用以下語句: n // cv::createEigenFaceRecognizer(10, 123.0); n// n// 如果你使用所有特徵並且使用一個閾值,使用以下語句: n // cv::createEigenFaceRecognizer(0, 123.0); nPtr<FaceRecognizer> model = createEigenFaceRecognizer(); nmodel->train(images, labels); nmodel->save("MyFacePCAModel.xml"); nPtr<FaceRecognizer> model1 = createFisherFaceRecognizer(); nmodel1->train(images, labels); nmodel1->save("MyFaceFisherModel.xml"); n Ptr<FaceRecognizer> model2 = createLBPHFaceRecognizer(); nmodel2->train(images, labels); nmodel2->save("MyFaceLBPHModel.xml"); n // 下面對測試圖像進行預測,predictedLabel是預測標籤結果 nint predictedLabel = model->predict(testSample); n int predictedLabel1 = model1->predict(testSample); nint predictedLabel2 = model2->predict(testSample); n// 還有一種調用方式,可以獲取結果同時得到閾值: n// int predictedLabel = -1; n// double confidence = 0.0; n// model->predict(testSample, predictedLabel, confidence); nstring result_message = format("Predicted class = %d / Actual class = %d.", predictedLabel, testLabel); nstring result_message1 = format("Predicted class = %d / Actual class = %d.", predictedLabel1, testLabel); nstring result_message2 = format("Predicted class = %d / Actual class = %d.", predictedLabel2, testLabel); ncout << result_message << endl;nncout << result_message1 << endl; ncout << result_message2 << endl; n waitKey(0); nreturn 0;n } n
原文鏈接:http://www.jianshu.com/p/aaa5c7064362
查閱更為簡潔方便的分類文章以及最新的課程、產品信息,請移步至全新呈現的
http://www.leadai.org
推薦閱讀:
※1.22【OpenCV圖像處理】像素重映射
※C++實現神經網路之五—模型的保存和載入以及畫出實時輸出曲線
※1.26【OpenCV圖像處理】模板匹配