C++开发人脸性别识别教程(12)——添加性别识别功能

  经过之前几篇博客的讲解,我们已经成功搭建了MFC应用框架,并实现了基本的图像显示和人脸检测程序,在这篇博文中我们要向其中添加性别识别代码。

  关于性别识别,之前已经专门拿出两篇博客的篇幅来进行讲解,这里不再赘述,具体参见:C++开发人脸性别识别教程(5)——通过FaceRecognizer类实现性别识别C++开发人脸性别识别教程(6)——通过SVM实现性别识别

  一、分类器训练

  在进行人脸性别识别之前需要训练性别识别的分类器,而分类器的训练过程是相对耗时的(大约五分钟),因此这里我们采用离线训练在线识别的模式,即提前将分类器训练好,作为程序的数据进行保存,程序运行过程中直接加载已经训练好的分类器进行性别分类,这样速度就会大大提高。

  在上面提供的两篇博客中都详细介绍了性别识别分类器的训练方法,这里一共需要训练四种分类器,分别是PCA、Fisher、LBP、SVM:

  二、添加下拉列表控件

  1、绘制控件

  由于这里有四种性别识别的方法,因此在程序运行时,需要用户指定一种性别识别的方法,这里提供一个下拉选择列表(Combo Box)控件来供用户选择。首先从工具箱中选中该控件,在MFC主窗口的合适位置进行绘制,并将ID更改为IDC_COMBO_FUNCTION:

  2、指定选项值

  接下来需要在CGenderRecognitionMFCDlg类的OnInitDialog()初始化函数中为下拉列表设置ID标号以及对应的显示文本:

    /*********初始化Combo Box控件**********/
    ((CComboBox*)GetDlgItem(IDC_COMBO_FUNCTION))->AddString("PCA变换");
    ((CComboBox*)GetDlgItem(IDC_COMBO_FUNCTION))->AddString("Fisher变换");
    ((CComboBox*)GetDlgItem(IDC_COMBO_FUNCTION))->AddString("LBP变换");
    ((CComboBox*)GetDlgItem(IDC_COMBO_FUNCTION))->AddString("支持向量机");
    ((CComboBox*)GetDlgItem(IDC_COMBO_FUNCTION))->SetCurSel(1);      //设置当前默认显示选项

  注意这里Combo Box控件的各个选项的标号是默认从“0”开始进行标号的,即这里“0”代表“PCA变换”,“1”代表“Fisher变换”,“2”代表“LBP变换”,“3”代表“支持向量机”,默认显示”Fisher变换“:

  这里有两个小细节需要注意:

  (1)需要提前指定Combo Box的下拉范围,这样才能保证在单击下拉按钮时控件能够将所有选项全部显示出来:

  (2)Combo Box控件的”sort“属性,应该置为”false“:

  三、添加性别识别算法

  绘制完ComboBox控件之后,开始向其中填入性别识别算法。

  1、全局变量声明

  在之前性别识别的博客中介绍得很清楚,在使用OpenCv封装的分类器之前,需要声明几个静态的模板变量,我们这里将其声明为全局变量,放在GenderRecognitionMFCDlg.cpp文件的开头部分:

/************初始化性别分类器************/
static Ptr<FaceRecognizer> model_PCA = createEigenFaceRecognizer();    //PCA分类器
static Ptr<FaceRecognizer> model_Fisher = createFisherFaceRecognizer();//Fisher分类器
static Ptr<FaceRecognizer> model_LBP = createLBPHFaceRecognizer();     //LBP分类器
static CvSVM svm;                                                      //支持向量机分类器

  2、在”初始化“按钮中加载分类器

  这里将分类器的加载操作安排在”初始化“按钮对应的事件响应函数OnBnClickedButtonInitial()中,即用户单击”初始化“按钮之后,程序会根据当前用户选择的方法来加载指定的分类器。由于需要根据用户当前在下拉列表中的选择情况来进行分类器的加载,因此需要下得到用户的选择的标号,然后通过switch语句实现有选择的加载,代码如下:

    /**********根据用户的选择来加载分类器**********/
    int index = 0;
    index = ((CComboBox*)GetDlgItem(IDC_COMBO_FUNCTION))->GetCurSel();
    switch (index)
    {
    case 0:
        model_PCA->load("E:\\性别识别数据库—CAS-PEAL\\面部训练样本\\PCA_Model.xml");
        break;
    case 1:
        model_Fisher->load("E:\\性别识别数据库—CAS-PEAL\\面部训练样本\\Fisher_Model.xml");
        break;
    case 2:
        model_LBP->load("E:\\性别识别数据库—CAS-PEAL\\面部训练样本\\LBP_Model.xml");
        break;
    case 3:
        svm.load("E:\\性别识别数据库—CAS-PEAL\\面部训练样本\\SVM_SEX_Model.txt");
        break;
    default:
        break;
    }

  加载完成后,给出提示:

