美文网首页
利用opencv实现人脸识别

利用opencv实现人脸识别

作者: lemonCode | 来源:发表于2017-05-25 18:56 被阅读704次

    利用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;
           }

    相关文章

      网友评论

          本文标题:利用opencv实现人脸识别

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