1. 程式人生 > >人臉識別之疲勞檢測(二)閾值法、KNN分類和K-means聚類

人臉識別之疲勞檢測(二)閾值法、KNN分類和K-means聚類

Table of Contents

1、均值法

2、中值法

3、KNN

結合上一節在獲得人眼特徵點後需要對睜眼閉眼狀態做出判斷,方法的選擇需要經驗結合公平的評價方法,使用大量測試集得到不同方法下的精確度並做出比較:

1、均值法

50幀睜眼資料取均值,得到不同閾值下精確度。

2、中值法

50幀睜眼資料取中值,得到不同閾值下精確度。

3、KNN

KNN是一種ML常用分類演算法,通過測量不同特徵值之間的距離進行分類。它的思路是:如果一個樣本在特徵空間中的k個最相似(即特徵空間中最鄰近)的樣本中的大多數屬於某一個類別,則該樣本也屬於這個類別,其中K通常是不大於20的整數。KNN演算法中,所選擇的鄰居都是已經正確分類的物件。該方法在定類決策上只依據最鄰近的一個或者幾個樣本的類別來決定待分樣本所屬的類別。

下面通過一個簡單的例子說明一下:如下圖,綠色圓要被決定賦予哪個類,是紅色三角形還是藍色四方形?如果K=3,由於紅色三角形所佔比例為2/3,綠色圓將被賦予紅色三角形那個類,如果K=5,由於藍色四方形比例為3/5,因此綠色圓被賦予藍色四方形類。

KNN演算法的思想:就是在訓練集中資料和標籤已知的情況下,輸入測試資料,將測試資料的特徵與訓練集中對應的特徵進行相互比較,找到訓練集中與之最為相似的前K個數據,則該測試資料對應的類別就是K個數據中出現次數最多的那個分類,其演算法的描述為:

1)計算測試資料與各個訓練資料之間的距離;

2)按照距離的遞增關係進行排序;

3)選取距離最小的K個點;

4)確定前K個點所在類別的出現頻率;

5)返回前K個點中出現頻率最高的類別作為測試資料的預測分類。

針對疲勞檢測問題:將50幀睜眼、50閉眼資料進行訓練,用1600張影象做測試,得到不同K值時的精確度。

KNN方法適用於訓練集較小,因為每次predict都需要遍歷訓練集,當然訓練集較大的話可以用KD-Tree KNN,減少搜尋時間。

OpenCV提供了KNN演算法介面,支援Mat資料作為訓練集和測試集,要求訓練集和測試集Mat形式相同,OpenCV設了很多assert,不支援直接給你丟擲異常,需要訓練前將特徵資料標準化。不過網上只提供了標準輸入引數,閱讀完介面可以根據具體資料做一些轉換,比如要訓練的資料是一維的,把Vector直接放進Mat也可以,有了這介面可以少敲不少程式碼。下附KNN法主要程式碼:

