使用opencv實現基於Haar特徵的人臉檢測

       

       此文章主要是學習的記錄。使用opencv的版本是 3.4.6。實現了圖片的人臉檢測及人的眼睛、鼻子和嘴巴的檢測。裏面使用的窗口顯示相關的代碼都是opencv的函數。

 

人臉檢測

        openCV的人臉識別主要通過Haar特徵分類器實現的,haar特徵分類器是一個xml文件,文件描述了檢測物體的Haar特徵值。Haar分類器需要通過大量的數據來訓練,opecv的安裝路徑裏面有已經預先訓練好的分類器,路徑爲opencv\sources\data\haarcascades。看到其中的正臉的分類器如下:

                    

       此技術雖然適用於人臉檢測,但不限於人臉檢測,還可用於其他物體的檢測(對對應的物體收集數據進行訓練,得到其xml文件)。當然也可以對人臉識別進行更多的數據訓練,提高人臉的檢測精度。

 

      這個是隻檢測臉部的demo,因爲這裏是使用Qt創建的工程,圖片的相對路徑是Makefile的路徑。

代碼如下:

//只檢測臉部的demo,直接調用
bool detectFace()
{
    /********************************** 1.打開圖片 *************************************/
    string str = "face4.jpg";
    Mat img = imread(str);
    Mat imgGray;
    //轉灰度圖像
    cvtColor(img, imgGray, COLOR_BGR2GRAY);
    //顯示窗口
    namedWindow("display");
    imshow("display", img);

    /*********************************** 2.加載檢測器  ******************************/
    // 建立級聯分類器
    // 加載訓練好的 人臉檢測器(.xml)
    CascadeClassifier cascade_face;
    const string path_face = "xml\\haarcascade_frontalface_alt.xml";
    if (!cascade_face.load(path_face))
    {
        cout << "cascade load failed!\n";
        return -1;
    }
    //計時
    double t = 0;
    t = (double)getTickCount();

    /*********************************** 3.人臉檢測 ******************************/
    vector<Rect> faces(0);
    cascade_face.detectMultiScale(imgGray,  //輸入的原圖
                             faces,    //表示檢測到的人臉目標序列,輸出的結果
                             1.1,    //每次圖像尺寸減小的比例爲1.1
                             3,      //每一個目標至少要被檢測到3次纔算是真的目標
                             0 ,   // 默認
                             Size(30, 30)  //最小的檢測區域,根據使用場景來確定大小,比如太小的臉放棄
                             );


    cout << "detect face number is :" << faces.size() << endl;

    /********************************  4.顯示人臉矩形框 ******************************/

    if (faces.size() > 0)
    {
        //對臉部進行循環
        for (size_t i = 0;i < faces.size();i++)
        {
            cout<<"face "<<i<<" area x = "<<faces[i].x<<" area y = "<<faces[i].y
               <<" area width = "<<faces[i].width<<" area height = "<<faces[i].height<<endl;
            rectangle(img, faces[i], Scalar(150, 0, 0), 3, 8, 0);

        }
    }
    else
    {
        cout << "cant not get faces " << endl;
    }

    t = (double)getTickCount() - t;  //getTickCount():  Returns the number of ticks per second.
    cout<< t * 1000 / getTickFrequency() << "ms " << endl;

    //畫完人臉框的圖片再顯示
    imshow("display", img);

    while(waitKey(0)!='q') ;
    destroyWindow("display");
}


int main(int argc,char *argv[])
{
    detectFace();
    return 0;
}

 

結果如下,因爲背景不算複雜,所以檢測結果很準確。如果背景複雜的話就很可能檢測到錯誤的人臉或漏掉人臉。

人臉及眼睛、鼻子、嘴巴的檢測

        如果檢測的臉部區域比較大,且檢測的是正臉,爲了提高識別率,可以通過檢測到人臉再檢測判斷是否有人眼睛、鼻子、嘴來加以判斷。在opencv的安裝路徑下只能找到了臉和眼睛的分類器,而鼻子、嘴的分類器可以到opencv_contrib那裏去找:https://github.com/opencv/opencv_contrib

        關於opencv_contrib:

