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)
全栈程序员-站长的头像全栈程序员-站长


相关推荐

  • MySQL八股文连环45问,你能坚持第几问?「建议收藏」

    MySQL八股文连环45问,你能坚持第几问?「建议收藏」文人从事多年面试工作,将MySQL面试分享给大家,希望大家顺利拿下offer

    2022年5月18日
    40
  • spring storedProcedure 使用

    spring storedProcedure 使用http://blog.csdn.net/xiao_jun_0820/article/details/7268219 StoredProcedure是一个抽象类,必须写一个子类来继承它,这个类是用来简化JDBCTemplate执行存储过程操作的。首先我们写一个实现类:[java]viewplaincopyprint?package com.huaye.f

    2022年7月26日
    7
  • apktool反编译详细使用教程「建议收藏」

    apktool反编译详细使用教程「建议收藏」apktool反编译详细使用教程,包括每个细节。还有为什么反编译不成功,反编译出现的各种情况将为大家详细写出来,如有写的不好的地方还请见谅,这些都是本人自学的,曾经请教过大神,让我悲剧的是尽然无一人为我解答,后只有自己琢磨,所以本人看不惯那些大神的高傲姿态,不就会个反编译,会做美化包,整个内核,相信我写完教程后大家都将会自己制作美化包。学完反编译后你们就可以自己制作美化包了。当然有一些大神除外..

    2022年9月18日
    3
  • request中的方法_requests发送get请求

    request中的方法_requests发送get请求request.getRealPath不推荐使用request.getRealPath(“”)这个方法已经不推荐使用了,那代替它的是什么方法Deprecated.AsofVersion2.1oftheJavaServletAPI,useServletContext.getRealPath(java.lang.String)instead.request.getSess

    2025年11月22日
    3
  • gb28181协议详解_GB28181收费吗

    gb28181协议详解_GB28181收费吗ssdp协议近似于http协议,事实上,和http协议相似得地方就是他得协议内容,当然,我们要去除他得端口和d类地址。为什么我在给其他员工或者面试得时候要他人深入一些,理解一下http协议,是因为理解了http协议,掌握ssdp也就不远了,很多人可能会问http协议有啥内容,无非就是get,post,put,delete么,还能怎么样,我经常问他们一点http协议怎么知道他结束了?虽然ssdp是udp协议,但是他依然需要\r\n来代表行结束,\r\n\r\n代表协议内容部分结束。……

    2022年10月11日
    1
  • C++异常处理建议收藏

    一C++异常处理机制异常处理基本思想:执行一个函数的过程中发现异常,可以不用再本函数内立即进行处理,而是抛出该异常,让函数的调用者直接或间接的处理这个问题。C++异常处理进制由三个模块组成:tr

    2021年12月19日
    44

发表回复

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

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