openCV学习笔记(十二) —— 人脸识别算法(1/3)—— 特征脸 EigenFaces

 

一、原理

opencv支持3种人脸识别的算法,分别是:
1.    Eigen Faces    PCA(特征脸方法)
2.    Fisher Faces    LDA(线性判别分析)
3.    Local Binary Pattern Histograms(LBP 局部二值模式直方图)

 

详细介绍可以参照以下链接

https://blog.csdn.net/wanghz999/article/details/78817265

https://blog.csdn.net/u010006643/article/details/46417127

 

以下是部分摘抄:

Eigenfaces的原理

Eigenfaces就是特征脸的意思,是一种从主成分分析(Principal Component Analysis,PCA)中导出的人脸识别和描述技术。特征脸方法的主要思路就是将输入的人脸图像看作一个个矩阵,通过在人脸空间中一组正交向量,并选择最重要的正交向量,作为“主成分”来描述原来的人脸空间。为了更好地理解特征脸方法,需要先了解PCA的主要过程。

PCA主要过程

在很多应用中,需要对大量数据进行分析计算并寻找其内在的规律,但是数据量巨大造成了问题分析的复杂性,因此我们需要一些合理的方法来减少分析的数据和变量同时尽量不破坏数据之间的关联性。于是这就有了主成分分析方法,PCA作用:

  • 数据降维。减少变量个数;确保变量独立;提供一个合理的框架解释。
  • 去除噪声,发现数据背后的固有模式。

PCA的主要过程:

  1. 特征中心化:将每一维的数据(矩阵A)都减去该维的均值,使得变换后(矩阵B)每一维均值为0;
  2. 计算变换后矩阵B的协方差矩阵C;
  3. 计算协方差矩阵C的特征值和特征向量;
  4. 选取大的特征值对应的特征向量作为”主成分”,并构成新的数据集;

特征脸方法

特征脸方法就是将PCA方法应用到人脸识别中,将人脸图像看成是原始数据集,使用PCA方法对其进行处理和降维,得到“主成分”——即特征脸,然后每个人脸都可以用特征脸的组合进行表示。这种方法的核心思路是认为同一类事物必然存在相同特性(主成分),通过将同一目标(人脸图像)的特性寻在出来,就可以用来区分不同的事物了。人脸识别嘛,就是一个分类的问题,将不同的人脸区分开来。特征脸方法的过程(先计算特征脸,然后识别人脸):

  1. 将训练集中的N个人脸拉成一列(reshape(1,1)),然后组合在一起形成一个大矩阵A。若人脸图像大小为m * m,则矩阵A的维度是m * m * N;
  2. 将N个人脸在对应的维度求平均,得到一个“平均脸”;
  3. 将矩阵A中N个图像都减去“平均脸”,得到新矩阵B;
  4. 计算B的协方差矩阵;
  5. 计算协方差矩阵的特征值和特征向量(特征脸);
  6. 将训练集图像和测试集图像都投影到特征向量空间中,再使用聚类方法(最近邻或k近邻等)得到里测试集中的每个图像最近的图像,进行分类即可。

在opencv中void EigenFaceRecognizer::train();就是对训练集进行处理最终得到特征向量和阈值的过程,其中的阈值用于后面对测试图像的识别。int EigenFaceRecognizer:: predict(InputArray src) const;是对测试图像进行识别。

特征脸识别的局限性

要让系统准确识别需要保证人脸图像满足:

  1. 待识别图像中人脸尺寸接近特征脸中人脸的尺寸;
  2. 待识别人脸图像必须为正面人脸图像。

若不满足此条件,识别错误率很高。从PCA方法的过程可以看出,特征脸识别的方法是以每张人脸的一个维度(可以看出是矩阵的一列)为单位进行处理的,求得的特征向量(特征脸)中包含训练集每个纬度的绝大部分信息。但是若测试集中人脸尺寸不同,那么与特征脸中维度的也就没法对应起来。

二、程序

人脸训练集的获取与制作

在人脸识别之前需要获取人脸的训练集,这里利用的是The ORL Database of Faces’数据库,也可以自己采集人脸图像进行检测然后调整大小,具体的过程可以参照:http://blog.csdn.net/wanghz999/article/details/78751406

cvs文件获取