2014年8月3.0 alpha發佈,除大部分方法都使用OpenCL加速外,3.x默認包含以及使用IPP,同時,matlab bindings、Face Recognition、SIFT、SURF、 text detector、motion templates & simple flow 等都移到了opencv_contrib下(opencv_contrib不僅存放了尚未穩定的代碼,同時也存放了涉及專利保護的技術實現),大量涌現的新方法也包含在其中。

       在contrib中的face模塊裏可以找到更多的分類器,比如嘴巴的路徑是:

        modules/face/data/cascades/haarcascade_mcs_mouth.xml

 

       檢測的方法還是和上面類似的。主要是先檢測人臉,然後在人臉的特定區域裏面檢測 眼睛、鼻子和嘴巴,如果 眼睛、鼻子和嘴巴都存在,那是人臉的概率就特別大了。

       demo裏面對眼睛、鼻子和嘴巴的檢測做了些簡單的限制,這個應該還有進一步細化研究。

代碼:

#include<opencv2/opencv.hpp>
#include<iostream>

using namespace std;
using namespace cv;

CascadeClassifier cascade_face;
CascadeClassifier cascade_eye;
CascadeClassifier cascade_mouth;
CascadeClassifier cascade_nose;

bool detectFaceDetail(Mat& img);

int main(int argc,char *argv[])
{

    /*********************************** 1.加載檢測器  ******************************/
    // 建立級聯分類器
    // 加載訓練好的 人臉檢測器(.xml)
    const string path_face = "xml\\haarcascade_frontalface_alt.xml";
    if (!cascade_face.load(path_face))
    {
        cout << "cascade load failed!\n";
        return -1;
    }

    // 加載訓練好的 眼睛檢測器(.xml)
    const string path_eye = "xml\\haarcascade_eye.xml";
    if (!cascade_eye.load(path_eye))
    {
        cout << "cascade_eye load failed!\n";
        return -1;
    }

    // 加載訓練好的 嘴巴檢測器(.xml)
    const string path_mouth = "xml\\haarcascade_mcs_mouth.xml";
    if (!cascade_mouth.load(path_mouth))
    {
        cout << "cascade_mouth load failed!\n";
        return -1;
    }

    // 加載訓練好的 鼻子檢測器(.xml)
    const string path_nose = "xml\\haarcascade_mcs_nose.xml";
    if ( ! cascade_nose.load(path_nose))
    {
        cout << "cascade_mouth load failed!\n";
        return -1;
    }

    //單個圖片檢測
    string str = "face4.jpg";
    Mat img = imread(str);//放到makefile文件下
    namedWindow("display");
    imshow("display", img);
    detectFaceDetail(img);
    destroyWindow("display");
    namedWindow("face_detect");
    imshow("face_detect", img);
    while(waitKey(0)!='k') ;
    destroyWindow("face_detect");


    return 0;
}


