Opencv人脸识别项目简介

Opencv人脸识别项目简介Opencv人脸识别Project综述项目要求使用opencv实现对人脸库的主成分提取(不使用PCA类),完成特征模型保存对一张测试照片进行识别,找到图片库中和测试图片最像的图配置说明Opencv3.0VS2015Win10配置过程网上太多了,就不做过多解释了,可以参照某个教程来做。主要的也就几步,下载Opencv,配Path,配置VC++目录的包含目录和库目录,配置链接器附加项的附

大家好,又见面了,我是你们的朋友全栈君。

项目要求

  • 使用opencv实现对人脸库的主成分提取(不使用PCA类),完成特征模型保存
  • 对一张测试照片进行识别,找到图片库中和测试图片最像的图

配置说明

  • Opencv3.0
  • VS2015
  • Win10

配置过程网上太多了,就不做过多解释了,可以参照某个教程来做。主要的也就几步,下载Opencv,配Path,配置VC++目录的包含目录和库目录,配置链接器附加项的附加依赖项。

人脸库

结果

  • 训练集是AT&T人脸库40×8(两张做测试用)
  • 左图是输入的测试人脸,右图两张是人脸库中的匹配到后还原人脸
测试人脸 匹配结果 匹配结果(前50%特征向量)
测试人脸 匹配结果 弱

前言

  • 为什么要做PCA (principle component analysis)

    主成分分析做的就是给了一堆很高维的数据,我们需要把它变成低维的数据,变成低维数据有一个好,最直观的就是数据量下来了。那图片举例,比如1000个样本,100×100的分辨率,以前1000×10000的数据,每个点就算是1Btye的数据,要10M。我把它映射到500维(取前50%特征向量)的空间,变成1000×500的数据,一下子降低到了500K,一下子降低了一个量级,更关键的是计算量瞬间也降了一个档次。一些场景如嵌入式设备上(如ZKteco的考勤机),计算和空间都是很奢侈的东西,我们的PCA就发挥作用了。

  • 怎么做数据的降维
    为什么要做降维原因还有很多很多,有兴趣的可以去查查,不在这里讲的主要原因是我自己也不知道。那么接下来就要分析怎么降维,降维大概的意思就是把一个数据点降到低维的数据点,比如XY二维的点映射到一维的话,如果是映射到x轴,那么所有点点乘个(1, 0)就好了;如果是三维的点降到一维那么点乘(1, 0, 0)就好了,降到二维点乘(1, 0, 0);(0, 1, 0)就好了,也就是相当于原来的数据矩阵乘了一个变换矩阵。

    问题来了,这个变换矩阵要怎么设定呢?是不是直接所有的都映射到X,Y,Z…轴上就好了呢?当然不是!比如下面有张数据点图表示男女的身高体重图

    黑点表示男生,红点表示女生
    X轴表示体重,Y轴表示身高

img

如果判断都是按照体重来划分的话,m1很可能就被判断成了女生,fm1则被判断成了男生。“虽然我体重轻一点,但是我身高比较高啊,我应该被划分成男生”。讲道理的话按照那条蓝线来分比较科学,为什么科学啊,因为比较符合图的分布,为什么符合分布啊?….这个时候有人就弄了一个PCA做降维的准则,有人说:“我觉得使得新的数据集方差最大的那种分法比较好。”大家想想,是这么个道理,方差比较小,大家挤成一团,很多数据点很容易重叠,也不容易区分。

数学推导

更加细致的推导可以去参考其他大学的pca课程ppt,如JHU这里有pca处理人脸步骤的详解,CMU的ppt则对整个过程的数学推导有详细的证明,CMU的ML课程主页还有更多的资源,是一个很好的学习的地方
看他们的ppt对PCA的理解帮助更大,如果想简单了解下直接看下我的证明也可以

假设数据集是

Opencv人脸识别项目简介

新的数据集是

Opencv人脸识别项目简介

原来的数据是p维,新的数据是k维,对于新数据的第一维数据

Opencv人脸识别项目简介

我们需要选择 Opencv人脸识别项目简介使得Opencv人脸识别项目简介最大

Opencv人脸识别项目简介

Opencv人脸识别项目简介

Opencv人脸识别项目简介

因此,目标变成了

Opencv人脸识别项目简介

用拉格朗日乘子法求值

