当前位置:   article > 正文

OpenCV人脸识别类FaceRecognizer

facerecognizer

官方文档:https://docs.opencv.org/3.4.3/dd/d81/facerec_8hpp.html

源代码在 .\opencv_contrib-3.4.3\modules\face\src中

Opencv实现

从OpenCV2.4开始,加入新的类FaceRecognizer,可以用它方便的进行人脸识别实验。

人脸识别的任务也就是两大部分,训练和预测,分别对应着train函数和predict函数,还有对应的数据加载保存函数save和load。

     先来说说训练的过程,train函数的两个参数也很简单,训练的图像组vector<Mat>和对应的标签组vector<int>,这个label标签只需保证同一个人的标签相同即可,不需要保证图像的按标签顺序输入。
    对于预测,有两种调用,其中的参数有测试图像、返回的标签值和测试样本和标签样本的相似性。返回的标签值为-1,说明测试样本在训练集中无对应或距离较远。

目前支持的算法有:

  1.        Eigenface特征脸   createEigenFaceRecognizer()
  2.        Fisherface             createFisherFaceRecognizer()
  3.       LBP局部二值直方图    createLBPHFaceRecognizer()

不过这些模块放在contri库中:

  1. CV_EXPORTS_W Ptr<FaceRecognizer> **createEigenFaceRecognizer**(int num_components = 0, double threshold = DBL_MAX);
  2. CV_EXPORTS_W Ptr<FaceRecognizer> **createFisherFaceRecognizer**(int num_components = 0, double threshold = DBL_MAX);
  3. CV_EXPORTS_W Ptr<FaceRecognizer> **createLBPHFaceRecognizer**(int radius=1, int neighbors=8,
  4. int grid_x=8, int grid_y=8, double threshold = DBL_MAX);
  1. num_components为PCA主成分的维数;threshold:预测时的阈值。主成分这里有一个选取的准则,要根据输入数据的大小而决定,通常认为80维主成分是足够的。除了这两个输入参数外,还有eigenvalues和eigenvectors分别代表特征值和特征向量,mean参数为训练样本的平均值,projections为训练数据的预测值,labels为预测时的阈值。
  2. FisherFace,也有num_components和threshold两个参数和其他5个参数,FisherFace的降维是LDA得到的。特别需要强调的是,EigenFace和FisherFace的训练图像和测试图像都必须是灰度图,而且是经过归一化裁剪过的。
  3. 对于LBPHFace,参数包括半径radius,邻域大小即采样点个数neighbors,x和y方向的单元格数目grid_x,grid_y,还有两个参数histograms为训练数据得到的直方图,labels为直方图对应的标签。这个方法也要求训练和测试的图像是灰度图

from:https://blog.csdn.net/yang_xian521/article/details/7735224


Eigenface

1、PCA类是OpenCV实现主要成分分析的类,在人脸识别等机器学习的项目中大量应用,使用前需要先实例化对象。

函数原型:
PCA(InputArray data, InputArray mean, int flags, int maxComponents = 0);
PCA(InputArray data, InputArray mean, int flags, double retainedVariance);

参数说明:
data:需要PCA的数据,每一行(列)表示一个样本;
mean:平均值;如果矩阵是空的(noArray()),则从数据中计算; 
flags:操作标志,具体参数如下:
                            DATA_AS_ROW :每一行表示一个样本;
                            DATA_AS_COL :每一列表示一个样本;
maxComponents :PCA应保留的最大组件数;默认情况下,所有组件都保留;
retainedVariance:PCA应保留的方差百分比。使用这个参数将让PCA决定保留多少组件,但它将始终保持至少2。 

2、PCA::project函数
    该函数的作用是将输入数据vec(该数据是用来提取PCA特征的原始数据)投影到PCA主成分空间中去,返回每一个样本主成分特征组成的矩阵。因为经过PCA处理后,原始数据的维数降低了,因此原始数据集中的每一个样本的维数都变了,由改变后的样本集就组成了本函数的返回值。

