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++
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请联系我们举报,一经查实,本站将立刻删除。

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

(0)
全栈程序员-站长的头像全栈程序员-站长


相关推荐

  • Yarn中ResourceManager的RPC协议[通俗易懂]

    Yarn中ResourceManager的RPC协议

    2022年2月6日
    52
  • css3 画半圆和1/4圆

    css3 画半圆和1/4圆

    2021年9月13日
    65
  • c++中constexpr_define和const定义常量的区别

    c++中constexpr_define和const定义常量的区别常量表达式是指值不会改变且在编译过程中就能够得到计算结果的表达式,能在编译时求值的表达式。例1:#include&lt;iostream&gt;usingnamespacestd;intmain(){ constinta1=10;//a1是常量表达式。 constinta2=a1+20;//a2是常量表达…

    2022年9月26日
    2
  • 文件下载,带转码-&gt;pdf-&gt;swf

    文件下载,带转码-&gt;pdf-&gt;swf

    2022年1月31日
    39
  • dpkg说明_dpkg命令

    dpkg说明_dpkg命令dpkg与centos中的rpm相似,被用于安装,卸载及查询deb包信息。下面简单介绍基础命令。已有安装包:test.deb。安装命令:dpkg-itest.deb安装test.deb软件包dpkg-ctest.deb#查看test.deb软件包中包含的文件结构安装后查询命令:dpkg-Itest查看已安装的test.deb软件包的详细信息,包括软件名称、版本等dpkg-Ltest#查看已安装test.deb软件包安装的所有文件dpkg-stest#查看test.

    2022年10月7日
    2
  • Idea配置SVN教程

    Idea配置SVN教程第一步:下载svn的客户端,通俗一点来说就是小乌龟啦!去电脑管理的软件管理里面可以直接下载,方便迅速下载之后直接安装就好了,但是要注意这里的这个文件也要安装上,默认是不安装的,如果不安装,svn中的bin目录下就会没有svn.exe,这个待会会用到,所以一点要注意哦。(都是坑啊)如果你不幸没点第二个,别着急,本文章末尾给你解决办法然后就下一步下一步就安装好了。配置svn的环境变量在此就…

    2022年5月14日
    242

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

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