在之前的博文中我们已经可以顺利驱动摄像头来採集源图像。在这篇博文中将正式为其加入性别识别的代码,实现摄像头视频的人脸性别识别。
一、人脸检測
在得到摄像头採集的源图像之后,首先要做的就是对其进行人脸检測,将人脸区域切割出来。这步相对来说比較简单。仅仅需在定时器时间触发函数中加入人脸检測的代码就可以。这里给出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代表视频流:
在“图像目录”button相应的处理函数OnBnClickedButtonImagefile()中,将该标志位置为真:
相同。在“打开视频”button相应的事件处理函数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; } }
这段代码看似繁琐。事实上非常easy理解,目的就是对各个帧的识别结果进行累计。在达到指定联合帧数之后依据之前的奇数帧中所含男性识别结果和女性识别结果的数量来给出终于的推断,相似于一种投票表决的工作方式。
因为这里我们对性别识别函数做了较大的修改。这里给出眼下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、图片较少。不愿爆照
这篇博客中我没怎么粘贴程序的执行效果图,主要是因为在下相貌不够可人,不好意思爆照。只是代码都没有问题。亲測可用。