在之前的博文中我们已经能够顺利驱动摄像头来采集源图像,在这篇博文中将正式为其加入性别识别的代码,实现摄像头视频的人脸性别识别。
一、人脸检测
在得到摄像头采集的源图像之后,首先要做的就是对其进行人脸检测,将人脸区域分割出来。这步相对来说比较简单,只需在定时器时间触发函数中加入人脸检测的代码即可,这里给出OnTimer()函数的整体代码:
void CGenderRecognitionMFCDlg::OnTimer(UINT_PTR nIDEvent) { /***********人脸检测并识别**********/ m_pVideoInfo->m_pFrameImage = cvQueryFrame(m_pVideoInfo->m_pCapture);//得到视频流中的下一帧 IplImage* IplImg; IplImg = m_pVideoInfo->m_pFrameImage; detect_and_draw(IplImg); /***********显示图像**********/ CvvImage cvvImage; cvvImage.CopyOf(IplImg); cvvImage.DrawToHDC(m_pPicCtlHdc,m_PicCtlRect); CDialogEx::OnTimer(nIDEvent); }
注意这里相对于上一篇博客中的OnTimer()函数有一些改动,主要体现在两个方面:一是添加了detect_and_draw(IplImg)人脸检测与性别识别操作(detect_and_draw()函数内部默认调用了性别识别函数GenderRecognition()),完成性别识别操作;二是在通过CvvImage类进行图片的显示时,推荐显示人脸检测之后的图像(这里的变量IplImg ),这样在显示结果中就能够将人脸检测过程中所画的人脸框一并显示出来,显得更为形象。
OK,此时的程序已经具备了基本的摄像头视频性别识别功能,F5运行,初始化,打开视频,程序正常工作。
二、性别识别函数改进
虽然此时程序能够正常运行,但在运行过程中会发现程序的识别结果有时(甚至大部分时候)会不太稳定,即不断的在“帅哥”和“美女”之间变来变去,这直接说明了我们所采用的识别算法的鲁棒性很不好,不过也情有可原,毕竟这里只是使用了OpenCv提供的最基本的人脸检测方法和人脸识别方法,但这里我仍然希望在算法受限的条件下对其鲁棒性进行一下改进,这就用到了视频识别中常用的手段——多帧联合。
所谓多帧联合,就是对多帧图像进行识别分析,得到多个识别结果,然后在这个基础上通过加权融合,给出最后的识别结果。理论上多帧联合识别的手段能够排除某些突发的、极端错误的干扰,给出“整体正确”的判别结果,我们这里就采用这个手段对算法的鲁棒性稍微做一点点改进。
2.1 确定识别帧数
要进行多帧联合识别,首先要确定一次处理多少帧,这里我们可以让用户来进行选择。与之前分类器的选择方法类似,我们在这里同样提供一个Combo-box控件,供用户在其列表中选择联合识别的帧数。
仿照之前下拉选择列表(Combo Box)控件的添加方法,这里再次添加一个同样类型的控件,将ID更改为:IDC_COMBO_NUM:
对应的,在CMFCShowVideoTestDlg类的OnInitDialog()函数中对其进行初始化,确定显示内容以及默认选项:
((CComboBox*)GetDlgItem(IDC_COMBO_NUM))->AddString("1"); ((CComboBox*)GetDlgItem(IDC_COMBO_NUM))->AddString("3"); ((CComboBox*)GetDlgItem(IDC_COMBO_NUM))->AddString("5"); ((CComboBox*)GetDlgItem(IDC_COMBO_NUM))->AddString("7"); ((CComboBox*)GetDlgItem(IDC_COMBO_NUM))->AddString("9"); ((CComboBox*)GetDlgItem(IDC_COMBO_NUM))->AddString("11"); ((CComboBox*)GetDlgItem(IDC_COMBO_NUM))->AddString("13"); ((CComboBox*)GetDlgItem(IDC_COMBO_NUM))->AddString("15"); ((CComboBox*)GetDlgItem(IDC_COMBO_NUM))->SetCurSel(4);
由于这里是二分类问题,因此联合帧数必须是奇数。这里同样需要提前指定Combo Box的下拉范围,以保证选项能够正常显示,同时将Combo Box控件的”sort“属性,应该置为”false“,此时运行程序,控件正常工作:
既然用户指定了联合识别的帧数,在进行视频性别识别之前,就需要先读取用户选择的帧数,与之前的分类器识别相似,需要在性别识别函数GenderRecognition()内部,通过switch语句来读取对应的选项值:
/***********根据当前用户选择的方法来确定联合识别的帧数**********/ int iFrameNum = 0; index = ((CComboBox*)GetDlgItem(IDC_COMBO_NUM))->GetCurSel(); switch (index) { case 0: { iFrameNum = 1; break; } case 1: { iFrameNum = 3; break; } case 2: { iFrameNum = 5; break; } case 3: { iFrameNum = 7; break; } case 4: { iFrameNum = 9; break; } default: { break; } }
2.2 标记工作模式
在采用了帧联合判别的视频识别模式之后,导致了程序对单张图片和视频的识别的处理方式是不同的:单张图片意味着只有一帧图像,无法进行帧联合判决,只能一次给出结果;而在处理视频时则需要进行帧联合判决。因此需要设置一个标志位来告诉程序当前的工作模式是单张图片还是视频流。向CGenderRecognitionMFCDlg类中添加一个布尔型变量m_boolModelFlag指示当前的工作模式,ture代表单张图片,False代表视频流:
在“图像文件夹”按钮对应的处理函数OnBnClickedButtonImagefile()中,将该标志位置为真:
同样,在“打开视频”按钮对应的事件处理函数OnBnClickedButton1Video()中,将该标志位置为False。
2.3 融合识别结果
接下来继续改造性别识别函数GenderRecognition()。这里首先需要向CGenderRecognitionMFCDlg类中添加两个整型变量m_ManNum和m_WomenNum,分别保存每一帧的识别结果,在添加一个整型变量m_FrameNum来保存当前已识别的帧数。然后就可以进行多帧联合判别了,这里先给出整体代码,稍后解释:
if (m_boolModelFlag == TRUE) //若当前为图像模式 { if (1 == m_genderLabel) { GetDlgItem(IDC_EDIT_RESULT)->SetWindowText("帅哥"); } else if(2 == m_genderLabel) { GetDlgItem(IDC_EDIT_RESULT)->SetWindowText("美女"); } } else //若当前为视频模式 { if (1 == m_genderLabel) //若当前帧识别结果为男性 { m_ManNum = m_ManNum + 1; } else if(2 == m_genderLabel) //若当前帧识别结果为女性 { m_WomenNum = m_WomenNum + 1; } m_FrameNum = m_FrameNum + 1; if (m_FrameNum = iFrameNum) //达到指定识别帧数 { if (m_ManNum > m_WomenNum) { GetDlgItem(IDC_EDIT_RESULT)->SetWindowText("帅哥"); } else { GetDlgItem(IDC_EDIT_RESULT)->SetWindowText("美女"); } m_ManNum = 0; //各个计数器清零 m_WomenNum = 0; m_FrameNum = 0; } }
这段代码看似繁琐,其实很容易理解,目的就是对各个帧的识别结果进行累计,在达到指定联合帧数之后根据之前的奇数帧中所含男性识别结果和女性识别结果的数量来给出最终的判断,类似于一种投票表决的工作方式。
由于这里我们对性别识别函数做了较大的改动,这里给出目前GenderRecognition()函数的完整代码:
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; } } /***********根据当前用户选择的方法来确定联合识别的帧数**********/ int iFrameNum = 0; index = ((CComboBox*)GetDlgItem(IDC_COMBO_NUM))->GetCurSel(); switch (index) { case 0: { iFrameNum = 1; break; } case 1: { iFrameNum = 3; break; } case 2: { iFrameNum = 5; break; } case 3: { iFrameNum = 7; break; } case 4: { iFrameNum = 9; break; } default: { break; } } /**********显示识别结果**********/ if (m_boolModelFlag == TRUE) //若当前为图像模式 { if (1 == m_genderLabel) { GetDlgItem(IDC_EDIT_RESULT)->SetWindowText("帅哥"); } else if(2 == m_genderLabel) { GetDlgItem(IDC_EDIT_RESULT)->SetWindowText("美女"); } } else //若当前为视频模式 { if (1 == m_genderLabel) //若当前帧识别结果为男性 { m_ManNum = m_ManNum + 1; } else if(2 == m_genderLabel) //若当前帧识别结果为男性 { m_WomenNum = m_WomenNum + 1; } m_FrameNum = m_FrameNum + 1; if (m_FrameNum == iFrameNum) //达到指定识别帧数 { if (m_ManNum > m_WomenNum) { GetDlgItem(IDC_EDIT_RESULT)->SetWindowText("帅哥"); } else { GetDlgItem(IDC_EDIT_RESULT)->SetWindowText("美女"); } m_ManNum = 0; //各个计数器清零 m_WomenNum = 0; m_FrameNum = 0; } } }
OK,此时在此运行程序,发现结果显示比之前要稳定许多,说明我们的改进是有效果的。
三、注意事项
1、人脸检测的准确度,性别识别的稳定性
在对视频图像进行识别的过程中,充分暴露了我们所采用算法的不可靠性,即结果变来变去的,因此如果需要真正开发性别识别的相关应用时,还是要寻求更准确,鲁棒性更高的算法。
2、性别识别的速度,图像显示的流畅性问题
这里由于人脸检检测、性别识别的过程存在较大消耗,因此会使得在显示过程中视频出现一定程度的卡顿现象。
3、变量的默认初始化
在向类中添加成员变量的时候一定要注意及时在构造函数中对成员变量进行初始化操作,这里VS的MFC类向导会默认帮助我们完成一些(但不是全部)初始化操作,方便许多。
4、图片较少,不愿爆照
这篇博客中我没怎么粘贴程序的运行效果图,主要是因为在下相貌不够可人,不好意思爆照,不过代码都没有问题,亲测可用。