1. 程式人生 > >C++開發人臉性別識別教程(17)——輔助功能之人臉批量分割

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

  在之前的博文中已經將性別識別部分敘述的基本完整,整個程式的開發也接近尾聲,在這篇博文中我們再為程式新增小的輔助功能:人臉批量分割。

  一、人臉批量分割

  在前面的博文中提到過,進行性別識別訓練所用到的訓練樣本是分割好的男性人臉樣本和女性人臉樣本,那麼如何去製作這些訓練樣本呢?這就需要進行人臉影象的批量人臉分割。

  1.1 新增控制元件

  首先新增一個“人臉批量分割”的按鈕,ID採用預設值即可:                        

  1.2 批量讀取圖片

  雙擊按鈕,生成對應的事件處理函式OnBnClickedButton1(),在其中加入對應程式碼。人臉批量分割的主要工作在於圖片的批量讀取,這裡先給出整體程式碼,稍後解釋:

void CGenderRecognitionMFCDlg::OnBnClickedButton1()
{
    // TODO: 在此新增控制元件通知處理程式程式碼
    /**********判斷是否已經載入好了分類器**********/
    if (m_boolInitOK == FALSE)
    {
        MessageBox("請先進行初始化");
        return;
    }

    /**********開啟圖片資料夾**********/
    CString str;         //儲存影象路徑
    BROWSEINFO bi;       //
用來儲存使用者選中的目錄資訊 TCHAR name[MAX_PATH];//儲存路徑 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); 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); } int SaveNum = 0; while (m_pDir && (m_pEnt = readdir(m_pDir)) != NULL) { //判斷名字中有沒有 .jpg .bmp .png char* pJpg = strstr(m_pEnt->d_name,".jpg"); char* pBmp = strstr(m_pEnt->d_name,".bmp"); char* pPng = strstr(m_pEnt->d_name,".tif"); char* pJPG = strstr(m_pEnt->d_name,".JPG"); if(pJpg==NULL && pBmp==NULL && pPng==NULL && pJPG==NULL) { continue; } SaveNum = SaveNum + 1; char imageFullName[500]; //拼出檔案的全路徑 sprintf_s(imageFullName,"%s%s",m_ImageDir,m_pEnt->d_name); IplImage* src; CvvImage srcCvvImg; //載入影象 src = cvLoadImage(imageFullName); /**********人臉檢測並儲存**********/ //繪製圖像到控制元件 srcCvvImg.CopyOf(src); srcCvvImg.DrawToHDC(m_pPicCtlHdc,&m_PicCtlRect); cvReleaseImage(&src); } }

  這裡在進行圖片批量讀取時主要借用了之前C++開發人臉性別識別教程(8)——搭建MFC框架之讀取資料夾資訊C++開發人臉性別識別教程(9)——搭建MFC框架之顯示圖片這兩篇部落格中的檔案批量讀取方法,只是這裡通過“while (m_pDir && (m_pEnt = readdir(m_pDir)) != NULL)”語句自動完成了“讀取下一張圖片”的操作(之前是通過單擊“下一張”按鈕來手動完成資料夾下下一張圖片的讀取),這樣程式就能夠自動實現資料夾下所有圖片檔案的遍歷。還有一點需要注意的就是在批量儲存分割後的人臉過程中,需要對待儲存的檔案進行批量的、統一格式的命名,這裡採用變數SaveNum的累加來對當前圖片進行計數,同時為命名提供依據。 

  1.3 人臉分割與儲存

  這裡需要稍稍對人臉檢測函式detect_and_draw()做一點改進,為其加入一個新的功能:批量儲存。實在這個目的的方法有很多種,這裡採用增加函式的形參並將其作為標誌位的方法,具體方法如下:

  首先,為detect_and_draw()函式再增加一個形參int number2save,用來接受待儲存的影象編號。注意這裡在對函式進行更改時,需要在兩處進行更改,一是函式宣告的部分,二是函式定義部分。首先在類檢視中找到這個函式的定義,將其更改為detect_and_draw(IplImage* img,int number2save = 0);然後右擊該函式,選擇“轉到定義”,將函式的宣告形式也改為detect_and_draw(IplImage* img,int number2save = 0),至於這裡為何需要將number2save的形參預設值設定為0,稍後給出解釋。

  然後,開始改造detect_and_draw()的函式體。這裡給出number2save預設值的好處就是能夠根據它的值來判斷當前是否需要對分割後的人臉進行批量儲存。由於我們在進行人臉批量儲存的過程是在呼叫detect_and_draw()函式時同時向其中傳入當前圖片的計數值,因此接下來只需在函式內部來判斷number2save是否為零,如果其為預設值零,則說明當前值需要進行人臉檢測,無需分割;若為非零則說明當前需要進行批量分割與儲存。接下來向detect_and_draw()其中加入批量儲存的程式碼:

        /**********如果手工傳入第二個引數,則說明需要進行臉部影象的儲存**********/
        if (0 != number2save)
        {
            Mat image(faceImage);
            stringstream ss;
            ss<<number2save;
            string s1 = ss.str();
            string str = "E:\\教學視訊1\\" + s1 + ".bmp";
            imwrite(str,image);
        }

  將這部分程式碼新增到性別識別操作之後、faceImage變數被釋放之前即可。這裡進行了一步IplImage*型別到Mat型別的轉換,是為了方便使用2.x版本中的影象儲存函式imwrite()。當然在這裡需要指定人臉圖片批量儲存的位置,這裡將其放在E盤的“教學視訊1”資料夾下。

  改造完成之後,在“人臉批量檢測”按鈕對應的事件觸發函式OnBnClickedButton1()中呼叫這個函式(繪製圖像到控制元件之前)即可,同時向其中傳入圖片計數值:

        /**********人臉檢測並儲存**********/
        detect_and_draw(src,SaveNum);

  OK,此時執行程式,初始化,單擊“人臉批量檢測”按鈕,選擇待進行人臉檢測的資料夾,程式會自動開始進行人臉檢測,並將檢測到的人臉分割並儲存在指定資料夾下。

  二 注意事項

  1、預設形參的意外收穫

  這裡在更改人臉檢測識別函式detect_and_draw()時將第二個形參設定了預設值,其實這種做法無形中給我們提供了很大方便。因為程式編寫到現在已經在很多地方用到了這個函式,而這些已有的用法無一例外的都是採用的detect_and_draw((IplImage* img)的形式,如果我們這時候對函式重新新增形參,理論上對這些就的函式用法都應該進行更改,但是如果為這個形參指定了預設值,則無需再對其已有的函式呼叫進行更改,因為detect_and_draw((IplImage* img)這種呼叫形式只是採用了預設的形參值,依然合法。

  2、C++預設引數設定規範

  在新增形參預設值需要注意,預設引數只可在函式宣告中設定一次,只有在沒有函式宣告時,才可以在函式定義中設定。因此在函式宣告時採用detect_and_draw(IplImage* img,int number2save = 0);在函式定義時則應為detect_and_draw(IplImage* img,int number2save)的形式,這個小問題一定要注意。

  3、int型別轉換為string型別

  在對人臉圖片進行批量儲存時,關鍵的一步是將計數值(int型)轉換為檔名(字元創string型別),這裡採用了stringstream方法,當然也可以用其他方法,具體參見C++ int與string的轉化。不過這裡有一個問題需要強調,就是有關format方法的知識。format同樣可以將整形值轉換成字串型別,但這裡轉換後的字串是CString型別的,在這裡直接使用CString型別的話會報錯,具體原因我會專門寫一篇博文來說明,總之這裡不推薦使用format方法。

如果覺得這篇文章對您有所啟發,歡迎關注我的公眾號,我會盡可能積極和大家交流,謝謝。