Opencv人脸识别项目简介

Opencv人脸识别项目简介 (看不懂的话可以查The Matrix Cookbook)

Opencv人脸识别项目简介

Opencv人脸识别项目简介

因此最大的方差就是最大的特征值,对应的

Opencv人脸识别项目简介

是最大特征值对应的特征向量。
再来求

Opencv人脸识别项目简介

这个时候

Opencv人脸识别项目简介

需要满足要求使得

Opencv人脸识别项目简介

同理可得它就是第二大的特征根,我们要找投影方差最大的K个向量,也就是协方差矩阵的前K大特征值对应的特征向量。
所以为了保存最多的信息,数据变换到K维空间我们的变换矩阵就是

Opencv人脸识别项目简介

Opencv人脸识别项目简介

通过

Opencv人脸识别项目简介

进行数据的还原


讲了那么多我们队PCA的大概求解过程也了解了,那么,具体用在人脸识别的分析上面是否又是如此呢?有点小小的不一样。
我们这时候不是图像的灰度值来算协方差矩阵,而是

Opencv人脸识别项目简介

为什么呢,因为用offset值算的协方差矩阵,重构图像后的error是最小的。具体的推导我实在是懒得打了,把CMU课程上的两张推导图放上来,大家将就着看好了。
这里写图片描述
这里写图片描述
这里写图片描述
这里写图片描述


计算小Tips

我们在计算协方差矩阵的时候,会发现特征值没法算,为什么呢,一个图像一万维,10000×10000的矩阵特征值不是算爆炸了吗?你会发现自己opencv的函数半天输出不出结果,这个时候如果人脸训练样本比较少的话,假设

Opencv人脸识别项目简介

可以算

Opencv人脸识别项目简介