//KNN clarify eye status
    cv::Mat train_data;
    cv::Mat train_labels;;   //特徵
    int train_num = 50;

    for(int i = 0; i < train_num ; i++)
    {
        train_data.push_back(left_eye_ratio_[i]);  //序列化後放入data
        train_labels.push_back(1);  //對應的標註 睜眼1
    }
    for(int i = left_eye_ratio_.size()/2; i < (left_eye_ratio_.size()/2 + train_num); i++)
    {
        train_data.push_back(left_eye_ratio_[i]);  //序列化後放入data
        train_labels.push_back(0);  //對應的標註 閉眼0
    }
    //使用KNN演算法    
    //cv::Ptr<cv::ml::TrainData> tData = cv::ml::TrainData::create(train_data, cv::ml::ROW_SAMPLE, train_labels);

    for(int K = 3;K <= 8;K += 1) {

        float count_left_eye = 0;
        float left_accuracy;
        float count_right_eye = 0;
        float right_accuracy;

        cv::Ptr<cv::ml::KNearest> model = cv::ml::KNearest::create();
        model->setDefaultK(K);
        model->setIsClassifier(true);
        //2nd KDTREE-KNN 訓練集很大
        model->setAlgorithmType(cv::ml::KNearest::BRUTE_FORCE);

        model->train(train_data, cv::ml::ROW_SAMPLE, train_labels);
        
        cout<< "K ="<< K<<"  ";
        for(int i = 0; i< left_eye_ratio_.size()/2; i++) {
//            if(left_eye_ratio_[i] > FACTOR*eyeMouthStatus.GetLeftEyeThresh()) {
//                count_left_eye++;
//            }
            cv::Mat test_Mat;
            test_Mat.push_back(left_eye_ratio_[i]);
            float cnn_num = model->predict(test_Mat);
            //cout << i <<"CNN predict"<< cnn_num << "  ";
            if(cnn_num == 1)
            {
                count_left_eye++;
            }
        }
        for(int i = left_eye_ratio_.size()/2; i< left_eye_ratio_.size(); i++) {
            cv::Mat test_Mat;
            test_Mat.push_back(left_eye_ratio_[i]);
            float cnn_num = model->predict(test_Mat);
//            if(left_eye_ratio_[i] < FACTOR*eyeMouthStatus.GetLeftEyeThresh()) {
//                count_left_eye++;
//            }
            if(cnn_num == 0)
            {
                count_left_eye++;
            }
        }
        left_accuracy = count_left_eye/left_eye_ratio_.size();
        cout<<"left_accuracy"<<left_accuracy<<"  ";

        for(int i = 0; i< right_eye_ratio_.size()/2; i++) {
            cv::Mat test_Mat;
            test_Mat.push_back(right_eye_ratio_[i]);
            float cnn_num = model->predict(test_Mat);
            if(cnn_num == 1)
            {
                count_right_eye++;
            }
        }
        for(int i = right_eye_ratio_.size()/2; i< right_eye_ratio_.size(); i++) {
            cv::Mat test_Mat;
            test_Mat.push_back(right_eye_ratio_[i]);
            float cnn_num = model->predict(test_Mat);
            if(cnn_num == 0)
            {
                count_right_eye++;
            }
        }
        right_accuracy = count_right_eye/right_eye_ratio_.size();
        cout<<"right_accuracy"<<right_accuracy<<endl;
    }

三種方法對比效果如下圖:其中K取值3-8效果相同,橫軸為test1、2不同閾值,縱軸為對應精確度。

4、K-means

K-means顧名思義,K表示需要聚成的K個類,means指物件均值。

K-MEANS演算法是輸入聚類個數k,以及包含 n個數據物件的資料庫,輸出滿足方差最小標準k個聚類的一種演算法。k-means 演算法接受輸入量 k ;然後將n個數據物件劃分為 k個聚類以便使得所獲得的聚類滿足:同一聚類中的物件相似度較高;而不同聚類中的物件相似度較小。

基本步驟
(1) 從 n個數據物件任意選擇 k 個物件作為初始聚類中心;
(2) 根據每個聚類物件的均值(中心物件),計算每個物件與這些中心物件的距離;並根據最小距離重新對相應物件進行劃分;
(3) 重新計算每個(有變化)聚類的均值(中心物件);
(4) 計算標準測度函式,當滿足一定條件,如函式收斂時,則演算法終止;如果條件不滿足則回到步驟(2)。

用K-means方法處理眼睛的睜閉狀態在實際應用中取得了非常好的效果,主要的優點有:校準時可以不用區分睜閉眼狀態,使得演算法相容性好,而前三種方法在實際使用時往往會使用錯誤的資料進行訓練;對個體自適應情況好,對不同個體能自適應較好的睜閉眼區間值。這部分程式碼就不貼了,主要還是用OpenCV自帶的演算法,步驟可以參考上一節KNN,引數調整下,OK了。