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

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

  一、分类器训练

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

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

  二、加入下拉列表控件

  1、绘制控件

  因为这里有四种性别识别的方法,因此在程序执行时,须要用户指定一种性别识别的方法,这里提供一个下拉选择列表(Combo Box)控件来供用户选择。

首先从工具箱中选中该控件,在MFC主窗体的合适位置进行绘制。并将ID更改为IDC_COMBO_FUNCTION:

  2、指定选项值

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

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

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

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

  (1)须要提前指定Combo Box的下拉范围。这样才干保证在单击下拉button时控件能够将所有选项所有显示出来:

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

  三、加入性别识别算法

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

  1、全局变量声明

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

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

  2、在”初始化“button中载入分类器

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

  1. /**********依据用户的选择来载入分类器**********/
  2. int index = 0;
  3. index = ((CComboBox*)GetDlgItem(IDC_COMBO_FUNCTION))->GetCurSel();
  4. switch (index)
  5. {
  6. case 0:
  7. model_PCA->load("E:\\性别识别数据库—CAS-PEAL\\面部训练样本\\PCA_Model.xml");
  8. break;
  9. case 1:
  10. model_Fisher->load("E:\\性别识别数据库—CAS-PEAL\\面部训练样本\\Fisher_Model.xml");
  11. break;
  12. case 2:
  13. model_LBP->load("E:\\性别识别数据库—CAS-PEAL\\面部训练样本\\LBP_Model.xml");
  14. break;
  15. case 3:
  16. svm.load("E:\\性别识别数据库—CAS-PEAL\\面部训练样本\\SVM_SEX_Model.txt");
  17. break;
  18. default:
  19. break;
  20. }

  载入完毕后,给出提示:

  1. MessageBox("初始化完毕");

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

  1. void CGenderRecognitionMFCDlg::OnBnClickedButtonInitial()
  2. {
  3. m_boolInitOK = true;
  4. cascade = cvLoadHaarClassifierCascade("D:\\opencv\\sources\\data\\haarcascades\
  5. \\haarcascade_frontalface_alt_tree.xml",cvSize(30,30));
  6. storage = cvCreateMemStorage(0);
  7.  
  8. /**********依据用户的选择来载入分类器**********/
  9. int index = 0;
  10. index = ((CComboBox*)GetDlgItem(IDC_COMBO_FUNCTION))->GetCurSel();
  11. switch (index)
  12. {
  13. case 0:
  14. model_PCA->load("E:\\性别识别数据库—CAS-PEAL\\面部训练样本\\PCA_Model.xml");
  15. break;
  16. case 1:
  17. model_Fisher->load("E:\\性别识别数据库—CAS-PEAL\\面部训练样本\\Fisher_Model.xml");
  18. break;
  19. case 2:
  20. model_LBP->load("E:\\性别识别数据库—CAS-PEAL\\面部训练样本\\LBP_Model.xml");
  21. break;
  22. case 3:
  23. svm.load("E:\\性别识别数据库—CAS-PEAL\\面部训练样本\\SVM_SEX_Model.txt");
  24. break;
  25. default:
  26. break;
  27. }
  28. MessageBox("初始化完毕");
  29. // TODO: 在此加入控件通知处理程序代码
  30. }

  3、编写性别识别函数

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

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

  接下来開始编写性别识别函数,与之前载入分类器的流程相似,这里相同须要推断用户所选择的方法的标号,然后调用相应的分类器对输入图片进行预測,只是这里须要先将输入的IplImage类型变量转换为Mat类型变量,代码例如以下:

  1. Mat image(img);
  2. Mat trainImg;
  3. resize(image,image,Size(92,112));
  4.  
  5. /***********依据当前用户选择的方法来使用相应的分类器进行分类**********/
  6. int index = 0;
  7. index = ((CComboBox*)GetDlgItem(IDC_COMBO_FUNCTION))->GetCurSel();
  8. switch (index)
  9. {
  10. case 0:
  11. {
  12. m_genderLabel = model_PCA->predict(image);
  13. break;
  14. }
  15. case 1:
  16. {
  17. m_genderLabel = model_Fisher->predict(image);
  18. break;
  19. }
  20. case 2:
  21. {
  22. m_genderLabel = model_LBP->predict(image);
  23. break;
  24. }
  25. case 3:
  26. {
  27. resize(image, trainImg, cv::Size(64,64), 0, 0, INTER_CUBIC);
  28. HOGDescriptor *hog=new HOGDescriptor(cvSize(64,64),cvSize(16,16),cvSize(8,8),cvSize(8,8), 9);
  29. vector<float>descriptors;
  30. hog->compute(trainImg, descriptors,Size(1,1), Size(0,0));
  31. Mat SVMtrainMat = Mat::zeros(1,descriptors.size(),CV_32FC1);
  32. int n=0;
  33. for(vector<float>::iterator iter=descriptors.begin();iter!=descriptors.end();iter++)
  34. {
  35. SVMtrainMat.at<float>(0,n) = *iter;
  36. n++;
  37. }
  38. m_genderLabel = svm.predict(SVMtrainMat);
  39. break;
  40. }
  41. default:
  42. {
  43. break;
  44. }
  45. }

  这里须要注意的一点就是在使用SVM进行性别识别时,相同须要先提取測试样本的HOG特征。參数设置要与之前训练时的HOG參数设置相同,详细參见:C++开发人脸性别识别教程(6)——通过SVM实现性别识别

