在前幾篇文章中,我們已經基本完成了性別識別的核心邏輯,整個程序開發也進入尾聲。本篇將為系統添加一個實用的輔助功能——人臉批量分割,用於自動化生成訓練本。
進行性別識別模型訓練時,需要大量已分割好的人臉樣本(男性樣本與女性樣本分開存放)。手動裁剪顯然效率極低,因此開發批量自動分割工具勢在必行。
在對話框中新增一個「人臉批量分割」按鈕,ID 保持預設值即可。雙擊按鈕生成點擊事件處理函數 OnBnClickedButton1(),這將作為批量處理的入口。
核心邏輯在於遍歷用戶選擇的資料夾,篩選出圖片檔案(.jpg、.bmp、.tif、.JPG 等格式)。這裡沿用了教程(8)與(9)中封裝的資料夾瀏覽與檔案枚舉代碼,關鍵差別在於將原本「手動點擊下一張」的單步操作,改為 while (m_pDir && (m_pEnt = readdir(m_pDir)) != NULL) 自動遍歷整個目錄。
void CGenderRecognitionMFCDlg::OnBnClickedButton1() {
// 1. 檢查分類器是否已初始化
if (m_boolInitOK == FALSE) {
MessageBox(_T("請先進行初始化"));
return;
}
// 2. 彈出資料夾選擇對話框
CString str;
BROWSEINFO bi = {0};
TCHAR name[MAX_PATH] = {0};
bi.hwndOwner = GetSafeHwnd();
bi.pszDisplayName = name;
bi.lpszTitle = _T("Select folder");
bi.ulFlags = 0x80; // 僅顯示資料夾
LPITEMIDLIST idl = SHBrowseForFolder(&bi);
if (idl == NULL) return;
SHGetPathFromIDList(idl, str.GetBuffer(MAX_PATH));
str.ReleaseBuffer();
m_Path = str;
if (str.GetAt(str.GetLength()-1) != _T('\\')) m_Path += _T("\\");
UpdateData(FALSE);
// 釋放 IDL 佔用的記憶體
IMalloc* imalloc = nullptr;
if (SUCCEEDED(SHGetMalloc(&imalloc))) {
imalloc->Free(idl);
imalloc->Release();
}
m_ImageDir = (LPSTR)(LPCTSTR)m_Path;
// 3. 打開目錄並跳過 "." 與 ".."
m_pDir = opendir(m_ImageDir);
for (int i = 0; i < 2; ++i) m_pEnt = readdir(m_pDir);
int SaveNum = 0;
while (m_pDir && (m_pEnt = readdir(m_pDir)) != NULL) {
// 4. 簡易副檔名過濾
char* pJpg = strstr(m_pEnt->d_name, ".jpg");
char* pBmp = strstr(m_pEnt->d_name, ".bmp");
char* pTif = strstr(m_pEnt->d_name, ".tif");
char* pJPG = strstr(m_pEnt->d_name, ".JPG");
if (!pJpg && !pBmp && !pTif && !pJPG) continue;
++SaveNum;
char imageFullName[500];
sprintf_s(imageFullName, "%s%s", m_ImageDir, m_pEnt->d_name);
// 5. 載入圖片並進行人臉檢測
IplImage* src = cvLoadImage(imageFullName);
if (!src) continue;
CvvImage srcCvvImg;
srcCvvImg.CopyOf(src);
srcCvvImg.DrawToHDC(m_pPicCtlHdc, &m_PicCtlRect);
// 核心:呼叫帶保存參數的檢測函數
detect_and_draw(src, SaveNum);
cvReleaseImage(&src);
}
closedir(m_pDir);
}
detect_and_draw() 實現批量保存為了不影響現有的單張檢測流程,採用「增加帶預設值的參數」策略:
detect_and_draw(IplImage* img, int number2save = 0);detect_and_draw(IplImage* img, int number2save)函數內部根據 number2save 是否為 0 判斷是否進入批量保存邏輯:
/********** 批量保存分割後的人臉 **********/
if (0 != number2save) {
// 將 IplImage* 轉為 Mat 以便使用 imwrite
Mat faceMat(faceImage);
stringstream ss;
ss << "E:\\教學視頻1\\" << number2save << ".jpg";
imwrite(ss.str(), faceMat);
}
回到按鈕事件中,在繪製介面前呼叫:
detect_and_draw(src, SaveNum); // 傳入當前圖片序號作為檔名依據
給新增參數設定預設值 = 0,使得專案中所有既有的 detect_and_draw(img) 呼叫無需任何修改即可繼續編譯通過,極大降低了重構風險。
宣告處寫一次預設值,定義處不寫。若兩處都寫會導致重複定義錯誤;若只有定義處寫,外部調用單元看不到預設值。
採用 stringstream 而非 CString::Format,因為後者產生 CString 型別,直接傳給 imwrite 會觸發型別不匹配錯誤。stringstream 輸出標準 std::string,完美適配 OpenCV 2.x 介面。
編譯運行 → 點擊「初始化」 → 點擊「人臉批量分割」 → 選擇含原始圖片的資料夾 → 程式自動遍歷、檢測、分割、保存至指定目錄(範例中為 E:\教學視頻1\)。至此,訓練樣本自動化生產管線建立完成。
