時光推移了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、存在人臉檢測失敗的可能性
這裡有一個隱含的問題需要注意,在進行人臉檢測時是存在檢測失敗的風險的,如果檢測失敗,雖然程序不會報錯,但會導致識別率的誤降低(具體原因大家自己思考哈),解決方案有三種,一是提前進行一遍人臉檢測,將檢測失敗的圖像剔除出去;二是設置人臉檢測標志位,當人臉檢測失敗時不進行樣本數目的累加;三是直接使用已經分割好的人臉做仿真,繞過人臉檢測這一步,具體采用哪種方法改進大家自行決定吧。