同一时候要将測试样本先归一化到和训练样本相同的尺寸,这里为92*112。

  4、显示识别结果

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

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

  1. /**********显示识别结果**********/
  2. if (1 == m_genderLabel)
  3. {
  4. GetDlgItem(IDC_EDIT_RESULT)->SetWindowText("帅哥");
  5. }
  6. else if(2 == m_genderLabel)
  7. {
  8. GetDlgItem(IDC_EDIT_RESULT)->SetWindowText("美女");
  9. }

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

  1. void CGenderRecognitionMFCDlg::GenderRecognition(IplImage* img)
  2. {
  3. Mat image(img);
  4. Mat trainImg;
  5. resize(image,image,Size(92,112));
  6.  
  7. /***********依据当前用户选择的方法来使用相应的分类器进行分类**********/
  8. int index = 0;
  9. index = ((CComboBox*)GetDlgItem(IDC_COMBO_FUNCTION))->GetCurSel();
  10. switch (index)
  11. {
  12. case 0:
  13. {
  14. m_genderLabel = model_PCA->predict(image);
  15. break;
  16. }
  17. case 1:
  18. {
  19. m_genderLabel = model_Fisher->predict(image);
  20. break;
  21. }
  22. case 2:
  23. {
  24. m_genderLabel = model_LBP->predict(image);
  25. break;
  26. }
  27. case 3:
  28. {
  29. resize(image, trainImg, cv::Size(64,64), 0, 0, INTER_CUBIC);
  30. HOGDescriptor *hog=new HOGDescriptor(cvSize(64,64),cvSize(16,16),cvSize(8,8),cvSize(8,8), 9);
  31. vector<float>descriptors;
  32. hog->compute(trainImg, descriptors,Size(1,1), Size(0,0));
  33. Mat SVMtrainMat = Mat::zeros(1,descriptors.size(),CV_32FC1);
  34. int n=0;
  35. for(vector<float>::iterator iter=descriptors.begin();iter!=descriptors.end();iter++)
  36. {
  37. SVMtrainMat.at<float>(0,n) = *iter;
  38. n++;
  39. }
  40. m_genderLabel = svm.predict(SVMtrainMat);
  41. break;
  42. }
  43. default:
  44. {
  45. break;
  46. }
  47. }
  48.  
  49. /**********显示识别结果**********/
  50. if (1 == m_genderLabel)
  51. {
  52. GetDlgItem(IDC_EDIT_RESULT)->SetWindowText("帅哥");
  53. }
  54. else if(2 == m_genderLabel)
  55. {
  56. GetDlgItem(IDC_EDIT_RESULT)->SetWindowText("美女");
  57. }
  58. }

  四、调用性别识别函数

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

  1、人脸区域切割

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

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

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

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

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

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

  1. /**********切割人脸区域**********/
  2. cvSetImageROI(img,*rect); //设置图像人脸部分ROI区域
  3. IplImage* faceImage = cvCreateImage(cvSize(rect->width,rect->width),IPL_DEPTH_8U,1);
  4. if (img->nChannels = 3)
  5. {
  6. cvCvtColor(img,faceImage, CV_BGR2GRAY);//将图像灰度化存放在gray中
  7. }
  8. else
  9. {
  10. faceImage = img;
  11. }
  12. cvResetImageROI(img);
  13.  
  14. /**********性别识别**********/
  15. GenderRecognition(faceImage);
  16. cvReleaseImage(&faceImage);

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

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

  1. void CGenderRecognitionMFCDlg::detect_and_draw(IplImage* img)
  2. {
  3. /**********初始化**********/
  4. IplImage* gray = cvCreateImage(cvSize(img->width,img->height),8,1);
  5.  
  6. /**********灰度化**********/
  7. if (img->nChannels = 3)
  8. {
  9. cvCvtColor(img,gray, CV_BGR2GRAY);//将图像灰度化存放在gray中
  10. }
  11. else
  12. {
  13. gray = img;
  14. }
  15.  
  16. /**********直方图均衡**********/
  17. cvEqualizeHist(gray,gray);
  18.  
  19. /**********人脸检測**********/
  20. cvClearMemStorage(storage);
  21. CvSeq* objects = cvHaarDetectObjects(gray,//待检測图像
  22. cascade, //分类器标识
  23. storage, //存储检測到的候选矩形
  24. 1.3, //相邻两次检測中窗体扩大的比例
  25. 3, //觉得是人脸的最小矩形数(阈值)
  26. 0, //CV_HAAR_DO_CANNY_PRUNING
  27. cvSize(30,30)); //初始检測窗体大小
  28.  
  29. /**********对检測出的人脸区域面积做比較,选取当中的最大矩形**********/
  30. int maxface_label = 0; //最大面积人脸标签
  31. Mat max_face = Mat::zeros(objects->elem_size,1,CV_32FC1); //候选矩形面积
  32. for(int i = 0;i< objects->total;i++)
  33. {
  34. CvRect* r = (CvRect*)cvGetSeqElem(objects,i);
  35. max_face.at<float>(i,0) = (float)(r->height * r->width);
  36. if(i > 0&&max_face.at<float>(i,0) > max_face.at<float>(i - 1,0))
  37. {
  38. maxface_label = i;
  39. }
  40.  
  41. }
  42.  
  43. /**********绘制检測结果**********/
  44. if(objects->total > 0) //假设人脸检測成功
  45. {
  46. CvRect* rect = (CvRect*)cvGetSeqElem(objects,maxface_label);
  47. cvRectangle(img,cvPoint(rect->x,rect->y),
  48. cvPoint(rect->x + rect->width,rect->y + rect->height),cvScalar(0.0,255));
  49.  
  50.   /**********切割人脸区域**********/
  51.   cvSetImageROI(img,*rect); //设置图像人脸部分ROI区域
  52.   IplImage* faceImage = cvCreateImage(cvSize(rect->width,rect->width),IPL_DEPTH_8U,1);
  53.   if (img->nChannels = 3)
  54.   {
  55.    cvCvtColor(img,faceImage, CV_BGR2GRAY);//将图像灰度化存放在gray中
  56.   }
  57.   else
  58.   {
  59.    faceImage = img;
  60.    }
  61.   cvResetImageROI(img);
  62.  
  63.    /**********性别识别**********/
  64.    GenderRecognition(faceImage);
  65.    cvReleaseImage(&faceImage);
  66. }
  67.  
  68. /**********在图像控件上显示图像**********/
  69. CvvImage cvvImage;
  70. cvvImage.CopyOf(img);
  71. cvvImage.DrawToHDC(m_pPicCtlHdc,m_PicCtlRect);
  72. cvReleaseImage(&gray);
  73. }

  OK,大功告成:

  四、总结

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

  1、分类器种类

  之前我们说程序中用到了四种性别识别分类器:PCA、Fisher、LBP、SVM。事实上这样的说法是不严谨的,这里仅仅是有四种API函数,而从分类器层面上将仅仅有两种分类器。