函数原型:
Mat project(InputArray vec) const;
参数说明:vec:参与投影(降维)的数据

eigenvalues:特征值

eigenvectors:特征向量

train函数的部分代码:

  1. // get labels
  2. Mat labels = _local_labels.getMat();
  3. // observations in row
  4. Mat data = asRowMatrix(_src, CV_64FC1);
  5. // number of samples
  6. int n = data.rows;
  7. // assert there are as much samples as labels
  8. if(static_cast<int>(labels.total()) != n) {
  9. String error_message = format("The number of samples (src) must equal the number of labels (labels)! len(src)=%d, len(labels)=%d.", n, labels.total());
  10. CV_Error(Error::StsBadArg, error_message);
  11. }
  12. // clear existing model data
  13. _labels.release();
  14. _projections.clear();
  15. // clip number of components to be valid
  16. if((_num_components <= 0) || (_num_components > n))
  17. _num_components = n;
  18. // perform the PCA
  19. PCA pca(data, Mat(), PCA::DATA_AS_ROW, _num_components);
  20. // copy the PCA results
  21. _mean = pca.mean.reshape(1,1); // store the mean vector
  22. _eigenvalues = pca.eigenvalues.clone(); // eigenvalues by row
  23. transpose(pca.eigenvectors, _eigenvectors); // eigenvectors by column
  24. // store labels for prediction
  25. _labels = labels.clone();
  26. // save projections
  27. for(int sampleIdx = 0; sampleIdx < data.rows; sampleIdx++) {
  28. Mat p = LDA::subspaceProject(_eigenvectors, _mean, data.row(sampleIdx));
  29. _projections.push_back(p);
  30. }

这里讲一下,从上面代码我们看到使用了LDA(它不是用在FisherFace中的吗,通过看下面FisherFace中LDA的用法,发现两者还是有区别的,其实这里只是用了LDA类中的subspaceProject函数,用于计算投影,实际上,投影的特征向量_eigenvectors不还是PCA求解的主成分向量嘛。这与LDA算法原理无关!!!)

  Mat p = LDA::subspaceProject(_eigenvectors, _mean, data.row(sampleIdx));// 作用:映射输入图像到特征空间

      你想啊,对输入m个图像矩阵 m*n,进行PCA,求出前K个最大特征值对应的特征向量,然后用输入的图像矩阵乘以K个特征向量,相当于把图像投影到K个向量上,得到一个特征图。参数_projections存放M个矩阵。

test:

  1. void Eigenfaces::predict(InputArray _src, Ptr<PredictCollector> collector) const {
  2. // get data
  3. Mat src = _src.getMat();
  4. // make sure the user is passing correct data
  5. if(_projections.empty()) {
  6. // throw error if no data (or simply return -1?)
  7. String error_message = "This Eigenfaces model is not computed yet. Did you call Eigenfaces::train?";
  8. CV_Error(Error::StsError, error_message);
  9. } else if(_eigenvectors.rows != static_cast<int>(src.total())) {
  10. // check data alignment just for clearer exception messages
  11. String error_message = format("Wrong input image size. Reason: Training and Test images must be of equal size! Expected an image with %d elements, but got %d.", _eigenvectors.rows, src.total());
  12. CV_Error(Error::StsBadArg, error_message);
  13. }
  14. // project into PCA subspace 将测试图像投影到特征向量上,得到P
  15. Mat q = LDA::subspaceProject(_eigenvectors, _mean, src.reshape(1, 1));
  16. collector->init(_projections.size());
  17. for (size_t sampleIdx = 0; sampleIdx < _projections.size(); sampleIdx++) {
  18. //把测试图像的特征图,与之前训练得到的所有图的特征图进行比较
  19. double dist = norm(_projections[sampleIdx], q, NORM_L2);//范数,即每个对应像素的差异
  20. int label = _labels.at<int>((int)sampleIdx);
  21. if (!collector->collect(label, dist))return;//不知道是干嘛的??
  22. }
  23. }

