【OpenCV学习笔记 022】人脸识别 小试牛刀

现代人脸检测技术有十分广泛的应用领域,传言iphone8可以进行人脸解锁,这项技术将给用户带来全新的体验。人脸识别技术究竟为何物呢?
人脸识别包括人脸检测和人脸匹配两个部分。在人脸检测算法被提出和发展的过程中,最具有代表性莫过于基于AdaBoost的人脸检测算法。AdaBoost算法主要包括五个关键技术分别为PAC学习模型、Harr-like特征、积分图、分类器训练及检测器级联。一篇通俗易懂的文章推荐给大家,《浅析人脸检测之Haar分类器方法:Haar特征、积分图、 AdaBoost 、级联》。在进行人脸检测时我们需要用到一个.xml的级联分类器,该文件存在于OpenCV安装目录下的\sources\data\haarcascades内,也可以自己训练生成,参考:《【OpenCV学习笔记 021】haartraining训练生成xml过程》。

一、人脸检测

以下为人脸检测代码的实现:

#include "opencv2/core/core.hpp"  
#include "opencv2/objdetect/objdetect.hpp"  
#include "opencv2/highgui/highgui.hpp"  
#include "opencv2/imgproc/imgproc.hpp"  

#include   
#include   

using namespace std;
using namespace cv;
string face_cascade_name = "haarcascade_frontalface_alt.xml";
//该文件存在于OpenCV安装目录下的\sources\data\haarcascades内,需要将该xml文件复制到当前工程目录下  
CascadeClassifier face_cascade;
void detectAndDisplay(Mat frame);
int main(int argc, char** argv){
    Mat image;
    image = imread("img1.jpg", 1);  //当前工程的image目录下的mm.jpg文件,注意目录符号  

    detectAndDisplay(image); //调用人脸检测函数  
    waitKey(0);
    //暂停显示一下。  
}

void detectAndDisplay(Mat face){
    vector faces;
    Mat face_gray;

    if (!face_cascade.load(face_cascade_name)){
        printf("级联分类器错误,可能未找到文件,拷贝该文件到工程目录下!\n");
        return;
    }

    cvtColor(face, face_gray, CV_BGR2GRAY);  //rgb类型转换为灰度类型        
    equalizeHist(face_gray, face_gray);   //直方图均衡化  

    face_cascade.detectMultiScale(face_gray, faces, 1.1, 2, 0 | CV_HAAR_SCALE_IMAGE, Size(1, 1));

    for (int i = 0; i < faces.size(); i++){
        Point center(faces[i].x + faces[i].width*0.5, faces[i].y + faces[i].height*0.5);
        ellipse(face, center, Size(faces[i].width*0.5, faces[i].height*0.5), 0, 0, 360, Scalar(255, 0, 0), 2, 7, 0);
    }

    imshow("人脸检测", face);
}
检测结果:

【OpenCV学习笔记 022】人脸识别 小试牛刀_第1张图片

关键函数解读:

(1)直方图均衡化函数

//! normalizes the grayscale image brightness and contrast by normalizing its histogram
CV_EXPORTS_W void equalizeHist( InputArray src, OutputArray dst );

src 源图像

dst 目标 (均衡化后) 图像

直方图均衡化是通过拉伸像素强度分布范围来增强图像对比度的一种方法。详细内容可以参考OpenCV API imgproc模块图像处理直方图均衡化: http://www.opencv.org.cn/opencvdoc/2.3.2/html/doc/tutorials/imgproc/histograms/histogram_equalization/histogram_equalization.html

(2)CascadeClassifier初始化函数

bool CascadeClassifier::read(constFileNode&root)

{

    if( !data.read(root) )//data成员变量的读取

        return false;

    // load features---特征的读取

    featureEvaluator= FeatureEvaluator::create(data.featureType);

    FileNodefn =root[CC_FEATURES];

    if( fn.empty() )

        return false;

    return featureEvaluator->read(fn);
}

CascadeClassifier类中既有load也有read函数,二者是相同的,load将引用read函数。read的结果一是初始化了分类器的特征类型、最小检测窗口size等参数;二是建立级联的分类器树;三是提取了xml中的特征池。

(3)多尺度检测函数