前面三个本质上都是用的K近邻分类器,仅仅是提取了三种不同的特征而已。

  2、MFC教程

  在这个程序的开发过程中用到了非常多MFC的相关知识,假设大家希望系统了解MFC开发的相关注意事项及技巧的话。推荐大家參考孙鑫老师的MFC视频教程。

这个视频教程比較长。大家有选择性的学习就可以。

  3、加入初始化完毕的提示对话框

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

  4、resource.h文件的功能

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

  5、全局变量

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

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

  1. 【GStreamer开发】GStreamer基础教程12——流

    目标 直接播放Internet上的文件而不在本地保存就被称为流播放.我们在前面教程里已经这样做过了,使用了http://的URL.本教程展示的是在播放流的时候需要记住的几个点,特别是: 如何设置缓冲 ...

  2. C++开发人脸性别识别教程(19)——界面美化

    在这篇博文中将完毕<C++开发人脸性别识别>的收尾工作.主要内容分为两部分:加入视频暂定功能.界面规范化. 一 视频暂停功能 严格来说这个视频暂定功能算是视频人脸性别识别的一个遗留问题,本 ...

  3. C++开发人脸性别识别教程(10)——加入图片的人脸检測程序

    现在我们的MFC框架已经初具规模,能够读取并显示目录下的图片.在这篇博文中我们将向当中加入人脸检測的程序. 一.人脸检測算法 这里我们使用OpenCv封装的Adaboost方法来进行人脸检測,參见:C ...

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

    在之前的博客中已经攻克了人脸检測的问题,我们计划在这篇博客中介绍人脸识别.性别识别方面的相关实现方法. 事实上性别识别和人脸识别本质上是相似的,由于这里仅仅是一个简单的MFC开发,主要工作并不在算法研 ...

  5. C++开发人脸性别识别教程(16)——视频人脸性别识别

    在之前的博文中我们已经可以顺利驱动摄像头来採集源图像.在这篇博文中将正式为其加入性别识别的代码,实现摄像头视频的人脸性别识别. 一.人脸检測 在得到摄像头採集的源图像之后,首先要做的就是对其进行人脸检 ...

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

    历时一个月,最终在昨天把<C++开发人脸性别识别总结>系列博客完毕了,第一篇博客发表在2015年12月29日,截止昨天2016年2月29日最后一篇完毕,去除中间一个月的寒假,正好一个月,首 ...

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

    前几段微软推出的大数据人脸识别年龄应用how-old.net在微博火了一把,它可以通过照片快速获得照片上人物的年龄,系统会对瞳孔.眼角.鼻子等27个“面部地标点"展开分析,进而得出你的“颜龄 ...

  8. Nodejs开发人脸识别系统-教你实现高大上的人工智能

    Nodejs开发人脸识别系统-教你实现高大上的人工智能   一.缘起缘生 前段时间有个H5很火,上传个头像就可以显示自己穿军装的样子,无意中看到了一篇帖子叫 全民刷军装背后的AI技术及简单实现 ,里面 ...

  9. 基于人脸识别+IMDB-WIFI+Caffe的性别识别

    本文用记录基于Caffe的人脸性别识别过程.基于imdb-wiki模型做finetune,imdb-wiki数据集合模型可从这里下载:https://data.vision.ee.ethz.ch/cv ...