MessageBox("初始化完成");

  这里给出初始化函数的完整代码:

void CGenderRecognitionMFCDlg::OnBnClickedButtonInitial()
{
    m_boolInitOK = true;
    cascade = cvLoadHaarClassifierCascade("D:\\opencv\\sources\\data\\haarcascades\
\\haarcascade_frontalface_alt_tree.xml",cvSize(30,30));
    storage = cvCreateMemStorage(0);

    /**********根据用户的选择来加载分类器**********/
    int index = 0;
    index = ((CComboBox*)GetDlgItem(IDC_COMBO_FUNCTION))->GetCurSel();
    switch (index)
    {
    case 0:
        model_PCA->load("E:\\性别识别数据库—CAS-PEAL\\面部训练样本\\PCA_Model.xml");
        break;
    case 1:
        model_Fisher->load("E:\\性别识别数据库—CAS-PEAL\\面部训练样本\\Fisher_Model.xml");
        break;
    case 2:
        model_LBP->load("E:\\性别识别数据库—CAS-PEAL\\面部训练样本\\LBP_Model.xml");
        break;
    case 3:
        svm.load("E:\\性别识别数据库—CAS-PEAL\\面部训练样本\\SVM_SEX_Model.txt");
        break;
    default:
        break;
    }
    MessageBox("初始化完成");
    // TODO: 在此添加控件通知处理程序代码
}

  3、编写性别识别函数

  将性别识别编写为一个名为GenderRecognition(IplImage* img)的函数,将其作为成员函数添加到CGenderRecognitionMFCDlg类中:

  然后再向CGenderRecognitionMFCDlg类中添加一个int类型的标签,用来保存对当前图片的预测结果(“1”代表男性,“2”代表女性):

  接下来开始编写性别识别函数,与之前加载分类器的流程类似,这里同样需要判断用户所选择的方法的标号,然后调用对应的分类器对输入图片进行预测,不过这里需要先将输入的IplImage类型变量转换为Mat类型变量,代码如下:

    Mat image(img);
    Mat trainImg;
    resize(image,image,Size(92,112));

    /***********根据当前用户选择的方法来使用对应的分类器进行分类**********/
    int index = 0;
    index     = ((CComboBox*)GetDlgItem(IDC_COMBO_FUNCTION))->GetCurSel();
    switch (index)
    {
    case 0:
        {
            m_genderLabel = model_PCA->predict(image);
            break;
        }
    case 1:
        {
            m_genderLabel = model_Fisher->predict(image);
            break;
        }
    case 2:
        {
            m_genderLabel = model_LBP->predict(image);
            break;
        }
    case 3:
        {
            resize(image, trainImg, cv::Size(64,64), 0, 0, INTER_CUBIC);
            HOGDescriptor *hog=new HOGDescriptor(cvSize(64,64),cvSize(16,16),cvSize(8,8),cvSize(8,8), 9);
            vector<float>descriptors;
            hog->compute(trainImg, descriptors,Size(1,1), Size(0,0));
            Mat SVMtrainMat =  Mat::zeros(1,descriptors.size(),CV_32FC1);
            int n=0;
            for(vector<float>::iterator iter=descriptors.begin();iter!=descriptors.end();iter++)
            {
                SVMtrainMat.at<float>(0,n) = *iter;
                n++;
            }
            m_genderLabel = svm.predict(SVMtrainMat);
            break;
        }
    default:
        {
            break;
        }
    }

  这里需要注意的一点就是在使用SVM进行性别识别时,同样需要先提取测试样本的HOG特征,参数设置要与之前训练时的HOG参数设置相同,具体参见:C++开发人脸性别识别教程(6)——通过SVM实现性别识别。同时要将测试样本先归一化到和训练样本相同的尺寸,这里为92*112。

  4、显示识别结果

  我们设计通过一个编辑框控件(Edit Control)来显示当前图片的性别识别结果,即m_genderRecognition为“1”时显示“帅哥”,为“2”时显示“美女”。首先在主界面上绘制这个控件,并将其ID指定为IDC_EDIT_RecognitionResult。

  然后我们在GenderRecognition()函数中添加结果显示代码:

    /**********显示识别结果**********/
    if (1 == m_genderLabel)
    {
        GetDlgItem(IDC_EDIT_RESULT)->SetWindowText("帅哥");
    }
    else if(2 == m_genderLabel)
    {
        GetDlgItem(IDC_EDIT_RESULT)->SetWindowText("美女");
    }

  此时性别识别函数编写完成,这里给出该函数的整体代码:

void CGenderRecognitionMFCDlg::GenderRecognition(IplImage* img)
{
    Mat image(img);
    Mat trainImg;
    resize(image,image,Size(92,112));

    /***********根据当前用户选择的方法来使用对应的分类器进行分类**********/
    int index = 0;
    index     = ((CComboBox*)GetDlgItem(IDC_COMBO_FUNCTION))->GetCurSel();
    switch (index)
    {
    case 0:
        {
            m_genderLabel = model_PCA->predict(image);
            break;
        }
    case 1:
        {
            m_genderLabel = model_Fisher->predict(image);
            break;
        }
    case 2:
        {
            m_genderLabel = model_LBP->predict(image);
            break;
        }
    case 3:
        {
            resize(image, trainImg, cv::Size(64,64), 0, 0, INTER_CUBIC);
            HOGDescriptor *hog=new HOGDescriptor(cvSize(64,64),cvSize(16,16),cvSize(8,8),cvSize(8,8), 9);
            vector<float>descriptors;
            hog->compute(trainImg, descriptors,Size(1,1), Size(0,0));
            Mat SVMtrainMat =  Mat::zeros(1,descriptors.size(),CV_32FC1);
            int n=0;
            for(vector<float>::iterator iter=descriptors.begin();iter!=descriptors.end();iter++)
            {
                SVMtrainMat.at<float>(0,n) = *iter;
                n++;
            }
            m_genderLabel = svm.predict(SVMtrainMat);
            break;
        }
    default:
        {
            break;
        }
    }

    /**********显示识别结果**********/
    if (1 == m_genderLabel)
    {
        GetDlgItem(IDC_EDIT_RESULT)->SetWindowText("帅哥");
    }
    else if(2 == m_genderLabel)
    {
        GetDlgItem(IDC_EDIT_RESULT)->SetWindowText("美女");
    }
}

  四、调用性别识别函数

  编写完性别识别函数之后,我们就可以准备调用这个函数来进行性别识别了,由于程序的设计是先进行人脸检测,然后进行性别识别,因此我们准备在人脸检测函数detect_and_draw()中调用这个性别识别函数。

  1、人脸区域分割

  显然,在进行人脸检测之后,我们需要将检测到的人脸区域分割出来,再送入GenderRecognition()性别识别函数中进行识别,因此我们需要向detect_and_draw()函数中添加人脸区域分割的代码。

  首先,分析一下detect_and_draw(IplImage* img)函数中现有变量的含义:

  IplImage* img:为输入的原始图像,需要在这个原始图像上进行人脸区域分割;

  IplImage* gray:为灰度化的图像,但gray经过了直方图均衡化的操作,导致其丢失了原始的性别信息,因此无法用其进行性别识别,这也就意味着我们需要重新对原始图像img进行灰度化操作,然后进行分割;

  CvRect* rect:保存了人脸检测的结果,需要根据这个矩形的位置和 尺寸来进行人脸区域分割。

  OK,经过以上分析,我们给出人脸区域分割的代码:

    /**********分割人脸区域**********/
    cvSetImageROI(img,*rect);                //设置图像人脸部分ROI区域
    IplImage* faceImage = cvCreateImage(cvSize(rect->width,rect->width),IPL_DEPTH_8U,1);
    if (img->nChannels = 3)
    {
        cvCvtColor(img,faceImage, CV_BGR2GRAY);//将图像灰度化存放在gray中
    }
    else
    {
        faceImage = img;
    }
    cvResetImageROI(img);

    /**********性别识别**********/
    GenderRecognition(faceImage);
    cvReleaseImage(&faceImage);

  这里在进行区域分割时采用了设置ROI区域的方法,这是OpenCv1.x中的方法,在2.x中的Mat类型中封装了更为简洁的方法,详见OpenCV中ROI 总结

  考虑到在进行人脸检测时会出现检测失败的情况,如果我们在人脸检测失败的情况下仍坚持启用人脸分割及性别识别程序,程序就会因为各种变量的未定义而崩溃,因此我们这里选择将这段人脸分割、性别识别的代码放在if语句中,保证其只有在人脸检测成功的情况下才执行,为了方便大家理清逻辑,这里给出detect_and_draw()函数修改后的整体代码:

void CGenderRecognitionMFCDlg::detect_and_draw(IplImage* img)
{
    /**********初始化**********/
    IplImage* gray = cvCreateImage(cvSize(img->width,img->height),8,1);

    /**********灰度化**********/
    if (img->nChannels = 3)
    {
        cvCvtColor(img,gray, CV_BGR2GRAY);//将图像灰度化存放在gray中
    }
    else
    {
        gray = img;
    }

    /**********直方图均衡**********/
    cvEqualizeHist(gray,gray); 

    /**********人脸检测**********/
    cvClearMemStorage(storage);
    CvSeq* objects = cvHaarDetectObjects(gray,//待检测图像
        cascade,                              //分类器标识
        storage,                              //存储检测到的候选矩形
        1.3,                                  //相邻两次检测中窗口扩大的比例
        3,                                    //认为是人脸的最小矩形数(阈值)
        0,                                    //CV_HAAR_DO_CANNY_PRUNING
        cvSize(30,30));                       //初始检测窗口大小

    /**********对检测出的人脸区域面积做比较,选取其中的最大矩形**********/
    int maxface_label = 0;                    //最大面积人脸标签
    Mat max_face = Mat::zeros(objects->elem_size,1,CV_32FC1);                     //候选矩形面积
    for(int i = 0;i< objects->total;i++)
    {
        CvRect* r = (CvRect*)cvGetSeqElem(objects,i);
        max_face.at<float>(i,0) = (float)(r->height * r->width);
        if(i > 0&&max_face.at<float>(i,0) > max_face.at<float>(i - 1,0))
        {
            maxface_label = i;
        }

    }

    /**********绘制检测结果**********/
    if(objects->total > 0)                    //如果人脸检测成功
    {
        CvRect* rect = (CvRect*)cvGetSeqElem(objects,maxface_label);
        cvRectangle(img,cvPoint(rect->x,rect->y),
            cvPoint(rect->x + rect->width,rect->y + rect->height),cvScalar(0.0,255));

      /**********分割人脸区域**********/
      cvSetImageROI(img,*rect);                  //设置图像人脸部分ROI区域
      IplImage* faceImage = cvCreateImage(cvSize(rect->width,rect->width),IPL_DEPTH_8U,1);
      if (img->nChannels = 3)
      {
          cvCvtColor(img,faceImage, CV_BGR2GRAY);//将图像灰度化存放在gray中
      }
      else
      {
          faceImage = img;
      }
      cvResetImageROI(img);

      /**********性别识别**********/
      GenderRecognition(faceImage);
      cvReleaseImage(&faceImage);
    }

    /**********在图像控件上显示图像**********/
    CvvImage cvvImage;
    cvvImage.CopyOf(img);
    cvvImage.DrawToHDC(m_pPicCtlHdc,m_PicCtlRect);
    cvReleaseImage(&gray);
}

  OK,大功告成:

  四、总结

  经过这篇博客之后,可以说我们的性别识别MFC程序已经基本成型,拥有了图片读取与显示,人脸检测、性别识别等基本功能,在接下来的博文中我们将介绍如何进行摄像头视频流的人脸性别识别。不过这里有几个问题需要再次强调一下。

  1、分类器种类

  之前我们说程序中用到了四种性别识别分类器:PCA、Fisher、LBP、SVM。其实这种说法是不严谨的,这里只是有四种API函数,而从分类器层面上将只有两种分类器。前面三个本质上都是用的K近邻分类器,只是提取了三种不同的特征而已。

  2、MFC教程

  在这个程序的开发过程中用到了很多MFC的相关知识,如果大家希望系统了解MFC开发的相关注意事项及技巧的话,推荐大家参考孙鑫老师的MFC视频教程。这个视频教程比较长,大家有选择性的学习即可。

  3、添加初始化完成的提示对话框

  这里我们向“初始化”按钮的响应函数中添加了初始化完成的提示对话框,原因是加载分类器的过程需要大约5秒左右的时间,添加一个完成提示对话框会使得程序显得更有提示性,更友好。

  4、resource.h文件的功能

  resource.h保存了当前资源(各种空间,图片,字符串)的ID号,必要时大家可以从这个文件中查找:

  5、全局变量

  程序中不推荐使用静态的全局变量,会降低程序的安全性。

