使用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工程待整理:

 

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