经典人脸识别算法小结——EigenFace, FisherFace & LBPH(下)

该文章由下面两部分组成:html

1).经典人脸识别算法小结——EigenFace, FisherFace & LBPH(上),这部分介绍人脸开源库,和图片的读取等准备工做。ios

2).经典人脸识别算法小结——EigenFace, FisherFace & LBPH(下),这部分介绍三种人脸识别算法。算法

若是对于opencv中的人脸识别API感兴趣,可参看官方的说明:Face Recognition with OpenCV数据库

1.EigenFace

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个特征向量进行重建后的效果图。很明显随着引入进来的特征向量愈来愈多,重建后的效果也愈来愈接近原图。




2. FisherFace

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副左右的图像。


3. LBPH

LBPH是利用局部二值模式直方图的人脸识别算法。关于LBP的原理能够参考:LBP小结:LBP及改进版本的原理和opencv实现源代码

下面是其计算步骤:


LBP是典型的二值特征描述子,因此相比前面EigenFace和FisherFace,更多的是整数计算,而整数计算的优点是能够经过各类逻辑操做来进行优化,所以效率较高。另外一般光照对图中的物件带来的影响是全局的,也就是说照片中的物体明暗程度,是往同一个方向改变的,多是变亮或变暗,只是改变的幅度会由于距离光源的远近而有所不一样。因此基本上局部相邻(Local)的像素间,受光照影响后数值也许会改变,但相对大小不会改变,所以LBP特征对光照具备比较好的鲁棒性。

LBPH在opencv的API调用以下:

//训练模型 Ptr<LBPHFaceRecognizer> model = createLBPHFaceRecognizer(); model->train(images, labels);