2、Fisher face方法(PCA+LDA)

LDA(线性判别分析或称Fisher线性判别),PCA(主成份分析)代码及表情识别中的应用

第八章 采用PCA(主成分分析)或LDA(线性判别分析)的人脸识别(二)

LDA算法原理可参见:人脸识别算法二:Fisherface(LDA)

      为了提高识别效率,在对特征向量进行降维的同时还需要寻求更有利用分类的向量。 
      Fisher Fface方法是主成分分析(PCA)与Fisher线性判别分析(FLD Fisher Linear Discriminant Analysis)相结合的算法,算法首先对高维特征样本进行PCA降维,投影到低维特征空间,再采用LDA方法得到最优判别向量。

Fisher线性判别分析: 
     基本思想是计算出使Fisher准则函数达到极值的向量,并将此向量作为最佳投影方向,样本在该方向上进行投影,投影后的特征向量具有类间离散度最大,类内离散度最小特点。将特征向量(直接将图像转换成1*mn)映射到K个低维的向量上(这K个低维的向量就是判别向量),然后判断离哪个类别最近,就属于哪个人的人脸。

train函数的部分源码:

  1. // get data
  2. Mat labels = _lbls.getMat();
  3. Mat data = asRowMatrix(src, CV_64FC1);
  4. // number of samples
  5. int N = data.rows;
  6. // make sure labels are passed in correct shape
  7. if(labels.total() != (size_t) N) {
  8. String error_message = format("The number of samples (src) must equal the number of labels (labels)! len(src)=%d, len(labels)=%d.", N, labels.total());
  9. CV_Error(Error::StsBadArg, error_message);
  10. } else if(labels.rows != 1 && labels.cols != 1) {
  11. String error_message = format("Expected the labels in a matrix with one row or column! Given dimensions are rows=%s, cols=%d.", labels.rows, labels.cols);
  12. CV_Error(Error::StsBadArg, error_message);
  13. }
  14. // clear existing model data
  15. _labels.release();
  16. _projections.clear();
  17. // safely copy from cv::Mat to std::vector
  18. std::vector<int> ll;
  19. for(unsigned int i = 0; i < labels.total(); i++) {
  20. ll.push_back(labels.at<int>(i));
  21. }
  22. // get the number of unique classes
  23. int C = (int) remove_dups(ll).size();
  24. // clip number of components to be a valid number
  25. if((_num_components <= 0) || (_num_components > (C-1)))
  26. _num_components = (C-1);
  27. // perform a PCA and keep (N-C) components
  28. PCA pca(data, Mat(), PCA::DATA_AS_ROW, (N-C));
  29. // 将样本投影到主成分的特征图最为输入,计算它的LDA向量,构造 LDA on it,注意这里输入的不是原图
  30. LDA lda(pca.project(data),labels, _num_components);
  31. // 保存total mean vector
  32. _mean = pca.mean.reshape(1,1);
  33. // store labels
  34. _labels = labels.clone();
  35. // lda的特征向量
  36. lda.eigenvalues().convertTo(_eigenvalues, CV_64FC1);
  37. // 计算投影矩阵:pca.eigenvectors * lda.eigenvectors.存放到 _eigenvectors中
  38. // Note: OpenCV stores the eigenvectors by row,要先转制
  39. gemm(pca.eigenvectors, lda.eigenvectors(), 1.0, Mat(), 0.0, _eigenvectors, GEMM_1_T);
  40. // store the projections of the original data
  41. for(int sampleIdx = 0; sampleIdx < data.rows; sampleIdx++) {
  42. //把样本图像头像到特征空间_eigenvectors中
  43. Mat p = LDA::subspaceProject(_eigenvectors, _mean, data.row(sampleIdx));
  44. _projections.push_back(p);
  45. }
  46. }

