利用opencv实现人脸识别

利用opencv实现人脸识别

introduction

  1. 使用弱联级分类器主要包含两部分:训练和检测,检测通常使用HAAR或LBP模型,这部分的描述在另外一个文件中,这部分主要介绍弱联级加速分类器的训练,主要包括:收集训练数据,准备或者处理训练数据,以及进行训练;
  2. 使用的工具 opencv_traincascade tool.这个是C++利用opencv2.x和opencv3.x写的,这个训练工具支持HAAP/LBP特征,对于训练的效果,这个主要取决于所用的数据和参数的选择
  3. 实现的工具:VS2015+opencv(plus contrib);

question

  1. 这篇文章是跟一位大神学得大神博客地址opencv官网,按照步骤写的,我自己也实践了;所以就按照自己的理解再写一点,将自己填的坑记录一下;
  2. 做人脸识别需要清楚一些opencv的一些基础的概念以及图像处理的一些基础知识,比如一些很简单的区别人脸识别和人脸检测等;
  3. opencv官方版没有将一些算法开放,还有一些在contrib中,所以我们想用的一些opencv 的高级一点的算法均在contrib中,这个就需要自己去编译了,具体的编译网上有很多的个版本,但一定要找到合适自己电脑的那一个,我将自己的编译的坑也总结出来了,用得到的可以去看看;建议你刚开始用就自己编译的加了contrib的opencv,否则训练模型的时候就会各种问题,各种API不支持的奇葩问题出现;
  4. 生成标签的部分用到了python,由于以前没有接触过,语法也很不熟悉,需要熟悉这部分来实现标签的区分;

practice

数据的收集和预处理

  1. 数据的收集,使用到的数据库是opencv官方给出的 The AT&T Facedatabase ,又称之为ORL数据库,但你下载下来之后发现是由10个文件夹包含的400张pgm的照片,但是在window 上你还打不开看不了这个到底张什么样,只能通过opencv自己写个小程序imread()来看看了..
  2. 收集到别人的数据库也不可啊,你得需求是让计算机得认得我啊,所以还需要你自己的照片,拿这些照片和你的照片一起来训练模型,既然学了opencv那么就自己写个小程序来实现,在我的前两篇文章中写了具体的实现代码,具体的逻辑就是打开电脑的摄像头,当按下空格键的时候保存当前帧并显示出来,感兴趣的可以关注我的简书,此片博文的链接opencv实现简单的拍照程序及照片的裁切;
  3. 这样收集到自己的图片了,发现自己的图片太大,所以需要处理一下,利用opencv自己的模型检测并分割出人脸,这个地方需要注意的是下载的图片是92X112,那么你在检测之后对得到的ROI做一次reSize()即可;这样就可以得到想要的大小的图片了;
  4. 将处理之后的图片放置在第41文件夹中;这个时候就需要处理at.txt了,这个相当于一个标签,标注每个图片代表的是谁,那几张图片表示的是同一个人;需要处理这个;
  5. 对于数据的收集和预处理的总结:
    1. 这部分没有什么代码,但是要深刻理解这里面的一些词的含义,一些细节性的东西,前期的准备一定要做足,理论知识,工具,数据资料等;我的时间主要浪费在自己编译opencv+contrib上面了,下载了多个不同版本的vs,安装且试用了,也利用不同版本的编译了,下载时间+安装时间+卸载时间的耗费超大,所以给大家建议,一定要看清楚vs版本自己电脑的配置以及opencv的版本这几个的搭配再搞;
    2. 这部分的理论知识:
      1. 要做人脸识别,首先要做的就是收集数据训练模型,这个模型就是含有你特征的模型,在识别的时候加载模型看相似度,在一定值范围之内则判定为同一个人;
      2. 然后理解cv的每一个API的利用场景,比如:若是要训练模型接受的图像必须是灰度图,且为了减少光照干扰灰度图必须实现归一化,若是再做的好点可以在把图片resize一下;