随机推荐

  1. lodop多打印一页白纸

    [错误还原]Lodop多张空白页测试2 [错误还原]Lodop多出空白页测试 http://blog.sina.com.cn/s/blog_157ebf1370102wta1.html 上面这个链接是 ...

  2. 利用python+tkinter开发一个点名软件

    最近上课学生多名字记不住,名册忘记了带,要点名怎么办,好久没有写代码了,于是自己写了个点名软件,记录下吧,第一次接触TK也不是太熟悉,写的不太好,记录下源代码 以后遇到要写桌面软件还是可以耍耍的. t ...

  3. WebService开发-CXF

    Web Service 开发方式 Apache CXF 一.关于Apache CXF 在网址http://cxf.apache.org/可以查看到关于Apache CXF的下载及文档介绍,这里不再多做 ...

  4. A - Supermarket

    Problem description We often go to supermarkets to buy some fruits or vegetables, and on the tag the ...

  5. ES6 Template String 模板字符串

    模板字符串(Template String)是增强版的字符串,用反引号(`)标识,它可以当作普通字符串使用,也可以用来定义多行字符串,或者在字符串中嵌入变量. 大家可以先看下面一段代码: $(&quo ...

  6. python--1、入门

    python的创始人为吉多·范罗苏姆(Guido van Rossum). python在2017年统计的所有语言排名中处于第四名,稳步上升状态. python应用领域: WEB开发(Django框架 ...

  7. 编码的来历和使用 utf-8 和GB2312比较

    经常我们打开外国网站的时候出现乱码,又或者打开很多非英语的外国网站的时候,显示的都是口口口口口的字符, wordpress程序是用的UTF-8,很多cms用的是GB2312. ● 为什么有这么多编码? ...

  8. 【JSP】中文乱码问题

     原作者http://www.cnblogs.com/xing901022/p/4354529.html 阅读目录 之前总是碰到JSP页面乱码的问题,每次都是现在网上搜,然后胡乱改,改完也不明白原因. ...

  9. (转)基于MVC4+EasyUI的Web开发框架经验总结(12)--利用Jquery处理数据交互的几种方式

    http://www.cnblogs.com/wuhuacong/p/4085682.html 在基于MVC4+EasyUI的Web开发框架里面,大量采用了Jquery的方法,对数据进行请求或者提交, ...

  10. Apex语言(五)循环结构

    1.循环结构 循环语句允许我们多次执行一个语句或一组语句(重执行语句).  2.while语句 只要给定条件为真,目标语句就会重复执行. [格式] while (循环条件){ 语句; } [流程图] ...