在load分类器之后,可以调用该函数对一幅图像做多尺度检测。detectMultiscale只是对detectSingleScale做了一次多尺度的封装。在单一尺度的图像中使用detectSingleScale进行检测。

 CV_WRAP virtual void detectMultiScale( const Mat& image,
                                   CV_OUT vector& objects,
                                   double scaleFactor=1.1,
                                   int minNeighbors=3, int flags=0,
                                   Size minSize=Size(),
                                   Size maxSize=Size() );

【OpenCV学习笔记 022】人脸识别 小试牛刀_第2张图片

imageMat类型的图像

objects—检测得到的矩形

scaleFactor指定在每个图像缩放处缩小图像大小的参数

minNeighbors参数指定每个候选矩形应该保留多少个领域

flags参数具有与在函数cvHaarDetectObjects中的旧级联相同的含义。它不用于新的级联。

minSize最小可能的对象大小。小于该值的对象将被忽略。

maxSize最大可能的对象大小。大于该值的对象将被忽略。

(4)ellipse绘制椭圆圆弧和椭圆扇形

//! draws an elliptic arc, ellipse sector or a rotated ellipse in the image
CV_EXPORTS_W void ellipse(CV_IN_OUT Mat& img, Point center, Size axes,
                        double angle, double startAngle, double endAngle,
                        const Scalar& color, int thickness=1,
                        int lineType=8, int shift=0);

imageMat类型的图像

center—椭圆圆心坐标

axes—轴的长度

angle—偏转的角度

startAngle—圆弧起始角的角度

endAngle—圆弧终结角的角度

color—线条的颜色

thickness—线条的粗细程度

lineType—线条的类型,见CVLINE的描述

shift—圆心坐标点和数轴的精度

二、PCA人脸识别

以下为PCA人脸识别代码的实现:

#include 
#include "cv.h"
#include "cvaux.h"
#include "highgui.h"
#include "time.h"
#include "iostream"

using namespace cv;
using namespace std;

//定义几个重要的全局变量
IplImage **faceImgArr = 0;//指向训练人脸和测试人脸的指针(在学习和识别阶段指向不同)
CvMat *personNumTruthMat = 0;//人脸图像的ID号
int nTrainFaces = 0;//训练图像的数目
int nEigens = 0;//自己取的主要特征值数目
IplImage *pAvgTrainImg = 0;//训练人脸数据的平均值
IplImage **eigenVectArr = 0;//投影矩阵,也即主特征向量
CvMat *eigenValMat = 0;//特征值
CvMat *projectedTrainFaceMat = 0; //训练图像的投影 

//函数原型
void learn();
void recognize();
void doPCA();
void storeTrainingData();
int loadTrainingData(CvMat **pTrainPersonNumMat);
int findNearestNeighbor(float * projectedTestFace);
int loadFaceImgArray(char * filename);
void printUsage();

//主函数,主要包括学习和识别两个阶段,需要运行两次,通过命令行传入的参数区分
int main(){

	//learn();
	recognize();
}

void learn(){

	cout << "开始训练过程" << endl;
	//开始计时
	clock_t start, finish;
	double duration;
	start = clock();
	int i, offset;
	//加载训练图像集
	nTrainFaces = loadFaceImgArray("train.txt");
	if (nTrainFaces < 2)
	{
		fprintf(stderr,"Need 2 or more training faces\n" "Input file contains only %d\n", nTrainFaces);
		return;
	}
	//进行主成分分析
	doPCA();
	//将训练图集投影到子空间中
	projectedTrainFaceMat = cvCreateMat(nTrainFaces, nEigens, CV_32FC1);
	offset = projectedTrainFaceMat->step / sizeof(float);
	for (i = 0; i < nTrainFaces;i++)
	{
		cvEigenDecomposite(faceImgArr[i], nEigens, eigenVectArr, 0, 0, pAvgTrainImg, projectedTrainFaceMat->data.fl + i*offset);
	}

	//将训练阶段得到的特征值,投影矩阵等数据存为.xml文件,以备测试时使用
	storeTrainingData();
	//结束计时
	finish = clock();
	duration = (double)(finish - start) / CLOCKS_PER_SEC;
	cout << "训练过程结束,共耗时:" << duration << "秒" << endl;

}