训练模型

  1. CSV文件的生成,记录每张图片的位置和是谁,这个也就相当于一个标签,这个csv文件的生成比较麻烦,使用python脚本自动生成;代码在下面贴出来;

  2. 训练模型;csv文件和图片已经准备好了,接下来就是训练模型,这个要用到opencv里面的Facerecognizer类,opencv里面的所有的人脸识别模型都来自于这个类,这个类为所有的人脸识别算法提供了通用的借口,这个类包含了接下来的几个函数:

         Moreover every FaceRecognizer supports the:
         * Training of a FaceRecognizer with FaceRecognizer::train() on a given set of images (your face database!).
         * Prediction of a given sample image, that means a face. The image is given as a Mat.
         * Loading/Saving the model state from/to a given XML or YAML.
         * Setting/Getting labels info, that is storaged as a string. String labels info is useful for keeping names of the recognized people
    
  3. 使用facerecoginzer类来训练模型

         Ptr<FaceRecognizer> model = createEigenFaceRecognizer(); 
         model->train(images, labels);  
         model->save("MyFacePCAModel.xml");  
           
         Ptr<FaceRecognizer> model1 = createFisherFaceRecognizer();  
         model1->train(images, labels);  
         model1->save("MyFaceFisherModel.xml");  
           
         Ptr<FaceRecognizer> model2 = createLBPHFaceRecognizer();  
         model2->train(images, labels);  
         model2->save("MyFaceLBPHModel.xml"); 
    
  4. 要训练模型就需要image和lable了,也就是把刚刚用python生成的at.txt读出来了,读取这个文件在cv中使用stringstream和getline;

         std::ifstream file(filename.c_str(), ifstream::in);
         if (!file) {
             string error_message = "No valid input file was given, please check the given filename.";
             CV_Error(CV_StsBadArg, error_message);
         }
         string line, path, classlabel;
         while (getline(file, line)) {
             stringstream liness(line);
             getline(liness, path, separator);
             getline(liness, classlabel);
             if (!path.empty() && !classlabel.empty()) {
                 images.push_back(imread(path, 0));
                 labels.push_back(atoi(classlabel.c_str()));
             }
         }
    
  5. 训练完了还有比较重要的一部prediction

         int predictedLabel = model->predict(testSample);
         int predictedLabel1 = model1->predict(testSample);
         int predictedLabel2 = model2->predict(testSample);
     
         // 还有一种调用方式,可以获取结果同时得到阈值:  
         //      int predictedLabel = -1;  
         //      double confidence = 0.0;  
         //      model->predict(testSample, predictedLabel, confidence);  
     
         string result_message = format("Predicted class = %d / Actual class = %d.", predictedLabel, testLabel);
         string result_message1 = format("Predicted class = %d / Actual class = %d.", predictedLabel1, testLabel);
         string result_message2 = format("Predicted class = %d / Actual class = %d.", predictedLabel2, testLabel);
         cout << result_message << endl;
         cout << result_message1 << endl;
         cout << result_message2 << endl;
    
  6. 模型训练的总结

    1. 主要分为准备数据做csv文件,读取文件,训练模型,做预测,这个是主要的步骤,但里面需要注意的点很多;我在上面也分别做了说明
    2. 下面的代码分为两部分,一部分是训练,另一部分则是一个生成csv文件的python脚本;
  7. 源代码

    #include<opencv2\face\facerec.hpp>
    #include<opencv2\core.hpp>
    #include<opencv2\face.hpp>
    #include<opencv2\highgui.hpp>
    #include<opencv2\imgproc.hpp>
    #include <iostream>  
    #include <fstream>  
    #include <sstream>  
    #include <math.h>  
    
    using namespace cv;
    using namespace cv::face;
    using namespace std;
    
    static Mat norm_0_255(InputArray _src) {
        Mat src = _src.getMat();
        // 按照不同通道创建和返回一个归一化后的图像矩阵:  
        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;
    }
    
    //使用CSV文件去读图像和标签,主要使用stringstream和getline方法,这里面涉及到一些常见的C++语法  
    static void read_csv(const string& filename, vector<Mat>& images, vector<int>& labels, char separator = ';') {
        std::ifstream file(filename.c_str(), ifstream::in);
        if (!file) {
            string error_message = "No valid input file was given, please check the given filename.";
            CV_Error(CV_StsBadArg, error_message);
        }
        string line, path, classlabel;
        while (getline(file, line)) {
            stringstream liness(line);
            getline(liness, path, separator);
            getline(liness, classlabel);
            if (!path.empty() && !classlabel.empty()) {
                images.push_back(imread(path, 0));
                labels.push_back(atoi(classlabel.c_str()));
            }
        }
    }
    
    
    int main()
    {
    
        //读取你的CSV文件路径.  
        string fn_csv = "at.txt";
    
        // 2个容器来存放图像数据和对应的标签  
        vector<Mat> images;
        vector<int> labels;
        // 读取数据. 如果文件不合法就会出错  
        // 输入的文件名已经有了.  
        try
        {
            read_csv(fn_csv, images, labels);
        }
        catch (cv::Exception& e)
        {
            cerr << "Error opening file \"" << fn_csv << "\". Reason: " << e.msg << endl;
            // 文件有问题,我们啥也做不了了,退出了  
            exit(1);
        }
        // 如果没有读取到足够图片,也退出.  
        if (images.size() <= 1) {
            string error_message = "This demo needs at least 2 images to work. Please add more images to your data set!";
            CV_Error(CV_StsError, error_message);
        }
    
        // 下面的几行代码仅仅是从你的数据集中移除最后一张图片  
        //[gm:自然这里需要根据自己的需要修改,他这里简化了很多问题]  
        Mat testSample = images[images.size() - 1];
        int testLabel = labels[labels.size() - 1];
        images.pop_back();
        labels.pop_back();
        // 下面几行创建了一个特征脸模型用于人脸识别,  
        // 通过CSV文件读取的图像和标签训练它。  
        //如果你只想保留10个主成分,使用如下代码  
        //   cv::createEigenFaceRecognizer(10);  
        //  
        // 如果你还希望使用置信度阈值来初始化,使用以下语句:  
        //      cv::createEigenFaceRecognizer(10, 123.0);  
        //  
        // 如果你使用所有特征并且使用一个阈值,使用以下语句:  
        //      cv::createEigenFaceRecognizer(0, 123.0);  
    
        Ptr<BasicFaceRecognizer> model = createEigenFaceRecognizer();
        model->train(images, labels);
        model->save("MyFacePCAModel.xml");
    
        Ptr<BasicFaceRecognizer> model1 = createFisherFaceRecognizer();
        model1->train(images, labels);
        model1->save("MyFaceFisherModel.xml");
    
        Ptr<LBPHFaceRecognizer> model2 = createLBPHFaceRecognizer();
        model2->train(images, labels);
        model2->save("MyFaceLBPHModel.xml");
    
        // 下面对测试图像进行预测,predictedLabel是预测标签结果  
        int predictedLabel = model->predict(testSample);
        int predictedLabel1 = model1->predict(testSample);
        int predictedLabel2 = model2->predict(testSample);
    
        // 还有一种调用方式,可以获取结果同时得到阈值:  
        //      int predictedLabel = -1;  
        //      double confidence = 0.0;  
        //      model->predict(testSample, predictedLabel, confidence);  
    
        string result_message = format("Predicted class = %d / Actual class = %d.", predictedLabel, testLabel);
        string result_message1 = format("Predicted class = %d / Actual class = %d.", predictedLabel1, testLabel);
        string result_message2 = format("Predicted class = %d / Actual class = %d.", predictedLabel2, testLabel);
        cout << result_message << endl;
        cout << result_message1 << endl;
        cout << result_message2 << endl;
    
        getchar();
        waitKey(0);
        return 0;
    }