1、LDA   lda(const Mat& src, vector<int> labels,int num_components = 0) :

  1. 第一个为数据集(行排列)
  2. 第二个为int类型的标签数组和第一个数据集相对应
  3. 第三个为默认降维的维度为c-1,c是类别的数量

2、void gemm(InputArray src1, InputArray src2, double alpha, InputArray src3, double gamma, OutputArray dst, int flags=0 )

src1 – 第一个矩阵
src2 – 第二个矩阵 of the same type as src1.
alpha – weight of the matrix product.
src3 – third optional delta matrix added to the matrix product; it should have the same type
as src1 and src2.
beta – weight of src3.
dst – output matrix; it has the proper size and the same type as input matrices.
flags – operation flags:
– GEMM_1_T transposes src1.
– GEMM_2_T transposes src2.
– GEMM_3_T transposes src3

公式    :      alpha*op(src1 )*op(src12) + beta*src13,

现在以为没弄明白的是:Ptr<PredictCollector> collector?????    if (!collector->collect(label, dist))return;是啥意思

from:https://blog.csdn.net/cshilin/article/details/52200627

调用:predicted返回预测的label:

  1. Ptr<FaceRecognizer> model = createFisherFaceRecognizer();
  2. model->train(images, labels);
  3. Mat img = imread("person1/2.jpg", CV_LOAD_IMAGE_GRAYSCALE);
  4. int predicted = model->predict(img);

     总结:EigenFace主要是使用PCA(主成分分析),通过消除数据中的相关性,将高维图像降低到低维空间,训练集中的样本被映射成低维空间中的一点,需要判断测试图片性别时,先将测试图片映射到低维空间中,然后计算离测试图片最近样本点是哪一个,将最近样本点的性别赋值给测试图片;FisherFace主要利用LDA(线性投影分析)的思想,将样本空间中的男女样本投影到过原点的一条直线上,并确保样本在该线上的投影类内距离最小,类间距离最大,从而分离出识别男女的分界线。

 

LBP

LBP算子也可以用于人脸检测,原理详情参见:https://blog.csdn.net/qq_30815237/article/details/88541546

基于LBP算子的人脸识别算法:

LBP被运用于计算机人脸识别领域时,提取出来的人脸特征通常是以LBP直方图向量进行表达的。 
1. 对预处理后的人脸图像进行分块 
2. 对分块后的各小块图像区域进行LBP特征提取变换 
3. 使用LBP直返图向量作为人脸特征的描述。

     一般分块数越多,人脸表达的效果就会越好,但是分块数越多,会直接导致特征向量维数的增加,会增加计算的复杂度。对每个分块计算LBP值的直方图,然后将所有分块直方图进行连接得到最终的直方图特征向量,这个特征向量代表原来的人脸图像,可以用来描述整体图像。 

 这里写图片描述

     对于这个融合的直方图,我们进行特征分类。  如果训练样本数量越大,分类的效果也会越好,

LBPH的预测进程

预测进程就比较简单了,首先将待查询点图象进行lbp编码并生成空间直方图,然后线性暴力的计算直方图的距离,采用基于直方图的相似性度量的最近邻分类方法来分类,终究输出距离最小的预测种别。部分源码:

  1. // find 1-nearest neighbor
  2. minDist = DBL_MAX;
  3. minClass = -1;
  4. for(size_t sampleIdx = 0; sampleIdx < _histograms.size(); sampleIdx++) {
  5. double dist = compareHist(_histograms[sampleIdx], query, CV_COMP_CHISQR);
  6. if((dist < minDist) && (dist < _threshold)) {
  7. minDist = dist;
  8. minClass = _labels.at<int>((int) sampleIdx);
  9. }
  10. }

 

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/菜鸟追梦旅行/article/detail/278481
推荐阅读
相关标签
  

闽ICP备14008679号