利用opencv實(shí)現(xiàn)人臉識(shí)別

利用opencv實(shí)現(xiàn)人臉識(shí)別

introduction

  1. 使用弱聯(lián)級(jí)分類器主要包含兩部分:訓(xùn)練和檢測,檢測通常使用HAAR或LBP模型,這部分的描述在另外一個(gè)文件中,這部分主要介紹弱聯(lián)級(jí)加速分類器的訓(xùn)練,主要包括:收集訓(xùn)練數(shù)據(jù),準(zhǔn)備或者處理訓(xùn)練數(shù)據(jù),以及進(jìn)行訓(xùn)練;
  2. 使用的工具 opencv_traincascade tool.這個(gè)是C++利用opencv2.x和opencv3.x寫的,這個(gè)訓(xùn)練工具支持HAAP/LBP特征,對于訓(xùn)練的效果,這個(gè)主要取決于所用的數(shù)據(jù)和參數(shù)的選擇
  3. 實(shí)現(xiàn)的工具:VS2015+opencv(plus contrib);

question

  1. 這篇文章是跟一位大神學(xué)得大神博客地址opencv官網(wǎng),按照步驟寫的,我自己也實(shí)踐了;所以就按照自己的理解再寫一點(diǎn),將自己填的坑記錄一下;
  2. 做人臉識(shí)別需要清楚一些opencv的一些基礎(chǔ)的概念以及圖像處理的一些基礎(chǔ)知識(shí),比如一些很簡單的區(qū)別人臉識(shí)別和人臉檢測等;
  3. opencv官方版沒有將一些算法開放,還有一些在contrib中,所以我們想用的一些opencv 的高級(jí)一點(diǎn)的算法均在contrib中,這個(gè)就需要自己去編譯了,具體的編譯網(wǎng)上有很多的個(gè)版本,但一定要找到合適自己電腦的那一個(gè),我將自己的編譯的坑也總結(jié)出來了,用得到的可以去看看;建議你剛開始用就自己編譯的加了contrib的opencv,否則訓(xùn)練模型的時(shí)候就會(huì)各種問題,各種API不支持的奇葩問題出現(xiàn);
  4. 生成標(biāo)簽的部分用到了python,由于以前沒有接觸過,語法也很不熟悉,需要熟悉這部分來實(shí)現(xiàn)標(biāo)簽的區(qū)分;

practice

數(shù)據(jù)的收集和預(yù)處理

  1. 數(shù)據(jù)的收集,使用到的數(shù)據(jù)庫是opencv官方給出的 The AT&T Facedatabase ,又稱之為ORL數(shù)據(jù)庫,但你下載下來之后發(fā)現(xiàn)是由10個(gè)文件夾包含的400張pgm的照片,但是在window 上你還打不開看不了這個(gè)到底張什么樣,只能通過opencv自己寫個(gè)小程序imread()來看看了..
  2. 收集到別人的數(shù)據(jù)庫也不可啊,你得需求是讓計(jì)算機(jī)得認(rèn)得我啊,所以還需要你自己的照片,拿這些照片和你的照片一起來訓(xùn)練模型,既然學(xué)了opencv那么就自己寫個(gè)小程序來實(shí)現(xiàn),在我的前兩篇文章中寫了具體的實(shí)現(xiàn)代碼,具體的邏輯就是打開電腦的攝像頭,當(dāng)按下空格鍵的時(shí)候保存當(dāng)前幀并顯示出來,感興趣的可以關(guān)注我的簡書,此片博文的鏈接opencv實(shí)現(xiàn)簡單的拍照程序及照片的裁切;
  3. 這樣收集到自己的圖片了,發(fā)現(xiàn)自己的圖片太大,所以需要處理一下,利用opencv自己的模型檢測并分割出人臉,這個(gè)地方需要注意的是下載的圖片是92X112,那么你在檢測之后對得到的ROI做一次reSize()即可;這樣就可以得到想要的大小的圖片了;
  4. 將處理之后的圖片放置在第41文件夾中;這個(gè)時(shí)候就需要處理at.txt了,這個(gè)相當(dāng)于一個(gè)標(biāo)簽,標(biāo)注每個(gè)圖片代表的是誰,那幾張圖片表示的是同一個(gè)人;需要處理這個(gè);
  5. 對于數(shù)據(jù)的收集和預(yù)處理的總結(jié):
    1. 這部分沒有什么代碼,但是要深刻理解這里面的一些詞的含義,一些細(xì)節(jié)性的東西,前期的準(zhǔn)備一定要做足,理論知識(shí),工具,數(shù)據(jù)資料等;我的時(shí)間主要浪費(fèi)在自己編譯opencv+contrib上面了,下載了多個(gè)不同版本的vs,安裝且試用了,也利用不同版本的編譯了,下載時(shí)間+安裝時(shí)間+卸載時(shí)間的耗費(fèi)超大,所以給大家建議,一定要看清楚vs版本自己電腦的配置以及opencv的版本這幾個(gè)的搭配再搞;
    2. 這部分的理論知識(shí):
      1. 要做人臉識(shí)別,首先要做的就是收集數(shù)據(jù)訓(xùn)練模型,這個(gè)模型就是含有你特征的模型,在識(shí)別的時(shí)候加載模型看相似度,在一定值范圍之內(nèi)則判定為同一個(gè)人;
      2. 然后理解cv的每一個(gè)API的利用場景,比如:若是要訓(xùn)練模型接受的圖像必須是灰度圖,且為了減少光照干擾灰度圖必須實(shí)現(xiàn)歸一化,若是再做的好點(diǎn)可以在把圖片resize一下;