//识别阶段代码
void recognize(){

	cout << "开始识别过程" << endl;
	//开始计时
	clock_t start, finish;
	double duration;
	start = clock();

	//测试人脸数
	int i, nTestFaces = 0;
	//训练阶段的人脸数
	CvMat * trainPersonNumMat = 0;
	float * projectedTestFace = 0;
	//加载测试图像,并返回人脸数
	nTestFaces = loadFaceImgArray("test.txt");
	printf("%d test faces loaded\n", nTestFaces);
	//加载保存在.xml文件中的训练结果
	if (!loadTrainingData(&trainPersonNumMat))
		return;
	projectedTestFace = (float*)cvAlloc(nEigens*sizeof(float));

	for (i = 0; i, nTestFaces;i++)
	{
		int iNearest, nearest, truth;
		//将测试图像投影到子空间中
		cvEigenDecomposite(faceImgArr[i], nEigens, eigenVectArr, 0, 0, pAvgTrainImg, projectedTestFace);
		iNearest = findNearestNeighbor(projectedTestFace);
		truth = personNumTruthMat->data.i[i];
		nearest = trainPersonNumMat->data.i[iNearest];
		printf("nearest = %d, Truth = %d\n", nearest, truth);
	}

	//结束计时
	finish = clock();
	duration = (double)(finish - start) / CLOCKS_PER_SEC;
	cout << "识别过程结束,共耗时:" << duration << "秒" << endl;
}

//加载保存过的训练结果
int loadTrainingData(CvMat ** pTrainPersonNumMat)
{

	CvFileStorage * fileStorage;
	int i;
	fileStorage = cvOpenFileStorage("facedata.xml", 0, CV_STORAGE_READ);
	if (!fileStorage)
	{
		fprintf(stderr, "Can't open facedata.xml\n");
		return 0;
	}

	nEigens = cvReadIntByName(fileStorage, 0, "nEigens", 0);
	nTrainFaces = cvReadIntByName(fileStorage, 0, "nTrainFaces", 0);
	*pTrainPersonNumMat = (CvMat *)cvReadByName(fileStorage, 0, "trainPersonNumMat", 0);
	eigenValMat = (CvMat *)cvReadByName(fileStorage, 0, "eigenValMat", 0);
	projectedTrainFaceMat = (CvMat *)cvReadByName(fileStorage, 0, "projectedTrainFaceMat", 0);
	pAvgTrainImg = (IplImage *)cvReadByName(fileStorage, 0, "avgTrainImg", 0);
	eigenVectArr = (IplImage **)cvAlloc(nTrainFaces*sizeof(IplImage *));
	for (i = 0; i < nEigens; i++)
	{
		char varname[200];
		sprintf(varname, "eigenVect_%d", i);
		eigenVectArr[i] = (IplImage *)cvReadByName(fileStorage, 0, varname, 0);
	}
	cvReleaseFileStorage(&fileStorage);
	return 1;
}

//存储训练结果
void storeTrainingData(){

	CvFileStorage * fileStorage;
	int i;
	fileStorage = cvOpenFileStorage("facedata.xml", 0, CV_STORAGE_WRITE);
	//存储特征值,投影矩阵,平均矩阵等训练结果
	cvWriteInt(fileStorage, "nEigens", nEigens);
	cvWriteInt(fileStorage, "nTrainFaces", nTrainFaces);
	cvWrite(fileStorage, "trainPersonNumMat", personNumTruthMat, cvAttrList(0, 0));
	cvWrite(fileStorage, "eigenValMat", eigenValMat, cvAttrList(0, 0));
	cvWrite(fileStorage, "projectedTrainFaceMat", projectedTrainFaceMat, cvAttrList(0, 0));
	cvWrite(fileStorage, "avgTrainImg", pAvgTrainImg, cvAttrList(0, 0));

	for (i = 0; i < nEigens; i++)
	{

		char varname[200];
		sprintf(varname, "eigenVect_%d", i);
		cvWrite(fileStorage, varname, eigenVectArr[i], cvAttrList(0, 0));
		cvNormalize(eigenVectArr[i], eigenVectArr[i], 255, 0, CV_L2, 0);
		cvNamedWindow("demo", CV_WINDOW_AUTOSIZE);
		cvShowImage("demo", eigenVectArr[i]);
		cvWaitKey(100);
	}

	cvNormalize(pAvgTrainImg, pAvgTrainImg, 255, 0, CV_L1, 0);
	cvNamedWindow("demo", CV_WINDOW_AUTOSIZE);
	cvShowImage("demo", pAvgTrainImg);
	cvWaitKey(100);
	cvReleaseFileStorage(&fileStorage);
}