人脸识别

  1. 实现理论:这个部分就是做检测了,打开摄像头,加载人脸检测器,检测出人脸,然后拿这个检测出来的人脸和模型里面的对比,看看是谁的;原理很简单但实现出来的结果差强人意;

  2. 问题点:

    • 的确cv实现的比较慢而且卡顿现象比较明显,python的确是机器学习的利器,而且上手容易
    • 使用cv最好使用自己编译的,官方的这个版本很多的比较好的算法都没有,自己编译的时候有很多的坑,我自己整理了一些opencv+contrib+vs编译的一些问题;
    • cv用起来不难,但是理解起来比较难,里面涉及到的算法有点多;很多都很不好理解;
  3. 代码实现

         #include<opencv2\opencv.hpp>  
         #include<opencv2\face.hpp>
         #include<iostream>  
         
         using namespace std;
         using namespace cv;
         using namespace cv::face;
         
         int main()
         {
             VideoCapture cap(0);   
             if (!cap.isOpened())
             {
                 return -1;
             }
             Mat frame;
             Mat edges;
             Mat gray;
             
         
         
             CascadeClassifier cascade;
             bool stop = false;
             //训练好的文件名称,放置在可执行文件同目录下  
             cascade.load("lbpcascade_frontalface.xml");
         
             Ptr<FaceRecognizer> modelPCA = createEigenFaceRecognizer();
             modelPCA->load("MyFacePCAModel.xml");
         
             while (1)
             {
                 cap >> frame;
         
                 //建立用于存放人脸的向量容器  
                 vector<Rect> faces(0);
         
                 cvtColor(frame, gray, CV_BGR2GRAY);
                 //改变图像大小,使用双线性差值  
                 //Mat smallImg(cvRound(frame.rows / 1.3), cvRound(frame.cols / 1.3), CV_8UC1);
                 //resize(gray, smallImg, smallImg.size(), 0, 0, INTER_LINEAR);  
                 //变换后的图像进行直方图均值化处理  
                 equalizeHist(gray, gray);
         
                 cascade.detectMultiScale(gray, faces,
                     1.1, 2, 0
                     //|CV_HAAR_FIND_BIGGEST_OBJECT  
                     //|CV_HAAR_DO_ROUGH_SEARCH  
                     | CV_HAAR_SCALE_IMAGE,
                     Size(30, 30));
         
                 Mat face;
                 Point text_lb;
         
                 for (size_t i = 0; i < faces.size(); i++)
                 {
                     if (faces[i].height > 0 && faces[i].width > 0)
                     {
                         face = gray(faces[i]);
                         text_lb = Point(faces[i].x, faces[i].y);
         
                         rectangle(frame, faces[i], Scalar(255, 0, 0), 1, 8, 0);
                     }
                 }
         
                 Mat face_test;
         
                 int predictPCA = 0;
                 if (face.rows >= 120)
                 {
                     resize(face, face_test, Size(92, 112));
         
                 }
                 //Mat face_test_gray;  
                 //cvtColor(face_test, face_test_gray, CV_BGR2GRAY);  
         
                 if (!face_test.empty())
                 {
                     //测试图像应该是灰度图  
                     predictPCA = modelPCA->predict(face_test);
                 }
         
                 cout << predictPCA << endl;
                 if (predictPCA == 41)
                 {
                     string name = "Lemon";
                     putText(frame, name, text_lb, FONT_HERSHEY_COMPLEX, 1, Scalar(0, 0, 255));
                 }
                 else {
                     putText(frame, "unknow", text_lb, FONT_HERSHEY_COMPLEX, 1, Scalar(0, 0, 255));
                 }
                 
         
                 imshow("face", frame);
                 waitKey(200);
             }
         
             return 0;
         }
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,425评论 4 361
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,058评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,186评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,848评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,249评论 3 286
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,554评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,830评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,536评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,239评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,505评论 2 244
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,004评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,346评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,999评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,060评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,821评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,574评论 2 271
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,480评论 2 267

推荐阅读更多精彩内容