美文网首页OpenCvOpenCV
2018-01-15 EigenFace OpenCV 实现

2018-01-15 EigenFace OpenCV 实现

作者: y4nghan | 来源:发表于2018-01-15 10:35 被阅读29次

    EigenFace OpenCV实现

    欢迎在 Yang Han's Notebook EigenFace OpenCV 实现 获得更佳阅读体验。

    实验目标

    自己写代码实现EigenFace 人脸识别的训练与识别过程:

    1. 假设每张⼈脸图像只有一张人脸,且两只眼睛位置已知(即可人工标注给出)。每张图像的眼睛位置存在相应目录下的㇐个与图像文件名相同但后缀名为txt的⽂本文件⾥,文本文件中用一行、以空格分隔的4个数字表示,分别对应于两只眼睛中⼼在图像中的位置;
    2. 实现两个程序过程(两个执行文件),分别对应训练与识别3. 自己构建一个人脸库(至少40个,包括自己),课程主页提供一个人脸库可选用。
    3. 不能直接调用OpenCV里面与Eigenface相关的一些函数,特征值与特征向量求解函数可以调用; 只能用C/C++,不能⽤其他编程语言; GUI只能用OpenCV自带的HighGUI,不能用QT或其他的;平台可以用Win/Linux/MacOS,建议Win优先;
    4. 训练程序格式大致为: “mytrain.exe 能量百分比 model文件名 其他参数…”,用能量百分比决定取多少个特征脸,将训练结果输出保存到model文件中 。同时将前10个特征脸拼成一张图像,然后显示出来。
    5. 识别程序格式大致为: “mytest.exe ⼈脸图像⽂件名model文件名 其他参数…”,将model文件装载进来后,
      对输入的人脸图像进行识别,并将识别结果叠加在输入的人脸图像上显示出来,同时显示人脸库中跟该人脸图像最相似的图像。

    实验环境

    Windows 10 1709

    OpenCV 3.3

    实验过程

    总览

    实现了FaceEntry类来进行对每张人脸图片的一些操作。

    实现了FaceLib类来进行人脸库的读入和对人脸的一些统一操作。

    然后train中实现了协方差矩阵和特征矩阵的计算,生成了特征脸。

    test中实现了用特征矩阵对图片进行降维,然后计算欧氏距离进行人脸类型的判断。

    实验中使用了att_faces人脸库,共40个人,每人10张人脸。我取每个人的前五张作为训练使用的图像,后面5张用于在测试中判断程序人脸识别是否准确。

    所以总共用于训练的人脸图像有200张。

    FaceEntry

    实现一个FaceEntry类来进行单张人脸的各种处理。

    class FaceEntry {
    public:
        Mat origin_pic;
        Mat gray_pic;
        Mat transformed_pic;
        int x1, y1, x2, y2;
        Mat trans_mat;
        Mat_<double> equalized_mat;
        Mat_<double> vect;
        void load(string& path, string ext) {
            load_eye_pos(path);
            origin_pic = imread(path + ext);
            gray_pic = imread(path + ext, IMREAD_GRAYSCALE);
            transform();
        }
        void load(string& path) {
            load_eye_pos(path.substr(0, path.length()-4));
            origin_pic = imread(path);
            gray_pic = imread(path, IMREAD_GRAYSCALE);
            transform();
        }
    
        void load_eye_pos(string& path) {
            ifstream file(path + ".txt", ifstream::in);
            file >> x1 >> y1 >> x2 >> y2;
        }
    
        void transform() {
            Point center((x1 + x2) / 2, (y1 + y2) / 2);
            double angle = atan((double)(y2 - y1) / (double)(x2 - x1)) * 180.0 / CV_PI;
            trans_mat = getRotationMatrix2D(center, angle, 1.0);
            trans_mat.at<double>(0, 2) += 37 - center.x;
            trans_mat.at<double>(1, 2) += 30 - center.y;
            warpAffine(gray_pic, transformed_pic, trans_mat, gray_pic.size()*4/5);
            equalizeHist(transformed_pic, transformed_pic);
            transformed_pic.copyTo(equalized_mat);
            vect = equalized_mat.reshape(1, 1).t();
        }
    };
    
    

    对于每张图片,读入一份3通道的原图和一份1通道的灰度图。

    然后读入对应文件名的txt文件(眼睛位置标注),存入结构体内。

    然后就是进行transform()

    先计算出两只眼睛的中心,然后计算两只眼睛倾斜的角度,然后通过仿射变换(旋转+缩放+平移),使两只眼睛水平并使两只眼睛对齐到确定模板的位置。这里为了使图片人脸仍然能几乎占满,我将模板的size确定维原图size的0.8倍。

    仿射变换完成之后,对灰度图进行直方图均衡化equalizeHist,使光照条件对人脸的影响变小。

    然后将$M \times N$的图片矩阵转换为$MN \times 1$的矩阵(向量)保存起来。

    至此,单张人脸处理完成。

    FaceLib

    class FaceLib {
    public:
        int num_of_faces = 200;
        int num_of_persons = 40;
        int faces_per_person = 5;
        vector<FaceEntry*> faces;
        vector<Mat_<double>> _samples;
        Mat_<double> samples;
    
        void load(string& path) {
            for (int i = 1; i <= num_of_persons; i++)
            {
                for (int j = 1; j <= faces_per_person; ++j) {
                    string entry_path = path + "/s" + to_string(i) + "/" + to_string(j);
                    FaceEntry* face = new FaceEntry();
                    face->load(entry_path, ".pgm");
                    faces.push_back(face);
                    _samples.push_back(face->vect);
                }
            }
            hconcat(_samples, samples);
        }
    };
    

    这个没什么可说的,就是一张一张load进来,然后最后把所有$MN \times 1$的向量合并到一个$MN \times K$的矩阵中去。(K为实验中训练集的大小)。

    train

    先解析一下命令行参数

    char* model_name = argv[2];
    double energy = atof(argv[1]);
    

    然后读入FaceLib

    FaceLib facelib;
    facelib.load(string("att_faces"));
    

    然后计算协方差矩阵

    calcCovarMatrix(samples, cov_mat, mean_mat, CV_COVAR_ROWS | CV_COVAR_NORMAL);
    cov_mat = cov_mat / (samples.rows - 1);
    

    这里计算协方差矩阵有一点小技巧,就是对于$MN \times K$的矩阵,因为最后的最终目的是求出特征矩阵,所以可以求出$K \times K$的协方差矩阵,可以大大加速之后的求特征矩阵过程。然后对于$1 \times K$的特征向量,可以通过与samples矩阵相乘得到$1 \times MN$的原始特征向量。(可用数学方式证明)。

    然后就是计算特征矩阵

    eigen(cov_mat, e_value_mat, e_vector_mat);
    

    然后用samples矩阵减去平均值,恢复到$MN$维的向量。

    for (int i = 0; i < samples.rows; ++i) {
      samples.row(i) -= mean_mat;
    }
    e_vector_mat = (samples * e_vector_mat.t()).t();
    

    然后通过能量百分比来计算出应该取多少特征脸

    double value_sum = sum(e_value_mat)[0];
    cout << e_vector_mat.size() << endl;
    double energy_level = value_sum * energy;
    double energy_sum = 0;
    int k = 0;
    for (k = 0; k < e_value_mat.rows; k++)
    {
        energy_sum += e_value_mat.at<double>(k, 0);
        if (energy_sum >= energy_level) break;
    }
    e_vector_mat = e_vector_mat.rowRange(0, k);
    e_value_mat = e_value_mat.rowRange(0, k);
    
    

    将运算的结果导出

    FileStorage model(model_name, FileStorage::WRITE);
    model << "e_vector_mat" << e_vector_mat;
    model << "e_value_mat" << e_value_mat;
    model.release();
    

    然后选出特征值最大的10张特征脸合并到一张图片然后显示出来

    vector<Mat> Top10EigenFace;
    for (int i = 0; i < 10; ++i) {
        Top10EigenFace.push_back(toImg(e_vector_mat.row(i), WIDTH, HEIGHT));
    }
    Mat result;
    hconcat(Top10EigenFace, result);
    imshow("Top10EigenFace", result);
    

    这里实现了一个toImg()函数,用于将$1 \times MN$的向量转换维$M \times N$的图像。

    test

    先解析了命令行参数,读入model, 并读入了FaceLib和需要检测的图片。

    FaceLib facelib;
    facelib.load(string("att_faces"));
    char* model_name = argv[2];
    char* file_name = argv[1];
    
    FileStorage model(model_name, FileStorage::READ);
    Mat e_vector_mat, e_value_mat;
    model["e_vector_mat"] >> e_vector_mat;
    model["e_value_mat"] >> e_value_mat;
    
    face.load(string(file_name));
    

    将所有人脸库中的训练脸都转换成特征脸为基的地位向量:

    distance = e_vector_mat * samples;
    

    然后计算和训练库中各张人脸的欧式距离,找出最小的距离的对应图片,认为与此张人脸最接近。

    Mat face_vect = e_vector_mat * face.vect;
    double min_d = norm(face_vect, distance.col(0), NORM_L2);
    double temp_d = 0;
    int min_i = 0;
    
    for (int i = 1; i < distance.cols; ++i) {
    temp_d = norm(face_vect, distance.col(i), NORM_L2);
        if (temp_d <= min_d) {
            min_d = temp_d;
            min_i = i;
        }
    }
    

    然后打上标记,显示图片

    string text = "s" + to_string(min_i / 5 + 1) + " No." + to_string(min_i % 5 + 1);
    putText(origin_mat, text, Point(10, 20), FONT_HERSHEY_COMPLEX, 0.5, (0, 255, 255), 2, 8);
    imshow("FaceResult", origin_mat);
    imshow("Similar Pic", similar_mat);
    

    实验结果

    实验中使用了att_faces人脸库,共40个人,每人10张人脸。我取每个人的前五张作为训练使用的图像,后面5张用于在测试中判断程序人脸识别是否准确。

    所以总共用于训练的人脸图像有200张。

    前十的特征脸的图像:

    Top10EigenFace

    随机选用mytest.exe att_faces\\s37\\8.pgm eigen.model进行测试:

    结果是识别出此人,并认为第2张人脸与之最相近

    运行结果

    我还输出了总共400张照片的测试结果(每个人10张人脸中的前五张在训练库中)(最后三张是我自己的脸,最终版本略去)

    运行结果

    可以看出识别结果有一定的偏差,但整体上还是比较准确的。

    心得体会

    这次实验稍微有一些难度,需要理解PCA中的降维的思想,通过转换基向量的形式使重要成分被当作向量的基向量,达到降维的目的。

    然后整个实验的过程中,熟悉了opencv的各种操作,对其的熟练程度有了提高。

    然后就还有知道了imwrite支持的Mat类型不多,float的Mat需要先convert到乘以255之后的整数类型才能够被正确地写入。

    附:源代码

    #pragma once
    // facelib.h
    #include <opencv2/opencv.hpp>
    #include <opencv2/opencv.hpp>
    #include <iostream>
    #include <string>
    #include <vector>
    
    // Bad Implementation Because of my Laziness.
    // Plz Don't Use Namespace in *.h as me.
    using namespace std;
    using namespace cv;
    
    const int WIDTH = 73;
    const int HEIGHT = 89;
    
    
    class FaceEntry {
    public:
        Mat origin_pic;
        Mat gray_pic;
        Mat transformed_pic;
        int x1, y1, x2, y2;
        Mat trans_mat;
        Mat_<double> equalized_mat;
        Mat_<double> vect;
        void load(string& path, string ext) {
            load_eye_pos(path);
            origin_pic = imread(path + ext);
            gray_pic = imread(path + ext, IMREAD_GRAYSCALE);
            transform();
        }
        void load(string& path) {
            load_eye_pos(path.substr(0, path.length()-4));
            origin_pic = imread(path);
            gray_pic = imread(path, IMREAD_GRAYSCALE);
            transform();
        }
    
        void load_eye_pos(string& path) {
            ifstream file(path + ".txt", ifstream::in);
            file >> x1 >> y1 >> x2 >> y2;
        }
    
        void transform() {
            Point center((x1 + x2) / 2, (y1 + y2) / 2);
            double angle = atan((double)(y2 - y1) / (double)(x2 - x1)) * 180.0 / CV_PI;
            trans_mat = getRotationMatrix2D(center, angle, 1.0);
            trans_mat.at<double>(0, 2) += 37 - center.x;
            trans_mat.at<double>(1, 2) += 30 - center.y;
            warpAffine(gray_pic, transformed_pic, trans_mat, gray_pic.size()*4/5);
            equalizeHist(transformed_pic, transformed_pic);
            transformed_pic.copyTo(equalized_mat);
            vect = equalized_mat.reshape(1, 1).t();
        }
    
    };
    
    class FaceLib {
    public:
        int num_of_faces = 200;
        int num_of_persons = 40;
        int faces_per_person = 5;
        vector<FaceEntry*> faces;
        vector<Mat_<double>> _samples;
        Mat_<double> samples;
    
        void load(string& path) {
            for (int i = 1; i <= num_of_persons; i++)
            {
                for (int j = 1; j <= faces_per_person; ++j) {
                    string entry_path = path + "/s" + to_string(i) + "/" + to_string(j);
                    FaceEntry* face = new FaceEntry();
                    face->load(entry_path, ".pgm");
                    faces.push_back(face);
                    _samples.push_back(face->vect);
                }
            }
            hconcat(_samples, samples);
        }
    };
    
    // train.cpp
    #include "facelib.h"
    
    Mat toImg(Mat vect, int w, int h) {
        assert(vect.type() == 6);
        //assert(vect.rows == h);
        //cout << vect.cols << endl;
        assert(vect.cols == w*h);
        //cout << "==" << endl;
        //cout << vect << endl;
        Mat result(Size(w, h), CV_64FC1);
        for (int i = 0; i < h; ++i) {
            vect.colRange(i*w, (i + 1)*w).convertTo(result.row(i), CV_64FC1);
        }
        //equalizeHist(result, result);
        normalize(result, result, 1.0, 0.0, NORM_MINMAX);
    
        //imshow("iiii", result);
        return result;
    
    }
    
    int main(int argc, char** argv) {
        char* model_name = "eigen.model";
        double energy = 0.95;
        if (argc >= 3) {
                model_name = argv[2];
                energy = atof(argv[1]);
        }
        FaceLib facelib;
        facelib.load(string("att_faces"));
        Mat samples, cov_mat, mean_mat;
        facelib.samples.copyTo(samples);
        //cout << samples << endl;
        //cout << samples.size() << endl;
        cout << "Calculating Covariance Mat..." << endl;
        calcCovarMatrix(samples, cov_mat, mean_mat, CV_COVAR_ROWS | CV_COVAR_NORMAL);
        cout << mean_mat.size() << endl;
        ////cout << cov_mat << endl;
        //cout << cov_mat.size() << endl;
        cov_mat = cov_mat / (samples.rows - 1);
        Mat e_vector_mat, e_value_mat;
        cout << "Calculating Eigen Vector..." << endl;
        eigen(cov_mat, e_value_mat, e_vector_mat);
        cout << "eigen size " << e_value_mat.size() << endl;
        cout << e_value_mat << endl;
        for (int i = 0; i < samples.rows; ++i) {
            samples.row(i) -= mean_mat;
        }
        
        double value_sum = sum(e_value_mat)[0];
        cout << e_vector_mat.size() << endl;
        double energy_level = value_sum * energy;
        double energy_sum = 0;
        int k = 0;
        for (k = 0; k < e_value_mat.rows; k++)
        {
            energy_sum += e_value_mat.at<double>(k, 0);
            if (energy_sum >= energy_level) break;
        }
        cout << k << endl;
        e_vector_mat = (samples * e_vector_mat.t()).t();
        e_vector_mat = e_vector_mat.rowRange(0, k);
        e_value_mat = e_value_mat.rowRange(0, k);
    
        FileStorage model(model_name, FileStorage::WRITE);
        model << "e_vector_mat" << e_vector_mat;
        model << "e_value_mat" << e_value_mat;
        model.release();
    
        vector<Mat> Top10EigenFace;
        for (int i = 0; i < 10; ++i) {
            Top10EigenFace.push_back(toImg(e_vector_mat.row(i), WIDTH, HEIGHT));
        }
        Mat result;
        hconcat(Top10EigenFace, result);
    
        result.convertTo(result, CV_8U, 255);
    
        imshow("Top10EigenFace", result);
        imwrite("Top10EigenFace.png", result);
    
    
        waitKey(0);
        destroyAllWindows();
        return 0;
    }
    
    // test.cpp
    #include "facelib.h"
    
    int main(int argc, char** argv) {
        FaceLib facelib;
        facelib.load(string("att_faces"));
    
        char* model_name = "eigen.model";
        char* file_name = "att_faces/s27/8.png";
        if (argc >= 3) {
            model_name = argv[2];
            file_name = argv[1];
        }
        FileStorage model(model_name, FileStorage::READ);
        Mat e_vector_mat, e_value_mat;
        model["e_vector_mat"] >> e_vector_mat;
        model["e_value_mat"] >> e_value_mat;
        Mat distance;
        Mat samples;
        FaceEntry face;
        facelib.samples.copyTo(samples);
        distance = e_vector_mat * samples;
        for (int _i = 1; _i <= 40; ++_i) {
            for (int _j = 1; _j <= 10; _j++)
            {
                face.load(string("att_faces/s") + to_string(_i) + "/" + to_string(_j), ".pgm");
                Mat face_vect = e_vector_mat * face.vect;
                double min_d = norm(face_vect, distance.col(0), NORM_L2);
                double temp_d = 0;
                int min_i = 0;
    
                for (int i = 1; i < distance.cols; ++i) {
                    temp_d = norm(face_vect, distance.col(i), NORM_L2);
                    if (temp_d <= min_d) {
                        min_d = temp_d;
                        min_i = i;
                    }
                }
                cout << (min_i/5)+1 << "/" << (min_i % 5)+1 << " ";
            }
            cout << endl;
        }
    
        face.load(string(file_name));
        
        Mat face_vect = e_vector_mat * face.vect;
        double min_d = norm(face_vect, distance.col(0), NORM_L2);
        double temp_d = 0;
        int min_i = 0;
    
        for (int i = 1; i < distance.cols; ++i) {
            temp_d = norm(face_vect, distance.col(i), NORM_L2);
            if (temp_d <= min_d) {
                min_d = temp_d;
                min_i = i;
            }
        }
        cout << (min_i / 5) + 1 << "/" << (min_i % 5) + 1 << " " << endl;
        Mat origin_mat = face.origin_pic;
        Mat similar_mat = facelib.faces.at(min_i)->origin_pic;
        string text = "s" + to_string(min_i / 5 + 1) + " No." + to_string(min_i % 5 + 1);
        cout << text << endl;
        putText(origin_mat, text, Point(10, 20), FONT_HERSHEY_COMPLEX, 0.5, (0, 255, 255), 2, 8);
        imshow("FaceResult", origin_mat);
        imshow("Similar Pic", similar_mat);
        waitKey(0);
        destroyAllWindows();
        return 0;
    }
    

    相关文章

      网友评论

        本文标题:2018-01-15 EigenFace OpenCV 实现

        本文链接:https://www.haomeiwen.com/subject/pderoxtx.html