时间: 2024-12-23 03:35:38

C++开发人脸性别识别教程(12)——添加性别识别功能的相关文章

C++开发人脸性别识别教程(5)——通过FaceRecognizer类实现性别识别

在之前的博客中已经解决了人脸检测的问题,我们计划在这篇博客中介绍人脸识别.性别识别方面的相关实现方法. 其实性别识别和人脸识别本质上是相似的,因为这里只是一个简单的MFC开发,主要工作并不在算法研究上,因此我们直接将性别识别视为一种特殊的人脸识别模式.人脸识别可能需要分为几十甚至上百个类(因为有几十甚至上百个人),而性别识别则是一种特殊的人脸识别——只有两个类. 一.基本工具 通过OpenCv进行性别识别的基本工具是FaceRecognizer.这是OpenCv2.x版本中的一个基本的人脸识别类

C++开发人脸性别识别教程(13)——针对单张图片的性别识别

在之前的博文中我们的性别识别程序已经初步成型,能够识别某个文件夹下的图片文件.不过这里有一个问题,假设这个文件夹下有着大量的图片,而我们希望识别这些图片中的某一张,此时需要我们不停的单击“下一张”按钮才会轮询到对应的图片,这是相当麻烦的,因此在这篇博客中我们向程序中添加一个功能——单张图片的性别识别. 一.基本思想 最基本的办法就是在主界面再添加一个按钮控件,命名为“图片文件”(之前的按钮为“图片文件夹”),不过这样会使得界面上的按钮控件过于繁多,给人一种“作者只会用button控件”的感觉.这

C++开发人脸性别识别教程(18)——辅助功能之文件名批量修改、方法验证

时光推移了30多天,这个人脸性别识别的小项目也接近尾声了,预计再通过三篇博文的篇幅来完成这个项目的收尾工作.在这篇博文中我们再为程序添加另外两个小的辅助功能:文件名批量修改.方法验证. 一 文件名批量修改 批量修改文件名是一件很基础也很常用的小操作,核心操作就是图像文件的批量读取.批量改名.批量保存.基本思想就是把文件读出来,然后在保存回去(注意不要和别的文件发生覆盖),从这个角度来讲文件名批量修改与上一篇博客C++开发人脸性别识别教程(17)——辅助功能之人脸批量分割中的人脸批量分割简直如出一