//檢測臉部、眼睛、鼻子、嘴巴,傳入需要檢測的圖像
bool detectFaceDetail(Mat& img)
{
    bool getFace = false;
    Mat imgGray;
    /* 因爲用的是類haar特徵,所以都是基於灰度圖像的,這裏要轉換成灰度圖像 */
    cvtColor(img, imgGray, COLOR_BGR2GRAY);


    //計時
    double t = 0;
    t = (double)getTickCount();
    /*********************************** 2.人臉檢測 ******************************/
    vector<Rect> faces(0);

    cascade_face.detectMultiScale(imgGray,
                             faces,
                             1.1,
                             3,
                             0 ,
                             Size(30, 30) //過大則檢測不到人臉,根據使用場景來確定大小,比如太小的臉放棄
                             );

    cout << "detect face number is :" << faces.size() << endl;
    /********************************  3.顯示人臉矩形框 ******************************/

    if (faces.size() > 0)
    {
        //對臉部進行循環
        for (size_t i = 0;i < faces.size();i++)
        {
            cout<<"face "<<i<<" area x = "<<faces[i].x<<" area y = "<<faces[i].y
               <<" area width = "<<faces[i].width<<" area height = "<<faces[i].height<<endl;
            rectangle(img, faces[i], Scalar(150, 0, 0), 3, 8, 0);

            bool bGetEyes = false;
            bool bGetNose = false;
            bool bGetMouth = false;
            //臉部區域
            Mat faceROIGray = imgGray(faces[i]);
            Mat faceROI = img(faces[i]);
            //眼睛識別
            vector<Rect> eyes;
            cascade_eye.detectMultiScale(faceROIGray, eyes, 1.1, 3, 0 ,Size(10, 10));
            if(eyes.size() > 0)
            {
                cout << "detect eye number is :" << eyes.size() << endl;
                int iCoutEye = 0;
                for(size_t j = 0; j<eyes.size(); j++)
                {
                    //現在限制一下眼睛的位置,可以進一步細化
                    if(eyes[j].y+eyes[j].height < faces[i].height/3*2 && eyes[j].width<faces[i].width/2){
                        rectangle(faceROI, eyes[j], Scalar(150, 150, 0), 3, 8, 0);
                        iCoutEye++;
                        cout<<"eyes "<<j<<" area x = "<<eyes[j].x<<" area y = "<<eyes[j].y
                           <<" area width = "<<eyes[j].width<<" area height = "<<eyes[j].height<<endl;
                    }
                }
                //檢測到兩個眼睛
                if(iCoutEye >= 2){
                    bGetEyes = true;
                }
            }

            //鼻子識別
            vector<Rect> noses;
            cascade_nose.detectMultiScale(faceROIGray, noses, 1.1, 3, 0 ,Size(30, 30));
            if(noses.size() > 0)
            {
                cout << "detect nose number is :" << noses.size() << endl;

                for(size_t j = 0; j<noses.size(); j++)
                {
                    //現在限制一下鼻子的位置,可以進一步細化,比如要求正臉正對攝像頭就可以對鼻子的中心點做些限制
                    if(noses[j].y > faces[i].height/4){
                        bGetNose = true;
                        rectangle(faceROI, noses[j], Scalar(150, 200, 200), 3, 8, 0);
                        cout<<"eyes "<<j<<" area x = "<<noses[j].x<<" area y = "<<noses[j].y
                           <<" area width = "<<noses[j].width<<" area height = "<<noses[j].height<<endl;
                    }
                }
            }

            //嘴巴識別
            vector<Rect> mouths;
            cascade_mouth.detectMultiScale(faceROIGray, mouths, 1.1, 3, 0 | CASCADE_SCALE_IMAGE ,Size(30, 30));
            if(mouths.size() > 0)
            {
                cout << "detect eye number is :" << mouths.size() << endl;

                for(size_t j = 0; j<mouths.size(); j++)
                {
//                    Point mouth_center(faces[i].x + mouths[0].x + mouths[0].width / 2, faces[i].y + mouths[0].y + mouths[0].height / 2); //嘴巴的中心
                    //現在限制一下嘴巴的位置,可以進一步細化
                    if(mouths[j].y > faces[i].height/3)
                    {
                        bGetMouth = true;
                        rectangle(faceROI, mouths[j], Scalar(150, 150, 150), 3, 8, 0);
                        cout<<"eyes "<<j<<" area x = "<<mouths[j].x<<" area y = "<<mouths[j].y
                           <<" area width = "<<mouths[j].width<<" area height = "<<mouths[j].height<<endl;
                    }
                }
            }

            //到時只有檢測到眼睛、鼻子、嘴巴才把人臉框畫出來,其它的都不畫
            if(bGetEyes && bGetMouth && bGetNose){
                getFace = true;
//                rectangle(img, faces[i], Scalar(150, 0, 0), 3, 8, 0);
            }

        }// end face while
    }
    else
    {
        cout << "cant not get faces " << endl;
    }

    t = (double)getTickCount() - t;  //getTickCount():  Returns the number of ticks per second.
    cout<< t * 1000 / getTickFrequency() << "ms " << endl;

    return getFace;
}

結果如下(上面的圖片臉好像太小了,換一張):

                                  

demo工程待整理:

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章