翠竹林 Opencv+C++之人脸识别

2023-11-19

最近一直在忙课程,老师让我看看他的论文也没放在心上。总算闲下来,看了他在人脸识别方面的相关论文,拿出一篇放在博客上跟大家共同分析下。在看以下内容前,首先要阅读下徐勇老师的这篇论文

A Two-Phase Test Sample Sparse Representation Method for Use With Face Recognition;当前人脸识别方面最热的方法就是稀疏表示方法(sparse represent),其主要思想是利用线性的或者非线性的表示方法将检查样本用训练样本表示出来,训练样本前的系数为代表比重,选取比重较大的训练样本所属的类来标记测试样本。这种方法在某些模式识别中效果较好,但是其原理并不明确,没有很好的理论基础,所以就方法的科学性而言相对欠缺。徐老师提出两步法,第一步利用所有训练样本来标示出测试样本,并提取M近邻训练样本;第二步利用第一步中提取的M近邻样本表出测试样本,选取代表比重大的训练样本所属于的类来标记测试样本。

关于该方法的理论,希望大家去下载论文阅读,这里就不在多说,重点在于算法的实现上:算法中将实现分为两步,第一步是用所有训练样本表示出测试样本,可以用SVD来计算出系数阵,但在这之前要通过PCA或者LDA的方法给特征向量降维;

opencv中PCA有现成的方法,具体代码如下(我的风格是先给出代码,在代码中介绍实现逻辑)

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

#include <fstream>
#include <sstream>

using namespace cv;
using namespace std;


//将给出的图像回归为值域在0~255之间的正常图像
Mat norm_0_255(const Mat& src) {
    // 构建返回图像矩阵
    Mat dst;
    switch(src.channels()) {
    case 1://根据图像通道情况选择不同的回归函数
        cv::normalize(src, dst, 0, 255, NORM_MINMAX, CV_8UC1);
        break;
    case 3:
        cv::normalize(src, dst, 0, 255, NORM_MINMAX, CV_8UC3);
        break;
    default:
        src.copyTo(dst);
        break;
    }
    return dst;
}

// 将一副图像的数据转换为Row Matrix中的一行;这样做是为了跟opencv给出的PCA类的接口对应
//参数中最重要的就是第一个参数,表示的是训练图像样本集合
Mat asRowMatrix(const vector<Mat>& src, int rtype, double alpha = 1, double beta = 0) {
    // 样本个数
    size_t n = src.size();
    // 如果样本为空,返回空矩阵
    if(n == 0)
        return Mat();
    // 样本的维度
    size_t d = src[0].total();
    // 构建返回矩阵
    Mat data(n, d, rtype);
    // 将图像数据复制到结果矩阵中
    for(int i = 0; i < n; i++) {
        //如果数据为空,抛出异常
        if(src[i].empty()) {
            string error_message = format("Image number %d was empty, please check your input data.", i);
            CV_Error(CV_StsBadArg, error_message);
        }
        // 图像数据的维度要是d,保证可以复制到返回矩阵中
        if(src[i].total() != d) {
            string error_message = format("Wrong number of elements in matrix #%d! Expected %d was %d.", i, d, src[i].total());
            CV_Error(CV_StsBadArg, error_message);
        }
        // 获得返回矩阵中的当前行矩阵:
        Mat xi = data.row(i);
        // 将一副图像映射到返回矩阵的一行中:
        if(src[i].isContinuous()) {
            src[i].reshape(1, 1).convertTo(xi, rtype, alpha, beta);
        } else {
            src[i].clone().reshape(1, 1).convertTo(xi, rtype, alpha, beta);
        }
    }
    return data;
}