的特征向量,然后乘以 A,就可以得到要求得协方差矩阵的特征向量,证明如下(摘自JHU的ppt
这里写图片描述
这再次说明了一个问题,多看看别人学校的课程公开的资源,用google随便一搜很多,或者是coursera,比很多博客上的靠谱全面太多了,囧


源码

有时间我再挑代码重点解释下吧,大家上面理解了,下面的大家应该基本都能够看懂,原谅我翔一样的代码,看个高兴就好了…人脸库是上面提到的ATT/T,上面有链接,可以自己去下

#include "opencv2/opencv.hpp"
#include <opencv2/core/core.hpp>
#include <opencv2/highgui/highgui.hpp>

cv::String root_folder = "att_faces";
int population = 10;
int gesture = 8;
double EigenPerc = 0.5;

/* --load model data-- */
int img_rows;
cv::Mat LoadNewSpaceData, loadEigenVector, loadDataMean; // datamean is a mean face (a vector)
/* ---------------- */

void showFace(cv::String windowName, cv ::Mat faceVector, int rows) {
    faceVector = faceVector.reshape(1, rows);
    faceVector.convertTo(faceVector, CV_8U);

    cv::imshow(windowName, faceVector); 
}

void saveModel() {  
    cv::Mat dataMat; // original image data 
    int img_rows; // will be wriiten to model file

    //read image data
    for (int man = 1; man <= population; man++)
        for (int pos = 1; pos <= gesture; pos++) {
            char filename[50];
            sprintf(filename, "att_faces/s%d/%d.pgm", man, pos);
            cv::Mat img = cv::imread(filename);
            cv::cvtColor(img, img, CV_RGB2GRAY);
            img_rows = img.rows;
            img = img.reshape(1, 1);            
            dataMat.push_back(img);         
        }
    dataMat.convertTo(dataMat, CV_32F);

    //calc covariance and dataMean (mean matrix will be written to model file)
    cv::Mat  cov, dataMean, dataMatOff, temp, temp12;
    cv::reduce(dataMat, dataMean, 0, CV_REDUCE_SUM);
    dataMean = dataMean / dataMat.rows;
    cv::repeat(dataMean, dataMat.rows, 1, temp12); //tips: source and destination array can't be the same
    cv::subtract(dataMat, temp12, dataMatOff); 
    cov = dataMatOff * dataMatOff.t();

    // Calculate eigenVectors of the corvariance matrix of original image matrix
    cv::Mat eigenVal, eigenVec;
    cv::Mat eigenVecFloat;

    cv::eigen(cov, eigenVal, eigenVec);
    eigenVec = eigenVec.t();        
    eigenVec.convertTo(eigenVecFloat, CV_32F);
    cv::Mat realEigenVec = dataMatOff.t() * eigenVecFloat;

    /* normalize eigenvector */
    cv::Mat temp1, temp2, temp3;
    cv::pow(realEigenVec, 2, temp1);
    cv::reduce(temp1, temp2, 0, CV_REDUCE_SUM);
    cv::sqrt(temp2, temp2);

    cv::repeat(temp2, temp1.rows, 1, temp3);
    cv::divide(realEigenVec, temp3, realEigenVec);      


    cv::Mat newSpaceData, newFace;

    int cutcols = (int)(EigenPerc * realEigenVec.cols);
    if (cutcols == 0) cutcols = 1;
    newSpaceData = dataMatOff * realEigenVec.colRange(0, cutcols);
    std::cout << "cols: "<< cutcols << std::endl;
    /*
    newFace = newSpaceData * (realEigenVec.colRange(0, cutcols).t());   
    newFace = newFace + dataMean;*/
    cv::FileStorage fsw("model.yml", cv::FileStorage::WRITE);
    fsw << "imageRows" << img_rows;
    fsw << "newSpaceData" << newSpaceData << "EigenVector" << realEigenVec.colRange(0, cutcols)
        << "dataMean" << dataMean;
    fsw.release();

    return; 
}


void loadModel() {
    cv::FileStorage fsr("model.yml", cv::FileStorage::READ);

    img_rows = (int)fsr["imageRows"];
    fsr["newSpaceData"] >> LoadNewSpaceData;
    fsr["EigenVector"] >> loadEigenVector;
    fsr["dataMean"] >> loadDataMean;
    fsr.release();
    cv::Mat newFace = LoadNewSpaceData * loadEigenVector.t();

    cv::Mat expandDataMean = cv::repeat(loadDataMean, newFace.rows, 1);
    newFace = newFace + expandDataMean;

    //show face
    showFace("model_face", newFace.rowRange(0, 1), img_rows);   
}

void checkFace(cv::Mat testFace) {
    cv::Mat testNewSpaceFace;
    testFace.convertTo(testFace, CV_32F);
    testNewSpaceFace = (testFace - loadDataMean) * loadEigenVector;

    // find a nearest face from newSpaceDataLoad
    float minDis = 3.402823466e+38F;
    cv::Mat matchFace;
    for (int i = 0; i < LoadNewSpaceData.rows; i++) {
        //std::cout << testNewSpaceFace;
        cv::Mat off = (testNewSpaceFace - LoadNewSpaceData.rowRange(i, i + 1));
        cv::Mat val = off * off.t();
        val.convertTo(val, CV_32F);
        float dis;
        dis = val.at<float>(0, 0);
        if (dis < minDis) {
            minDis = dis;
            matchFace = LoadNewSpaceData.rowRange(i, i + 1) * loadEigenVector.t();
        }       
    }
    cv::Mat face8U;
    matchFace = matchFace + loadDataMean;
    matchFace.convertTo(face8U, CV_8U);
    std::cout << minDis;

    showFace("matched_face", face8U, img_rows);
    cv::waitKey(0);
}

int main() {

    saveModel();
    loadModel();
    cv::namedWindow("original_face");
    cv::namedWindow("matched_face");
    cv::namedWindow("model_face");

    cv::Mat testFace = cv::imread("test.pgm");
    imshow("original_face", testFace);
    cv::cvtColor(testFace, testFace, CV_RGB2GRAY);
    testFace = testFace.reshape(1, 1);

    checkFace(testFace);

    return 0;

}

CV的教材及参考资料


  • 矩阵速查手册 the matrix cookbook,学习DM的时候Prof强力推荐的书,矩阵求导不知道怎么求的话可以看这本书
  • numerical recipes in c++
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

发布者:全栈程序员-用户IM,转载请注明出处:https://javaforall.cn/135290.html原文链接:https://javaforall.cn

【正版授权,激活自己账号】: Jetbrains全家桶Ide使用,1年售后保障,每天仅需1毛

【官方授权 正版激活】: 官方授权 正版激活 支持Jetbrains家族下所有IDE 使用个人JB账号...

(0)
blank

相关推荐

发表回复

您的电子邮箱地址不会被公开。

关注全栈程序员社区公众号