(转)C++开发人脸性别识别教程(6)——通过SVM实现性别识别

原文地址:http://blog.csdn.net/u013088062/article/details/50480518 上一篇教程中我们介绍了如何使用OpenCv封装的FaceRecognizer类实现简单的人脸性别识别,这里我们为大家提供另外一种基本的性别识别手段——支持向量机(SVM). 支持向量机在解决二分类问题方面有着强大的威力(当然也可以解决多分类问题),性别识别是典型的二分类模式识别问题,因此很适合用SVM进行处理,同时OpenCv又对SVM进行了很好的封装,调用非常方便,因此我

C++开发人脸性别识别教程(6)——通过SVM实现性别识别

上一篇教程中我们介绍了怎样使用OpenCv封装的FaceRecognizer类实现简单的人脸性别识别,这里我们为大家提供第二种主要的性别识别手段--支持向量机(SVM). 支持向量机在解决二分类问题方面有着强大的威力(当然也能够解决多分类问题).性别识别是典型的二分类模式识别问题,因此非常适合用SVM进行处理,同一时候OpenCv又对SVM进行了非常好的封装,调用非常方便,因此我们在这个性别识别程序中考虑增加SVM方法. 在这里我们採用了HOG+SVM的模式来进行,即先提取图像的HOG特征.然后

C++开发人脸性别识别教程(7)——搭建MFC框架之界面绘制

在之前的博客中我们已经将项目中用到的算法表述完毕,包括人脸检测算法以及四种性别识别算法,在这篇博客中我们将着手搭建基本的MFC框架. 一.框架概况 在这篇博文中我们将搭建最基本的MFC框架,绘制MFC界面. 二.搭建流程 1.新建一个MFC工程并配置OpenCv 打开VS,按下“ctrl+n”,在新建窗口中选择“MFC应用程序”,命名为GenderRecognitionMFC: 单击确定,程序类型选择“基于对话框”,MFC使用选择“在静态库中使用MFC”: 直接单击“完成”,创建完毕.OpenC

C++开发人脸性别识别总结

历时一个月,终于在昨天把<C++开发人脸性别识别总结>系列博客完成了,第一篇博客发表在2015年12月29日,截止昨天2016年2月29日最后一篇完成,去除中间一个月的寒假,正好一个月,首先这里把这系列博客的地址呈上:C++开发人脸性别识别教程. 在发牢骚之前首先强调几个重要问题: (1)在程序中存在一个小的易触发的BUG,就是”初始化“按钮对应的事件触发函数OnBnClickedButtonInitial()中有一句这样的代码: 在实际编程过程中经常需要对代码进行规范化,如果你习惯通过“ct

基于安卓高仿how-old.net应用实现人脸识别估算年龄与性别

前几段微软推出的大数据人脸识别年龄应用how-old.net在微博火了一把,它可以通过照片快速获得照片上人物的年龄,系统会对瞳孔.眼角.鼻子等27个“面部地标点"展开分析,进而得出你的“颜龄". 来看下关于这款应用的截图:   昨晚闲着没事,在网上查阅了点资料仿写了一款类似功能的APP,看下截图: 关于人脸识别技术本想去使用微软给开发人员提供的SDK,但由于天朝巨坑的网络,我连How-old.net官网都登不上,只能绕道去找找其他地方有没类似功能的SDK.后来想起之前在搞O2O的时候,

Unix/Linux环境C编程入门教程(12) openSUSECCPP以及Linux内核驱动开发环境搭建

1. openSUSE是一款优秀的linux. 2.选择默认虚拟机 3.选择稍后安装操作系统 4.选择linux  opensuse 5. 选择默认虚拟机名称 6.设置处理器为双核. 7.内存设置为2G 8. 选择网络地址转换 9.设置IO控制器 10. 选择默认磁盘类型 11.创建一个新的虚拟磁盘 12.设置磁盘大小 13.选择路径保存虚拟磁盘 14. 完成虚拟机创建 15.设置虚拟机 16.选择opensuse镜像 17.开启虚拟机 18.虚拟机启动 19.安装opensuse 20.安装程