int main(int argc, const char *argv[]) {
    // 训练图像集合
    vector<Mat> db;

    // 本例中使用的是ORL人脸库,可以自行在网上下载
    //将数据读入到集合中

    db.push_back(imread("s1/1.pgm", IMREAD_GRAYSCALE));
    db.push_back(imread("s1/2.pgm", IMREAD_GRAYSCALE));
    db.push_back(imread("s1/3.pgm", IMREAD_GRAYSCALE));

    db.push_back(imread("s2/1.pgm", IMREAD_GRAYSCALE));
    db.push_back(imread("s2/2.pgm", IMREAD_GRAYSCALE));
    db.push_back(imread("s2/3.pgm", IMREAD_GRAYSCALE));

    db.push_back(imread("s3/1.pgm", IMREAD_GRAYSCALE));
    db.push_back(imread("s3/2.pgm", IMREAD_GRAYSCALE));
    db.push_back(imread("s3/3.pgm", IMREAD_GRAYSCALE));

    db.push_back(imread("s4/1.pgm", IMREAD_GRAYSCALE));
    db.push_back(imread("s4/2.pgm", IMREAD_GRAYSCALE));
    db.push_back(imread("s4/3.pgm", IMREAD_GRAYSCALE));

    // 将训练数据读入到数据集合中,实现PCA类的接口
    Mat data = asRowMatrix(db, CV_32FC1);

    // PCA中设定的主成分的维度,这里我们设置为10维度
    int num_components = 10;

    // 构建一份PCA类
    PCA pca(data, Mat(), CV_PCA_DATA_AS_ROW, num_components);

    // 复制PCA方法获得的结果
    Mat mean = pca.mean.clone();
    Mat eigenvalues = pca.eigenvalues.clone();
    Mat eigenvectors = pca.eigenvectors.clone();

    // 平均脸:
    imshow("avg", norm_0_255(mean.reshape(1, db[0].rows)));

    // 前三个训练人物的特征脸
    imshow("pc1", norm_0_255(pca.eigenvectors.row(0)).reshape(1, db[0].rows));
    imshow("pc2", norm_0_255(pca.eigenvectors.row(1)).reshape(1, db[0].rows));
    imshow("pc3", norm_0_255(pca.eigenvectors.row(2)).reshape(1, db[0].rows));

    // Show the images:
    waitKey(0);

    // Success!
    return 0;
}

 以上代码中主要用到的opencv函数介绍:

Mat Mat::reshape(int cn, int rows=0) const

  opencv手册上的解释为:Changes the shape and/or the number of channels of a 2D matrix without copying the data.

参数cn:新的通道数;如果cn值为0表示变换前后通道数不变

参数rows:新的行数;如果rows值为0表示变换后矩阵的行数不变

该函数会为当前矩阵创建一个新的矩阵头(指针),新的矩阵拥有不同的尺寸或者不同的通道数,其优点在于运算复杂度为O(1),不用复制矩阵数据.正是因为不用复制数据,所以在转变过程中要保证原数据矩阵在数据上的连续性(这里的连续性是相对于原矩阵来说)为了更好的说明,举个例子:

std::vector<Point3f> vec;//一个3D数据点的集合
...
Mat pointMat = Mat(vec). // 将这个三维向量集合转换为矩阵,复制度为O(1);实际上形成的矩阵为一个N*1的3通道图像阵
                             reshape(1). // 用reshape方法将其映射为N*3的1通道图像阵,同样运算复杂度为O(1)


boolMat::isContinuous() const


       opencv手册上的解释:Reports whether the matrix is continuous or not.

如果矩阵元素相对于原始矩阵在元素存储上是连续的,行与行之间没有间隙,那么就返回true否则就返回false;很显然如果是1*1或者1*N矩阵,那么其返回值永远是true。这个矩阵的连续性比较晦涩,我们看下该方法的可替代方法的实现

// 替代 Mat::isContinuous()的方法
bool myCheckMatContinuity(const Mat& m)
{
return m.rows == 1 || m.step == m.cols * m.elemSize();//如果矩阵只有一行就不会出现行与行之间的间断;如果为多行,矩阵的步阶应该是列数*元素尺寸
}

void Mat::convertTo(OutputArray m, int rtype, double alpha=1, double beta=0 ) const

  该函数其实是对原Mat的每一个值做一个线性变换。参数1为目的矩阵,参数2为目d矩阵的类型,参数34变换的系数,看完下面的公式就明白了:

   

PCA::PCA(InputArray data, InputArray mean, int flags, int maxComponents=0)

  该构造函数的第一个参数为要进行PCA变换的输入Mat;参数2为该Mat的均值向量;参数3为输入矩阵数据的存储方式,如果其值为CV_PCA_DATA_AS_ROW则说明输入Mat的每一行代表一个样本,同理当其值为CV_PCA_DATA_AS_COL时,代表输入矩阵的每一列为一个样本;最后一个参数为该PCA计算时保留的最大主成分的个数。如果是缺省值,则表示所有的成分都保留。

Mat PCA::project(InputArray vec) const

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

Mat PCA::backProject(InputArray vec) const

  一般调用backProject()函数前需调用project()函数,因为backProject()函数的参数vec为经过PCA投影降维过后的矩阵。 因此backProject()函数的作用就是用vec来重构原始数据集(关于该函数的本质数学实现暂时还不是很了解)。

  另外PCA类中还有几个成员变量,mean,eigenvectors, eigenvalues等分别对应着原始数据的均值,协方差矩阵的特征值和特征向量。

获得的结果如下:

 

avrageface

 

            

EignFace