//寻找最接近的图像
int findNearestNeighbor(float * projectedTestFace){

	//定义最小距离,并初始化为无穷大
	double leastDistSq = DBL_MAX, accuracy;
	int i, iTrain, iNearest = 0;
	double a[10];
	for (iTrain = 0; iTrain < nTrainFaces; iTrain++)
	{
		double distSq = 0;
		for (i = 0; i < nEigens; i++)
		{
			float d_i =projectedTestFace[i] -projectedTrainFaceMat->data.fl[iTrain*nEigens + i];

			// Mahalanobis算法计算的距离
			//distSq += d_i*d_i; // Euclidean算法计算的距离
			distSq += d_i*d_i / eigenValMat->data.fl[i];
		}
		a[iTrain] = distSq;
		if (distSq < leastDistSq)
		{

			leastDistSq = distSq;
			iNearest = iTrain;
		}
	}
	//求阈值

	double max = a[0], threshold;
	int j;
	for (j = 1; j < 10; j++)
	{

		if (max < a[j])
			max = a[j];
		else
			max = max;
	}

	threshold = max / 2;
	//求相似率
	accuracy = 1 - leastDistSq / threshold;
	cout << "相似率为:" << accuracy << endl;
	return iNearest;
}

//主成分分析
void doPCA()
{

	int i;
	//终止算法准则
	CvTermCriteria calcLimit;
	//构造图像
	CvSize faceImgSize;
	// 自己设置主特征值个数
	nEigens = nTrainFaces - 1;
	//分配特征向量存储空间
	faceImgSize.width = faceImgArr[0]->width;
	faceImgSize.height = faceImgArr[0]->height;

	//分配个数为主特征值个数
	eigenVectArr = (IplImage**)cvAlloc(sizeof(IplImage*) * nEigens);
	for (i = 0; i < nEigens; i++)
		eigenVectArr[i] = cvCreateImage(faceImgSize, IPL_DEPTH_32F, 1);
	//分配主特征值存储空间
	eigenValMat = cvCreateMat(1, nEigens, CV_32FC1);
	// 分配平均图像存储空间
	pAvgTrainImg = cvCreateImage(faceImgSize, IPL_DEPTH_32F, 1);
	// 设定PCA分析结束条件
	calcLimit = cvTermCriteria(CV_TERMCRIT_ITER, nEigens, 1);
	// 计算平均图像,特征值,特征向量

		cvCalcEigenObjects(
		nTrainFaces,
		(void*)faceImgArr,
		(void*)eigenVectArr,
		CV_EIGOBJ_NO_CALLBACK,
		0,
		0,
		&calcLimit,
		pAvgTrainImg,
		eigenValMat->data.fl);
	//归一化大小
	cvNormalize(eigenValMat, eigenValMat, 1, 0, CV_L1, 0);

}

//加载txt文件的列举的图像
int loadFaceImgArray(char * filename)
{

	FILE * imgListFile = 0;
	char imgFilename[512];
	int iFace, nFaces = 0;
	if (!(imgListFile = fopen(filename, "r")))
	{
		fprintf(stderr, "Can\'t open file %s\n", filename);
		return 0;
	}

	// 统计人脸数
	while (fgets(imgFilename, 512, imgListFile)) ++nFaces;
	rewind(imgListFile);
	// 分配人脸图像存储空间和人脸ID号存储空间
	faceImgArr = (IplImage **)cvAlloc(nFaces*sizeof(IplImage *));
	personNumTruthMat = cvCreateMat(1, nFaces, CV_32SC1);
	for (iFace = 0; iFace < nFaces; iFace++)
	{
		// 从文件中读取序号和人脸名称
		fscanf(imgListFile,
			"%d %s", personNumTruthMat->data.i + iFace, imgFilename);

		// 加载人脸图像
		faceImgArr[iFace] = cvLoadImage(imgFilename, CV_LOAD_IMAGE_GRAYSCALE);
		if (!faceImgArr[iFace])
		{
			fprintf(stderr, "Can\'t load image from %s\n", imgFilename);
			return 0;
		}

		cvNamedWindow("demo", CV_WINDOW_AUTOSIZE);
		cvShowImage("demo", faceImgArr[iFace]);
		cvWaitKey(100);
	}
	fclose(imgListFile);
	return nFaces;
}

