时光推移了30多天,这个人脸性别识别的小项目也接近尾声了,预计再通过三篇博文的篇幅来完成这个项目的收尾工作。在这篇博文中我们再为程序添加另外两个小的辅助功能:文件名批量修改、方法验证。
一 文件名批量修改
批量修改文件名是一件很基础也很常用的小操作,核心操作就是图像文件的批量读取、批量改名、批量保存。基本思想就是把文件读出来,然后在保存回去(注意不要和别的文件发生覆盖),从这个角度来讲文件名批量修改与上一篇博客C++开发人脸性别识别教程(17)——辅助功能之人脸批量分割中的人脸批量分割简直如出一辙,这里就不再赘述,大家请自行添加控件按钮、编写相关函数吧:
二 方法验证
这个小功能是当初为了测试四种分类方法(PCA、Fisher、LBP、HOG+SVM)的分类性能而额外添加的一段代码,目的很简单,就是想看看四种方法分类时哪个方法更准确一点。
2.1 添加控件
首先,添加一个按钮控件,命名为“方法验证”,ID不用变:
然后在添加两个编辑框控件,用来显示各个方法的分类正确率,ID分别更改为IDC_Effect_Man和IDC_Effect_Wom:
2.2 批量读取测试样本
双击“方法验证”按钮,添加对应的事件处理函数OnBnClickedButton3(),首先,读取测试样本所在的文件夹:
CString str; //存储图像路径 BROWSEINFO bi; //用来存储用户选中的目录信息 TCHAR name[MAX_PATH]; //存储路径 char imageFullName[500]; //存储单个图像文件的全路径 name[0]='d'; ZeroMemory(&bi,sizeof(BROWSEINFO)); //清空目录对应的内存 bi.hwndOwner=GetSafeHwnd(); //得到窗口句柄 bi.pszDisplayName=name; BIF_BROWSEINCLUDEFILES; bi.lpszTitle=_T("Select folder"); //对话框标题 bi.ulFlags=0x80;//设置对话框形式 LPITEMIDLIST idl=SHBrowseForFolder(&bi);//返回所选中文件夹的ID if(idl==NULL) return; SHGetPathFromIDList(idl,str.GetBuffer(MAX_PATH));//将文件信息格式化存储到对应缓冲区中 str.ReleaseBuffer(); //与GerBuffer配合使用,清空内存 m_Path=str; //将路径存储在m_path中 if(str.GetAt(str.GetLength()-1)!='\\') m_Path+="\\"; UpdateData(FALSE); // free memory used IMalloc * imalloc = 0; if (SUCCEEDED(SHGetMalloc(&imalloc))) { imalloc->Free (idl); imalloc->Release(); } m_ImageDir=(LPSTR)(LPCTSTR)m_Path; m_pDir = opendir(m_ImageDir); //获取该路径下的第一个文件 for (int i = 0; i < 2; i ++) //过滤目录 .. 和 . { m_pEnt = readdir(m_pDir);
这里图像的批量读取采用SHBrowseForFolder方法,在之前的博文中详细介绍过,网上也有很多资料,这里不再赘述。
2.3 相关变量初始化
由于要计算正确率,因此需要使用到一些局部临时变量,这里先对其进行初始化,至于每个变量的具体用途,在接下来的代码中会帮助大家理解:
float sum_num_man = 0; float sum_num_women = 0; float current_num_man = 0; float current_num_women = 0;
2.4 方法测试
接下来开始循环读取测试文件夹下的测试样本,进行性别识别、累加计数、计算正确率,先给出代码:
/**********方法测试**********/ while (m_pDir && (m_pEnt = readdir(m_pDir)) != NULL) { //首先判断当前文件是否为图像文件 char* pJpg = strstr(m_pEnt->d_name,".jpg"); char* pBmp = strstr(m_pEnt->d_name,".bmp"); char* pPng = strstr(m_pEnt->d_name,".png"); char* pJPG = strstr(m_pEnt->d_name,".JPG"); if(pJpg==NULL && pBmp==NULL && pPng==NULL && pJPG==NULL) { break; } //拼出文件的全路径 sprintf(imageFullName,"%s%s",m_ImageDir,m_pEnt->d_name); IplImage* src; CvvImage srcCvvImg; //加载图像 src = cvLoadImage(imageFullName); detect_and_draw(src); //根据标签来判断当前图片是男性还是女性 char* pMan = strstr(m_pEnt->d_name,"man"); char* pWomen = strstr(m_pEnt->d_name,"women"); if (pMan != NULL)//如果当前图片为男性 { sum_num_man = sum_num_man + 1; } if (pWomen != NULL)//如果当前图片为女性 { sum_num_women = sum_num_women + 1; } if (m_genderLabel == 1)//如果当前图片计算机检测为男性 { current_num_man = current_num_man + 1; } if (m_genderLabel == 2)//如果当前图片计算机检测为女性 { current_num_women = current_num_women + 1; } cvReleaseImage(&src); }
这里主要有一下几个问题需要强调:
(1)测试样本集的制作。在制作测试样本集的时候,就用到了图像批量改名的手段,男性测试样本的名称用“man”来标记,女性测试样本的名称用“women”来进行标记,效果如下:
(2)正确率计算方法。在这里计算识别率的方法非常简单,首先通过strstr()函数判断当前测试样本的名称中是含有“man”还是含有“women”,若含有“man”字符串,则说明当前测试样本为男性测试样本,计数器sum_num_man加一,同理若测试样本为女性样本时则女性计数器sum_num_women加一。 然后在根据性别识别结果(标签m_genderLabel )来对当前的识别情况进行计数。
2.5、计算识别率并显示
遍历完成后,开始计算识别率并将其显示在编辑框中,代码如下:
/**********计算识别结果并显示**********/ char ch[10]; if (sum_num_man > sum_num_women)//如果当前测试集为男性 { float result1; result1 = current_num_man / sum_num_man; int res1; res1 = result1 * 100; itoa(res1,ch,10); GetDlgItem(IDC_Effect_Man)->SetWindowTextA(ch); } else { float result2; result2 = current_num_women / sum_num_women; int res2; res2 = result2 * 100; itoa(res2,ch,10); GetDlgItem(IDC_Effect_Wom)->SetWindowTextA(ch); }
OK,此时F5调试运行程序,选择分类方法,初始化,选择测试样本文件夹,程序开始自动进行方法验证,得出识别率。
三 注意事项
1、批量读取文件的重要性
从这两篇博文中可以说明文件遍历的重要性,C++乃至OpenCv都有相关的图像遍历方法,这里给出的SHBrowseForFolder方法只是其中之一,其中OpenCv也提供了Directory类,大家可以多参考网上资料。
2、存在人脸检测失败的可能性
这里有一个隐含的问题需要注意,在进行人脸检测时是存在检测失败的风险的,如果检测失败,虽然程序不会报错,但会导致识别率的误降低(具体原因大家自己思考哈),解决方案有三种,一是提前进行一遍人脸检测,将检测失败的图像剔除出去;二是设置人脸检测标志位,当人脸检测失败时不进行样本数目的累加;三是直接使用已经分割好的人脸做仿真,绕过人脸检测这一步,具体采用哪种方法改进大家自行决定吧。