人脸识别的过程就是对人脸图像进行训练然后读取人脸并进行识别。在训练时,计算机会对每张人脸的多张图像进行特征提取,然后根据特征值来区分不同的人脸。然而计算机并不知道两种不同的人脸彼此是哪张,因此需要告诉预先告诉计算机一个标签——区分不同的人脸。cvs文件就是包含人脸图像路径和标签的文件,格式如下:

D:/workspace/VS/opencv/face_recg/face_recg/face/s1/1.pgm;0
D:/workspace/VS/opencv/face_recg/face_recg/face/s1/10.pgm;0
D:/workspace/VS/opencv/face_recg/face_recg/face/s1/2.pgm;0
D:/workspace/VS/opencv/face_recg/face_recg/face/s1/3.pgm;0
D:/workspace/VS/opencv/face_recg/face_recg/face/s1/4.pgm;0
...

;之前的表示人脸图像的路径,;之后的0表示标签。opencv官方提供了create_cvs.py,该文件位于源码目录(OPENCV_SOURCE_DIR)的opencv_contrib模块目录下:

OPENCV_SOURCE_DIR/opencv_contrib/modules/face/samples/etc

也可以直接从github上获取:https://github.com/opencv/opencv_contrib/tree/master/modules/face/samples/etc

这里给出我使用的(python3.*适用): 
#!/usr/bin/env python

import sys  
import os.path  

# This is a tiny script to help you creating a CSV file from a face  
# database with a similar hierarchie:  
#  
#  philipp@mango:~/facerec/data/at$ tree  
#  .  
#  |-- README  
#  |-- s1  
#  |   |-- 1.pgm  
#  |   |-- ...  
#  |   |-- 10.pgm  
#  |-- s2  
#  |   |-- 1.pgm  
#  |   |-- ...  
#  |   |-- 10.pgm  
#  ...  
#  |-- s40  
#  |   |-- 1.pgm  
#  |   |-- ...  
#  |   |-- 10.pgm  
#  

if __name__ == "__main__":  

    #if len(sys.argv) != 2:  
    #    print "usage: create_csv <base_path>"  
    #    sys.exit(1)  

    #BASE_PATH=sys.argv[1]  
    BASE_PATH="D:/workspace/VS\opencv/face_recg/face_recg/face/"  

    SEPARATOR=";"  

    fh = open("at.txt",'w')  

    label = 0  
    for dirname, dirnames, filenames in os.walk(BASE_PATH):  
        for subdirname in dirnames:  
            subject_path = os.path.join(dirname, subdirname)  
            for filename in os.listdir(subject_path):  
                abs_path = "%s/%s" % (subject_path, filename)  
                print ("%s%s%d" % (abs_path, SEPARATOR, label))  
                fh.write(abs_path)  
                fh.write(SEPARATOR)  
                fh.write(str(label))  
                fh.write("\n")        
            label = label + 1  
    fh.close()  

其中,BASE_PATH表示存放人脸图像的文件夹,在该文件夹下,每个文件夹代表一个人脸,一个人脸应该有多张大小一样的图。该文件夹如下: 

opencv人脸识别模块简介

opencv支持3种人脸识别的算法,分别是:

  1. Eigen Faces
  2. Fisher Faces
  3. Local Binary Pattern Histograms(局部二值模式直方图)

自opencv3.0开始,人脸识别的模块也有许多变动,实现跟以前一些很多不一样。上述的3种算法在opencv3.*中对应三个类:EigenFaceRecognizerFisherFaceRecognizerLBPHFaceRecognizer,它们都是从BasicFaceRecognizer继承过来的子类。这三个类有相似的API函数(以EigenFaceRecognizer为例):

/*创建人脸识别的模型,若该两个参数为空,会以默认值创建
@param num_components:进行训练的人脸图片数
@param threshold: 阈值*/
Ptr<EigenFaceRecognizer> EigenFaceRecognizer::create(int num_components, 
                                                double threshold);
/*对模型进行训练
@param src:进行训练的人脸图像
@param labels:标签值,每张相同的人脸都有标签值来区分*/
void train(InputArrayOfArrays src, InputArray labels);