OK,我们已经可以获得ORL数据库中每个人物的PCA特征脸,下一步也是我们下一节要研究的就是用训练样本表示出测试样本,从而找到M近邻样本;



FROM: http://www.cnblogs.com/zcftech/archive/2013/04/13/3017411.html

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

翠竹林 Opencv+C++之人脸识别 的相关文章

  • 蓝桥杯 成绩统计

    目录 问题描述 思路分析及代码实现 问题描述 小蓝给学生们组织了一场考试 卷面总分为 100 分 每个学生的得分都是一个 0 到 100 的整数 如果得分至少是 60 分 则称为及格 如果得分至少为 85 分 则称为优秀 请计算及格率和优秀
  • 51单片机的串口通迅

    通信的基本方式分为并行通信和串行通信 并行通信 数据的各位同时在多根数据线上发送或接收 特点 控制简单 传输速度快 由于输线较多 长距离传送时成本高且接收方的各位同时接收存在困难 串行通信 使用一条数据线 将数据一位一位的依次传输 每一位数
  • 1600*B. pSort(并查集)

    解析 并查集 将能够交换的位置相连 查看对应的位置能够交换 include
  • 面试题: Vue中的 computed 和 watch的区别

    computed computed看上去是方法 但是实际上是计算属性 它会根据你所依赖的数据动态显示新的计算结果 计算结果会被缓存 computed的值在getter执行后是会缓存的 只有在它依赖的属性值改变之后 下一次获取computed
  • 巧用 ChatGPT,让开发者的学习和工作更轻松

    引言 随着人工智能技术的快速发展和广泛应用 ChatGPT 作为一种新兴的自然语言处理模型 近期备受瞩目 引发了广泛讨论 ChatGPT 具有多种应用场景 既可以用作聊天机器人 实现智能问答和自然语言交互 也可以作为文本生成工具 帮助人们撰
  • html5中如何去掉input type date默认样式

    html5中如何去掉input type date默认样式 1 时间选择的种类 HTML代码 选择日期
  • 【初探DETR】安装环境

    windows下 需要环境 cython git https github com cocodataset cocoapi git subdirectory PythonAPI egg pycocotools torch gt 1 5 0
  • C# 实例详解委托之Func、Action、delegate(精品)

    概述 委托是 NET编程的精髓之一 在日常编程中经常用到 在C 中实现委托主要有Func Action delegate三种方式 本节主要就这三种委托的用法通过实例展开讲解 Func用法解析 Func Func是带返回值的委托 原型函数如下
  • 软件测试技术(性能与自动化测试)

    一 实验原理 1 功能与自动化测试 功能自动化测试是指使用自动化工具和脚本来模拟用户操作 验证软件应用程序的各种功能是否按照预期工作的过程 在进行功能自动化测试时 测试人员通常编写脚本 这些脚本可以模拟用户在应用程序中进行的各种操作 例如点
  • 【机器学习】12、主题模型

    文章目录 一 pLSA 二 LDA模型 一 pLSA 每个文档都会在主题上有一个分布 每个主题在各个词上也有一个分布 p w j
  • React中使用if else 条件判断

    在react中用jsx渲染dom的时候经常会遇到if条件判断 然而在jsx中竟是不允许if条件判断的 以下有几种判断方式 可以根据自己的应用场景 挑选适合的 方案一 class HelloMessage extends React Comp
  • QTableWidget获取选中的内容及所在行数

    QTableWidget选中所有单元格及取消选中所有单元格 ui gt allowSelectKeyTableWidget gt selectAll ui gt allowSelectKeyTableWidget gt setFocus i
  • 使用 Blender* 重新拓扑 VR 和游戏素材

    本文介绍如何将网格重新拓扑成一个整洁的低密度模型 然后 UV 解包该网格 以便将纹理贴添加至新模型 本文还将探讨如何使用免费工具 比如 Blender 及其 Bsurface 插件 重新拓扑雕塑的 3D 网格 查看详情
  • bootstrap3 表单构建器_如何快速构建基于MES的开源云平台

    导读本文为2019工业互联网平台活动盘点文章 同时也欢迎广大工业互联网平台企业参与本次盘点 具体参与方式可加编辑微信号 13517202453 详细咨询 随着智能制造转型战略的持续推进 MES作为承载智能化生产制造过程的核心系统正在受到越来
  • 正则表达式语法大全

    前言 一般开发中都会接触到正则表达式 作为一名合格的前端 也应该了解一下正则表达式编写原则 便于自己也能快速编写想要的正则表达式 1 作用 正则表达式是一种字符串匹配的模式 数据验证 比如电话号码 邮箱等 替换文本 快速找到特定文本 用于替

随机推荐