void printUsage()
{
	printf("Usage: eigenface \n"," Valid commands are\n"" train\n"" test\n");
}

获得特征空间的函数:

/* Calculates eigen values and vectors of covariation matrix of a set of
   arrays */
CVAPI(void)  cvCalcEigenObjects( int nObjects, void* input, void* output,
                                 int ioFlags, int ioBufSize, void* userData,
                                 CvTermCriteria* calcLimit, IplImage* avg,
                                 float* eigVals );

nObjects:目标的数目,即输入训练图片的数目。
input:输入训练的图片。
output:输出特征脸,总共有nEigens
ioFlags、ioBufSize:默认为0
userData:指向回调函数(callback function)必须数据结构体的指针。
calcLimit:终止迭代计算目标特征的条件。根据calcLimit的参数,计算会在前nEigens主要特征目标被提取后结束(这句话有点绕,应该就是提取了前nEigens个特征值,),另一种结束的情况是:目前特征值同最s大特征值的比值降至calcLimit的epsilon值之下。
赋值如下calcLimit = cvTermCriteria( CV_TERMCRIT_ITER, nEigens, 1);

图像在特征空间的投影:

/* Projects image to eigen space (finds all decomposion coefficients */
CVAPI(void)  cvEigenDecomposite( IplImage* obj, int nEigObjs, void* eigInput,
                                 int ioFlags, void* userData, IplImage* avg,
                                 float* coeffs );

obj—输入图像,训练或识别图像

nEigObjs—特征空间的eigen数量

eigInput—特征空间中的特征脸

ioFlags— 默认为0

userData默认为0

avg—特征空间中的平均图像

coeffs—这是唯一一个输出,即人脸在子空间的投影,特征值

Reference:

http://blog.csdn.net/liulina603/article/details/28633403

http://blog.csdn.net/delltdk/article/details/9186875

http://kns.cnki.NET/KCMS/detail/detail.aspx?dbcode=CMFD&dbname=CMFD2011&filename=1011112112.nh&uid=WEEvREcwSlJHSldRa1FhcEE0NXdnek9UdVllQVRuUVNSSGU2VEMxUEs5bz0=$9A4hF_YAuvQ5obgVAqNKPCYcEjKensW4ggI8Fm4gTkoUKaID8j8gFw!!&v=MTQ4NzFxRnlEblY3dktWRjI2SDdLNUhORE5yWkViUElSOGVYMUx1eFlTN0RoMVQzcVRyV00xRnJDVVJMMmZaZVI=

http://kns.cnki.net/KCMS/detail/detail.aspx?dbcode=CMFD&dbname=CMFD201402&filename=1014268233.nh&uid=WEEvREcwSlJHSldRa1FhcTdWZDlscUEvR2E2bTdjZHczQnRHTWVoWFprRT0=$9A4hF_YAuvQ5obgVAqNKPCYcEjKensW4ggI8Fm4gTkoUKaID8j8gFw!!&v=MDE1NjFGMjZHckcrRnRQUHJKRWJQSVI4ZVgxTHV4WVM3RGgxVDNxVHJXTTFGckNVUkwyZlplUnBGeTNnVTcvS1Y=

http://blog.csdn.net/delltdk/article/details/9984719

http://docs.opencv.org/2.4/modules/objdetect/doc/cascade_classification.html

http://blog.csdn.net/gxiaob/article/details/9396955

https://wenku.baidu.com/view/4db3187152ea551811a68728.html

http://blog.csdn.net/liulina603/article/details/8089023

你可能感兴趣的:(OpenCV,opencv编程笔记)