该文章由下面两部分组成:html
1).经典人脸识别算法小结——EigenFace, FisherFace & LBPH(上),这部分介绍人脸开源库,和图片的读取等准备工做。ios
2).经典人脸识别算法小结——EigenFace, FisherFace & LBPH(下),这部分介绍三种人脸识别算法。算法
若是对于opencv中的人脸识别API感兴趣,可参看官方的说明:Face Recognition with OpenCV。数据库
EigenFace(特征脸)在人脸识别历史上应该是具备里程碑式意义的,其被认为是第一种有效的人脸识别算法。1987年 Sirovich and Kirby 为了减小人脸图像的表示(降维)采用了PCA(主成分分析)的方法,1991年 Matthew Turk和Alex Pentland首次将PCA应用于人脸识别,即将原始图像投影到特征空间,获得一系列降维图像,取其主元表示人脸,因其主元有人脸的形状,估称为“特征脸”。app
EigenFace是一种基于统计特征的方法,将人脸图像视为随机向量,并用统计方法辨别不一样人脸特征模式。EigenFace的基本思想是,从统计的观点,寻找人脸图像分布的基本元素,即人脸图像样本集协方差矩阵的特征向量,以此近似的表征人脸图像,这些特征向量称为特脸。性能
下图对特征脸的应用进行了说明。从下图能够看出,一组特征脸基图像(特征脸1~d)组成一个特征脸子空间,任何一幅人脸图象(减去平均人脸后)均可投影到该子空间,获得一个权值向量(§1~d)。计算此向量和训练集中每一个人的权值向量之间的欧式距离,取最小距离所对应的人脸图像的身份做为测试人脸图像的身份。而这里所提到的一组特征脸基图像(也就是特征脸,或者叫特征向量),正是利用PCA所求得的协方差矩阵的特征向量。具体能够参考主成分分析(PCA)和线性判别分析(LDA)原理简介。测试
EigenFace的工做流程以下所示,若是想要更具体的介绍能够参考:特征脸优化
下面代码是基于opencv的API,利用ORL的数据库来训练模型,并对test图像进行识别。为了更好的理解特征脸,下面也对第一幅图像进行了重建。spa
#include<opencv.hpp> #include<iostream> #include<opencv2\face.hpp> using namespace std; using namespace cv; using namespace cv::face; int main(void) { //读取已经生成好的list.txt文件,关于list.txt文件的 //生成请参考:经典人脸识别算法小结——EigenFace, FisherFace & LBPH(上) string fileName = string("D:/Opencv Picture/Face/list.txt"); ifstream file(fileName.c_str(), ios::in); if (!file) { cout << "Load file ERR!" << '\n'; return -1; } string line, path, classLabel; vector<Mat> images; vector<int> labels; char separater = ';'; while (getline(file, line)) { stringstream line_c(line); getline(line_c, path, separater); getline(line_c, classLabel); if (!path.empty() || !classLabel.empty()) { Mat image = imread(path, 0);//读取灰度图像 images.push_back(image); labels.push_back(atoi(classLabel.c_str())); } } if (images.size() < 1 || labels.size() < 1) { cout << " File Path ERR!" << '\n'; return -1; } //将images中的最后一张图像做为test图像 Mat testImg=images[images.size()-1]; int test_label=labels[labels.size()-1]; images.pop_back(); labels.pop_back(); //训练模型 Ptr<BasicFaceRecognizer> model = createEigenFaceRecognizer(); //model->train(images, labels); //model->save("model.xml"); model->load("model.xml");//对于已经训练好并保存的模型,能够直接调用 //对testImg进行识别 int predictLabel = model->predict(testImg); cout << "The test image label is " << predictLabel << '\n'; //显示meanFace Mat mean = model->getMean(); Mat meanFace = mean.reshape(1, testImg.rows); Mat meanFace_norm; if(meanFace.channels()==1) normalize(meanFace, meanFace_norm, 0, 255, NORM_MINMAX, CV_8UC1); else if(meanFace.channels()==3) normalize(meanFace, meanFace_norm, 0, 255, NORM_MINMAX, CV_8UC3); imshow("meanFace", meanFace_norm); //显示前10个eigenFace Mat eigenVectors = model->getEigenVectors(); for (int i = 0; i < min(10, eigenVectors.cols); ++i) { Mat eigenFace,eigenFace_norm; Mat ev = eigenVectors.col(i).clone(); eigenFace = ev.reshape(1, testImg.rows); if (eigenFace.channels() == 1) normalize(eigenFace, eigenFace_norm, 0, 255, NORM_MINMAX, CV_8UC1); else if (eigenFace.channels() == 3) normalize(eigenFace, eigenFace_norm, 0, 255, NORM_MINMAX, CV_8UC3); Mat colorFace; applyColorMap(eigenFace_norm,colorFace,COLORMAP_RAINBOW);//为了方便观察 imshow("eigenFace "+to_string(i), colorFace); } //每隔15个eigenVector重建一副图像 imshow("original", images[0]); for (int num = min(10, eigenVectors.cols); num < min(300, eigenVectors.cols); num+=15) { Mat evs = eigenVectors(Range::all(), Range(0, num));//提取eigenVectors的前num列数据 Mat projection = LDA::subspaceProject(evs, mean, images[0].reshape(1,1));//重建第一幅图像 Mat reconstraction = LDA::subspaceReconstruct(evs, mean, projection); //显示重建的图像 Mat result = reconstraction.reshape(1, testImg.rows); Mat result_norm; if (result.channels() == 1) normalize(result, result_norm, 0, 255, NORM_MINMAX, CV_8UC1); else if (result.channels() == 3) normalize(result, result_norm, 0, 255, NORM_MINMAX, CV_8UC3); imshow("Rec_face " + to_string(num), result_norm); } cvWaitKey(0); return 0; }
平均脸及前10个eigenFaces以下所示。.net
下面是第一幅图像的原图像,original和每隔15个特征向量进行重建后的效果图。很明显随着引入进来的特征向量愈来愈多,重建后的效果也愈来愈接近原图。
FisherFace 是一种基于LDA(全称Linear Discriminant Analysis, 线性判别分析)的人脸识别算法,而LDA是Ronald Fisher于193年提出来的,因此LDA也被称做是Fisher Discriminant Analysis, 也正由于如此,该人脸识别算法被称为FisherFace。
关于LDA能够参考主成分分析(PCA)和线性判别分析(LDA)原理简介。LDA有和PCA相同的地方是,都有利用特征值排序找到主元的过程,可是不一样的是PCA求的是协方差矩阵的特征值,而LDA是求的是一个更为复杂的矩阵的特征值(具体以下)。其中须要注意的是在求均值时,和PCA也是有所不一样的,LDA对每一个类别样本求均值,而PCA是对全部样本数据求均值,获得平均脸。
LDA的降维步骤以下:
若是用一句话来归纳LDA的中心思想就是最大化类间距离,最小化类内距离。
因为LDA利用了类成员信息并抽取了一个特征向量集,该特征向量集强调的是不一样人脸的差别而不是照明条件、人脸表情和方向的变化。所以,相比EigenFace(和谁比很重要),采用Fisherface方法对人脸进行识别对光照、人脸姿态的变化更不敏感,有助于提升识别效果。
在opencv中的调用相似于前面的EigenFace。只是获得的eigenvectors的维度不一样(从400变为39),另外在重建的时候,因为FisherFace只关注各种目标间的不一样特征,因此但愿重建出原图像是不现实的。
训练模型:
Ptr<BasicFaceRecognizer> model = createFisherFaceRecognizer(); //model->train(images, labels); //model->save("model.xml"); model->load("model.xml");//对于已经训练好并保存的模型,能够直接调用
图像重建
for (int num =0; num < min(10, eigenVectors.cols); ++num) { Mat evs = eigenVectors.col(num); Mat projection = LDA::subspaceProject(evs, mean, images[0].reshape(1, 1));//重建第一幅图像 Mat reconstraction = LDA::subspaceReconstruct(evs, mean, projection); //显示重建的图像 Mat result = reconstraction.reshape(1, testImg.rows); Mat result_norm; if (result.channels() == 1) normalize(result, result_norm, 0, 255, NORM_MINMAX, CV_8UC1); else if (result.channels() == 3) normalize(result, result_norm, 0, 255, NORM_MINMAX, CV_8UC3); imshow("Rec_face " + to_string(num), result_norm); }
对于EigenFace 和 FisherFace,这两个方法的性能主要取决于输入数据,若是是左右偏转脑壳,能够容纳15°左右仍然保持较高的识别率,可是上下的偏转相对来讲更敏感些。毕竟人脸是三维的,不是简单的旋转就能修复,稍微大一点的偏转最好仍是经过3D建模来处理的。若是须要对光照有好的鲁棒性,能够考虑gabor和LBPH。
有分析显示,对于EigenFace和FisherFace,要想达到好的识别效果,每一个样本对象至少要采集8副左右的图像。
LBPH是利用局部二值模式直方图的人脸识别算法。关于LBP的原理能够参考:LBP小结:LBP及改进版本的原理和opencv实现源代码。
下面是其计算步骤:
LBP是典型的二值特征描述子,因此相比前面EigenFace和FisherFace,更多的是整数计算,而整数计算的优点是能够经过各类逻辑操做来进行优化,所以效率较高。另外一般光照对图中的物件带来的影响是全局的,也就是说照片中的物体明暗程度,是往同一个方向改变的,多是变亮或变暗,只是改变的幅度会由于距离光源的远近而有所不一样。因此基本上局部相邻(Local)的像素间,受光照影响后数值也许会改变,但相对大小不会改变,所以LBP特征对光照具备比较好的鲁棒性。
LBPH在opencv的API调用以下:
//训练模型 Ptr<LBPHFaceRecognizer> model = createLBPHFaceRecognizer(); model->train(images, labels);