赞
踩
源代码在 .\opencv_contrib-3.4.3\modules\face\src中
从OpenCV2.4开始,加入新的类FaceRecognizer,可以用它方便的进行人脸识别实验。
人脸识别的任务也就是两大部分,训练和预测,分别对应着train函数和predict函数,还有对应的数据加载保存函数save和load。
先来说说训练的过程,train函数的两个参数也很简单,训练的图像组vector<Mat>和对应的标签组vector<int>,这个label标签只需保证同一个人的标签相同即可,不需要保证图像的按标签顺序输入。
对于预测,有两种调用,其中的参数有测试图像、返回的标签值和测试样本和标签样本的相似性。返回的标签值为-1,说明测试样本在训练集中无对应或距离较远。
目前支持的算法有:
不过这些模块放在contri库中:
- CV_EXPORTS_W Ptr<FaceRecognizer> **createEigenFaceRecognizer**(int num_components = 0, double threshold = DBL_MAX);
- CV_EXPORTS_W Ptr<FaceRecognizer> **createFisherFaceRecognizer**(int num_components = 0, double threshold = DBL_MAX);
- CV_EXPORTS_W Ptr<FaceRecognizer> **createLBPHFaceRecognizer**(int radius=1, int neighbors=8,
- int grid_x=8, int grid_y=8, double threshold = DBL_MAX);
from:https://blog.csdn.net/yang_xian521/article/details/7735224
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:参与投影(降维)的数据
train函数的部分代码:
- // get labels
- Mat labels = _local_labels.getMat();
- // observations in row
- Mat data = asRowMatrix(_src, CV_64FC1);
-
- // number of samples
- int n = data.rows;
- // assert there are as much samples as labels
- if(static_cast<int>(labels.total()) != n) {
- 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());
- CV_Error(Error::StsBadArg, error_message);
- }
- // clear existing model data
- _labels.release();
- _projections.clear();
- // clip number of components to be valid
- if((_num_components <= 0) || (_num_components > n))
- _num_components = n;
-
- // perform the PCA
- PCA pca(data, Mat(), PCA::DATA_AS_ROW, _num_components);
- // copy the PCA results
- _mean = pca.mean.reshape(1,1); // store the mean vector
- _eigenvalues = pca.eigenvalues.clone(); // eigenvalues by row
- transpose(pca.eigenvectors, _eigenvectors); // eigenvectors by column
- // store labels for prediction
- _labels = labels.clone();
- // save projections
- for(int sampleIdx = 0; sampleIdx < data.rows; sampleIdx++) {
- Mat p = LDA::subspaceProject(_eigenvectors, _mean, data.row(sampleIdx));
- _projections.push_back(p);
- }
这里讲一下,从上面代码我们看到使用了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个矩阵。
- void Eigenfaces::predict(InputArray _src, Ptr<PredictCollector> collector) const {
- // get data
- Mat src = _src.getMat();
- // make sure the user is passing correct data
- if(_projections.empty()) {
- // throw error if no data (or simply return -1?)
- String error_message = "This Eigenfaces model is not computed yet. Did you call Eigenfaces::train?";
- CV_Error(Error::StsError, error_message);
- } else if(_eigenvectors.rows != static_cast<int>(src.total())) {
- // check data alignment just for clearer exception messages
- 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());
- CV_Error(Error::StsBadArg, error_message);
- }
- // project into PCA subspace 将测试图像投影到特征向量上,得到P
- Mat q = LDA::subspaceProject(_eigenvectors, _mean, src.reshape(1, 1));
- collector->init(_projections.size());
- for (size_t sampleIdx = 0; sampleIdx < _projections.size(); sampleIdx++) {
- //把测试图像的特征图,与之前训练得到的所有图的特征图进行比较
- double dist = norm(_projections[sampleIdx], q, NORM_L2);//范数,即每个对应像素的差异
- int label = _labels.at<int>((int)sampleIdx);
- if (!collector->collect(label, dist))return;//不知道是干嘛的??
- }
- }
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函数的部分源码:
- // get data
- Mat labels = _lbls.getMat();
- Mat data = asRowMatrix(src, CV_64FC1);
- // number of samples
- int N = data.rows;
- // make sure labels are passed in correct shape
- if(labels.total() != (size_t) N) {
- 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());
- CV_Error(Error::StsBadArg, error_message);
- } else if(labels.rows != 1 && labels.cols != 1) {
- 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);
- CV_Error(Error::StsBadArg, error_message);
- }
- // clear existing model data
- _labels.release();
- _projections.clear();
- // safely copy from cv::Mat to std::vector
- std::vector<int> ll;
- for(unsigned int i = 0; i < labels.total(); i++) {
- ll.push_back(labels.at<int>(i));
- }
- // get the number of unique classes
- int C = (int) remove_dups(ll).size();
- // clip number of components to be a valid number
- if((_num_components <= 0) || (_num_components > (C-1)))
- _num_components = (C-1);
- // perform a PCA and keep (N-C) components
- PCA pca(data, Mat(), PCA::DATA_AS_ROW, (N-C));
- // 将样本投影到主成分的特征图最为输入,计算它的LDA向量,构造 LDA on it,注意这里输入的不是原图
- LDA lda(pca.project(data),labels, _num_components);
- // 保存total mean vector
- _mean = pca.mean.reshape(1,1);
- // store labels
- _labels = labels.clone();
- // lda的特征向量
- lda.eigenvalues().convertTo(_eigenvalues, CV_64FC1);
- // 计算投影矩阵:pca.eigenvectors * lda.eigenvectors.存放到 _eigenvectors中
- // Note: OpenCV stores the eigenvectors by row,要先转制
- gemm(pca.eigenvectors, lda.eigenvectors(), 1.0, Mat(), 0.0, _eigenvectors, GEMM_1_T);
- // store the projections of the original data
- for(int sampleIdx = 0; sampleIdx < data.rows; sampleIdx++) {
- //把样本图像头像到特征空间_eigenvectors中
- Mat p = LDA::subspaceProject(_eigenvectors, _mean, data.row(sampleIdx));
- _projections.push_back(p);
- }
- }
1、LDA lda(const Mat& src, vector<int> labels,int num_components = 0) :
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:
- Ptr<FaceRecognizer> model = createFisherFaceRecognizer();
- model->train(images, labels);
-
- Mat img = imread("person1/2.jpg", CV_LOAD_IMAGE_GRAYSCALE);
- int predicted = model->predict(img);
总结:EigenFace主要是使用PCA(主成分分析),通过消除数据中的相关性,将高维图像降低到低维空间,训练集中的样本被映射成低维空间中的一点,需要判断测试图片性别时,先将测试图片映射到低维空间中,然后计算离测试图片最近样本点是哪一个,将最近样本点的性别赋值给测试图片;FisherFace主要利用LDA(线性投影分析)的思想,将样本空间中的男女样本投影到过原点的一条直线上,并确保样本在该线上的投影类内距离最小,类间距离最大,从而分离出识别男女的分界线。
LBP算子也可以用于人脸检测,原理详情参见:https://blog.csdn.net/qq_30815237/article/details/88541546
基于LBP算子的人脸识别算法:
LBP被运用于计算机人脸识别领域时,提取出来的人脸特征通常是以LBP直方图向量进行表达的。
1. 对预处理后的人脸图像进行分块
2. 对分块后的各小块图像区域进行LBP特征提取变换
3. 使用LBP直返图向量作为人脸特征的描述。
一般分块数越多,人脸表达的效果就会越好,但是分块数越多,会直接导致特征向量维数的增加,会增加计算的复杂度。对每个分块计算LBP值的直方图,然后将所有分块直方图进行连接得到最终的直方图特征向量,这个特征向量代表原来的人脸图像,可以用来描述整体图像。
对于这个融合的直方图,我们进行特征分类。 如果训练样本数量越大,分类的效果也会越好,
预测进程就比较简单了,首先将待查询点图象进行lbp编码并生成空间直方图,然后线性暴力的计算直方图的距离,采用基于直方图的相似性度量的最近邻分类方法来分类,终究输出距离最小的预测种别。部分源码:
- // find 1-nearest neighbor
- minDist = DBL_MAX;
- minClass = -1;
- for(size_t sampleIdx = 0; sampleIdx < _histograms.size(); sampleIdx++) {
- double dist = compareHist(_histograms[sampleIdx], query, CV_COMP_CHISQR);
- if((dist < minDist) && (dist < _threshold)) {
- minDist = dist;
- minClass = _labels.at<int>((int) sampleIdx);
- }
- }
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。