首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> C語言 >> C++ >> 關於C++

C++ 開發人臉性別識別教程(17)——輔助功能:人臉批量分割

     編輯:關於C++

在前幾篇文章中,我們已經基本完成了性別識別的核心邏輯,整個程序開發也進入尾聲。本篇將為系統添加一個實用的輔助功能——人臉批量分割,用於自動化生成訓練本。

一、人臉批量分割的必要性

進行性別識別模型訓練時,需要大量已分割好的人臉樣本(男性樣本與女性樣本分開存放)。手動裁剪顯然效率極低,因此開發批量自動分割工具勢在必行。

二、介面佈局與控件添加

在對話框中新增一個「人臉批量分割」按鈕,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() 實現批量保存

為了不影響現有的單張檢測流程,採用「增加帶預設值的參數」策略:

  1. 在類視圖中修改宣告:detect_and_draw(IplImage* img, int number2save = 0);
  2. 在定義處同步修改(定義處不寫預設值):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); // 傳入當前圖片序號作為檔名依據

五、關鍵技術點總結

1. 預設參數的「意外收穫」

給新增參數設定預設值 = 0,使得專案中所有既有的 detect_and_draw(img) 呼叫無需任何修改即可繼續編譯通過,極大降低了重構風險。

2. C++ 預設參數規範

宣告處寫一次預設值,定義處不寫。若兩處都寫會導致重複定義錯誤;若只有定義處寫,外部調用單元看不到預設值。

3. 整數轉字串的最佳實踐

採用 stringstream 而非 CString::Format,因為後者產生 CString 型別,直接傳給 imwrite 會觸發型別不匹配錯誤。stringstream 輸出標準 std::string,完美適配 OpenCV 2.x 介面。

六、運行效果驗證

編譯運行 → 點擊「初始化」 → 點擊「人臉批量分割」 → 選擇含原始圖片的資料夾 → 程式自動遍歷、檢測、分割、保存至指定目錄(範例中為 E:\教學視頻1\)。至此,訓練樣本自動化生產管線建立完成。

批量分割按鈕介面

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved