利用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閱讀 228,923評論 6 535
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,740評論 3 420
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,856評論 0 380
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,175評論 1 315
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,931評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,321評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,383評論 3 443
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,533評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,082評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,891評論 3 356
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,067評論 1 371
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,618評論 5 362
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,319評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,732評論 0 27
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,987評論 1 289
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,794評論 3 394
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,076評論 2 375

推薦閱讀更多精彩內容