訓(xùn)練模型

  1. CSV文件的生成,記錄每張圖片的位置和是誰,這個(gè)也就相當(dāng)于一個(gè)標(biāo)簽,這個(gè)csv文件的生成比較麻煩,使用python腳本自動(dòng)生成;代碼在下面貼出來;

  2. 訓(xùn)練模型;csv文件和圖片已經(jīng)準(zhǔn)備好了,接下來就是訓(xùn)練模型,這個(gè)要用到opencv里面的Facerecognizer類,opencv里面的所有的人臉識(shí)別模型都來自于這個(gè)類,這個(gè)類為所有的人臉識(shí)別算法提供了通用的借口,這個(gè)類包含了接下來的幾個(gè)函數(shù):

         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類來訓(xùn)練模型

         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. 要訓(xùn)練模型就需要image和lable了,也就是把剛剛用python生成的at.txt讀出來了,讀取這個(gè)文件在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. 訓(xùn)練完了還有比較重要的一部prediction

         int predictedLabel = model->predict(testSample);
         int predictedLabel1 = model1->predict(testSample);
         int predictedLabel2 = model2->predict(testSample);
     
         // 還有一種調(diào)用方式,可以獲取結(jié)果同時(shí)得到閾值:  
         //      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. 模型訓(xùn)練的總結(jié)

    1. 主要分為準(zhǔn)備數(shù)據(jù)做csv文件,讀取文件,訓(xùn)練模型,做預(yù)測,這個(gè)是主要的步驟,但里面需要注意的點(diǎn)很多;我在上面也分別做了說明
    2. 下面的代碼分為兩部分,一部分是訓(xùn)練,另一部分則是一個(gè)生成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();
        // 按照不同通道創(chuàng)建和返回一個(gè)歸一化后的圖像矩陣:  
        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文件去讀圖像和標(biāo)簽,主要使用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個(gè)容器來存放圖像數(shù)據(jù)和對應(yīng)的標(biāo)簽  
        vector<Mat> images;
        vector<int> labels;
        // 讀取數(shù)據(jù). 如果文件不合法就會(huì)出錯(cuò)  
        // 輸入的文件名已經(jīng)有了.  
        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);
        }
    
        // 下面的幾行代碼僅僅是從你的數(shù)據(jù)集中移除最后一張圖片  
        //[gm:自然這里需要根據(jù)自己的需要修改,他這里簡化了很多問題]  
        Mat testSample = images[images.size() - 1];
        int testLabel = labels[labels.size() - 1];
        images.pop_back();
        labels.pop_back();
        // 下面幾行創(chuàng)建了一個(gè)特征臉模型用于人臉識(shí)別,  
        // 通過CSV文件讀取的圖像和標(biāo)簽訓(xùn)練它。  
        //如果你只想保留10個(gè)主成分,使用如下代碼  
        //   cv::createEigenFaceRecognizer(10);  
        //  
        // 如果你還希望使用置信度閾值來初始化,使用以下語句:  
        //      cv::createEigenFaceRecognizer(10, 123.0);  
        //  
        // 如果你使用所有特征并且使用一個(gè)閾值,使用以下語句:  
        //      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");
    
        // 下面對測試圖像進(jìn)行預(yù)測,predictedLabel是預(yù)測標(biāo)簽結(jié)果  
        int predictedLabel = model->predict(testSample);
        int predictedLabel1 = model1->predict(testSample);
        int predictedLabel2 = model2->predict(testSample);
    
        // 還有一種調(diào)用方式,可以獲取結(jié)果同時(shí)得到閾值:  
        //      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;
    }

人臉識(shí)別

  1. 實(shí)現(xiàn)理論:這個(gè)部分就是做檢測了,打開攝像頭,加載人臉檢測器,檢測出人臉,然后拿這個(gè)檢測出來的人臉和模型里面的對比,看看是誰的;原理很簡單但實(shí)現(xiàn)出來的結(jié)果差強(qiáng)人意;

  2. 問題點(diǎn):

    • 的確cv實(shí)現(xiàn)的比較慢而且卡頓現(xiàn)象比較明顯,python的確是機(jī)器學(xué)習(xí)的利器,而且上手容易
    • 使用cv最好使用自己編譯的,官方的這個(gè)版本很多的比較好的算法都沒有,自己編譯的時(shí)候有很多的坑,我自己整理了一些opencv+contrib+vs編譯的一些問題;
    • cv用起來不難,但是理解起來比較難,里面涉及到的算法有點(diǎn)多;很多都很不好理解;
  3. 代碼實(shí)現(xiàn)

         #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;
             //訓(xùn)練好的文件名稱,放置在可執(zhí)行文件同目錄下  
             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);  
                 //變換后的圖像進(jìn)行直方圖均值化處理  
                 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())
                 {
                     //測試圖像應(yīng)該是灰度圖  
                     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;
         }
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容