/*利用训练好的模型进行人脸识别,返回标签值
@param src:需要进行识别的图像/
CV_WRAP_AS(predict_label) int predict(InputArray src) const;

opencv人脸识别代码实现

#include <iostream>
#include <fstream>
#include <vector>

#include <opencv2/opencv.hpp>
#include <opencv2/highgui.hpp>
#include <opencv2/objdetect.hpp>
#include <opencv2/face.hpp>

using namespace std;
using namespace cv;
using namespace cv::face;


//使用CVS文件读取图片和标签
void read_cvs(const string& filename, vector<Mat>& img, vector<int>& lable, char separator = ';')
{
	Mat tmp_img;
	ifstream cvs_file(filename.c_str());
	assert(cvs_file);
	string line, path, tag;
	while (getline(cvs_file, line))
	{
		stringstream lines(line);
		getline(lines, path, separator);
		getline(lines, tag);
		if (!path.empty() && !tag.empty())
		{
			tmp_img = imread(path, IMREAD_GRAYSCALE);   //读入图像时转成灰度图
			assert(!tmp_img.empty());
			img.push_back(tmp_img);
			lable.push_back(atoi(tag.c_str()));
		}
	}
	tmp_img.release();
}

int main()
{
	vector<Mat> faces;
	vector<int> labels;

	string cvs_path = ".\\face_recg\\face\\at.txt";
	try
	{
		read_cvs(cvs_path, faces, labels);	//读取人脸图像和标签
	}
	catch (cv::Exception e)
	{
		printf("Error open file, reason: %s\n", e.msg);
		return -1;
	}

	if (faces.size() <= 1)
	{
		printf("Too few face images\n");
		return -1;
	}

	/* 从训练集中读取一张图片作为测试图 */
	Mat test_face;
	int sample_num = 0;
	int test_label;

	sample_num = (int)faces.size();
	test_face = faces[sample_num - 1];
	test_label = labels[sample_num - 1];

	faces.pop_back();
	labels.pop_back();

	double start1, start2, start3;
	double end1, end2, end3;

	/* 创建人脸识别的模型并进行训练,之后保存训练结果 */
	//PCA特征脸算法
	start1 = (double)getTickCount();
	Ptr<EigenFaceRecognizer> eigen_model = EigenFaceRecognizer::create();
	eigen_model->train(faces, labels);
	eigen_model->save("my_eigen_face_model.xml");
	end1 = (getTickCount() - start1) / getTickFrequency();

	//LDA线性判别分析
	start2 = (double)getTickCount();
	Ptr<FisherFaceRecognizer> fisher_model = FisherFaceRecognizer::create();
	fisher_model->train(faces, labels);
	fisher_model->save("my_fisher_face_model.xml");
	end2 = (getTickCount() - start2) / getTickFrequency();

	//LBP局部二值模式直方图
	start3 = (double)getTickCount();
	Ptr<LBPHFaceRecognizer> lbph_model = LBPHFaceRecognizer::create();
	lbph_model->train(faces, labels);
	lbph_model->save("my_lbph_face_model.xml");
	end3 = (getTickCount() - start3) / getTickFrequency();

	printf("PAC算法训练所用时间:	%f s\n", end1);
	printf("LDA算法训练所用时间:	%f s\n", end2);
	printf("LBP算法训练所用时间:	%f s\n", end3);

	//对人脸进行测试,查看是否能够识别
	int p_label;

	p_label = eigen_model->predict(test_face);
	printf("(PCA)Test label is: %d, predict label is: %d\n", test_label, p_label);

	p_label = fisher_model->predict(test_face);
	printf("(LDA)Test label is: %d, predict label is: %d\n", test_label, p_label);

	p_label = lbph_model->predict(test_face);
	printf("(LBP)Test label is: %d, predict label is: %d\n", test_label, p_label);

	waitKey(0);

	return 0;
}

人脸识别程序说明

训练和测试使用灰度图。EigenFaceRecognizer、 FisherFaceRecognizer两个类在调用train训练时可以使用BGR图像,但是训练结果中保存的是单通道的灰度图结果。可以查看生成的训练结果文件my_eigen_face_model.xml: 
 
可以看到在训练结果中保存的训练集是1*10304大小的图片,而我训练集图片大小为92 * 112(92 * 112 = 10304)。在对测试图进行人脸识别时,必须确保测试图大小与保存的训练集大小一致,否则会报错。而LBPHFaceRecognizer类必须使用灰度图进行训练,不然也会报错。

结果

可以看到程序可以对人脸进行识别,正确给出标签值。 

从算法耗时来看,PAC最耗时,LDA次之,LBP最省时。

从生成的训练结果文件来看,eigen算法最大,Fisher算法最小。

 

  • 11
    点赞
  • 135
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值