1. 程式人生 > >C++開發人臉性別識別教程(13)——針對單張圖片的性別識別

C++開發人臉性別識別教程(13)——針對單張圖片的性別識別

  在之前的博文中我們的性別識別程式已經初步成型,能夠識別某個資料夾下的圖片檔案。不過這裡有一個問題,假設這個資料夾下有著大量的圖片,而我們希望識別這些圖片中的某一張,此時需要我們不停的單擊“下一張”按鈕才會輪詢到對應的圖片,這是相當麻煩的,因此在這篇部落格中我們向程式中新增一個功能——單張圖片的性別識別。

  一、基本思想

  最基本的辦法就是在主介面再新增一個按鈕控制元件,命名為“圖片檔案”(之前的按鈕為“圖片資料夾”),不過這樣會使得介面上的按鈕控制元件過於繁多,給人一種“作者只會用button控制元件”的感覺。這裡我們決定用一種相對巧妙的方式來解決這個問題,即向之前的“圖片資料夾”button控制元件在新增一個讀取單張圖片的功能,然後再通過某一操作來進行兩個功能間的切換,這裡我們使用滑鼠雙擊的操作。所以最終的效果就是:我們雙擊一下滑鼠,就會從“圖片資料夾”模式轉換到“單張圖片”模式(或者從“單張圖片”模式轉換為“圖片資料夾”模式)。

  二、新增滑鼠雙擊的響應事件

  MFC程式是基於訊息響應機制的,關於這點一兩句話也說不清楚,推薦大家去看孫鑫老師的MFC教學視訊。在這裡我們需要通過雙擊滑鼠左鍵來觸發一個訊息,在訊息響應函式中進行圖片讀取的模式反轉。首先,我們向類中新增雙擊滑鼠左鍵的事件響應函式。在類檢視視窗中,右擊CGenderRecognitionMFCDlg類,選擇屬性:

  在開啟的屬性對話方塊中,單擊“訊息”圖示,在訊息列表中找到WM_LBUTTONDBLCLK訊息,單擊右側的下拉按鈕,選擇“add OnLButtonDblClk”命令:

  此時,滑鼠的雙擊訊息響應函式新增完成:

  三、編寫雙擊訊息響應函式

  1、新增模式標記

  接下來我們開始編寫滑鼠雙擊後對應的執行程式碼OnLButtonDblClk()。首先,我們需要一個布林變數來記錄當前處於何種模式(是“圖片資料夾”模式還是“單張圖片”模式),這裡有兩個選擇,一是將這個變數定義為全域性布林變數,二是定義為類的成員變數,首選成員變數。因此需要向CGenderRecognitionMFCDlg新增一個bool變數m_boolFolderOrImage來進行標記:

  2、狀態轉換,更新按鈕

  接下來進行模式反轉,同時在反轉後更改按鈕所顯示的文字用以提示使用者當前程式的工作狀態,完成後OnLButtonDblClk()函式的程式碼如下:

void CGenderRecognitionMFCDlg::OnLButtonDblClk(UINT nFlags, CPoint point)
{
    
// TODO: 在此新增訊息處理程式程式碼和/或呼叫預設值 m_boolFolderOrImage = !m_boolFolderOrImage; if(m_boolFolderOrImage == FALSE) { SetDlgItemTextA(IDC_BUTTON_ImageFile,"圖片資料夾"); } else if(m_boolFolderOrImage == TRUE) { SetDlgItemTextA(IDC_BUTTON_ImageFile,"圖片檔案"); } CDialogEx::OnLButtonDblClk(nFlags, point); }

  OK,除錯執行,在主介面的任意位置雙擊滑鼠左鍵,會發現按鈕控制元件會在“圖片資料夾”和“圖片檔案”兩種模式之間進行切換。

  四、改寫OnBnClickedButtonImagefile函式

  OnBnClickedButtonImagefile()函式是“圖片資料夾”(或者是“圖片檔案”)按鈕的控制元件響應函式,由於我們對按鈕控制元件添加了新的功能,理所應當需要對其控制元件響應函式進行擴充。

  1、“開啟檔案”程式碼片

  這裡首先編寫開啟單個檔案的程式碼,MFC中開啟檔案需要使用CFileDialog這個類,這個類使用起來也非常簡單:

        CFileDialog  FDlg(TRUE);
        if(FDlg.DoModal() == IDOK)
        {
            m_Path = FDlg.GetPathName();
            UpdateData(false);
        }

  m_Path變數中儲存了當前選中檔案的全路徑。

  2、OnBnClickedButtonImagefile()函式

  接下來需要對已有的OnBnClickedButtonImagefile()函式體的結構進行改造,即需要先判斷m_boolFolderOrImage標誌位,如果其為假,則為“圖片資料夾”模式,執行資料夾批量讀取程式碼(SHBrowseForFolder方法);若為真,則為“圖片檔案”模式,執行單張圖片的讀取程式碼(CFileDialog)方法。這裡給出更改後的OnBnClickedButtonImagefile()函式的整體程式碼:

void CGenderRecognitionMFCDlg::OnBnClickedButtonImagefile()
{
    /**********是否已經進行了初始化操作**********/
    if (m_boolInitOK == false)
    {
        MessageBox("請先進行初始化");
        return;
    }

    if (!m_boolFolderOrImage)
    {

        /**********初始化變數**********/
        CString str;                                      //儲存影象路徑
        BROWSEINFO bi;                                    //用來儲存使用者選中的目錄資訊
        TCHAR name[MAX_PATH];                             //儲存路徑
        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
        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 < 1; i ++)                     //過濾目錄 ..   和  .
        {
            m_pEnt = readdir(m_pDir);
        }

        /**********啟動影象顯示程式**********/
        GetNextBigImg();
    }
    else
    {
        /**********通過開啟檔案對話方塊來獲得目標檔案的路徑**********/
        CFileDialog  FDlg(TRUE);
        if(FDlg.DoModal() == IDOK)
        {
            m_Path = FDlg.GetPathName();
            UpdateData(false);
        }

        /**********判斷是否為影象檔案**********/
        char* jpg = strstr((LPSTR)(LPCTSTR)m_Path,".jpg");
        char* bmp = strstr((LPSTR)(LPCTSTR)m_Path,".bmp");
        char* png = strstr((LPSTR)(LPCTSTR)m_Path,".png");

        if (jpg == NULL && bmp == NULL && png == NULL) //如果該檔案不是影象檔案
        {
            MessageBox("這不是一個影象檔案");
            return;
        }

        /**********人臉檢測過程**********/
        IplImage* src;
        CvvImage srcCvvImg;

        src = cvLoadImage(m_Path);
        detect_and_draw(src);

        /**********繪製圖像到控制元件**********/
        srcCvvImg.CopyOf(src);
        srcCvvImg.DrawToHDC(m_pPicCtlHdc,&m_PicCtlRect);

        cvReleaseImage(&src);
        srcCvvImg.Destroy();
    }
    // TODO: 在此新增控制元件通知處理程式程式碼
}

  這裡用幾個問題需要強調:

  (1)檔案屬性判斷。在之前資料夾工作模式下,程式會在GetNextBigImg()函式中自動判斷檔案屬性,過濾非影象檔案。而在直接讀取具體檔案時,無法保證使用者選擇的是一個影象檔案,如果程式因為使用者檔案選擇失誤而崩潰,明顯是不合理的,因此在這裡添加了檔案屬性判斷,並當使用者選擇了一個非影象檔案時程式會給出友好提示並安全返回。

  (2)與之前資料夾讀取模式的另外一個不同點就是這裡將影象的載入和檢測識別操作直接放在了OnBnClickedButtonImagefile()函式中,因為這裡無需再進行檔案輪詢的操作,況且我們已經將性別識別程式封裝在了人臉檢測函式中,這樣寫也並不會使程式碼顯得有多亂。

  OK,此時執行程式,如預期結果:

  五、總結

  在寫這篇博文所對應的程式中,我發現了一個非常嚴重,同時隱藏的也非常深的BUG,這個BUG直接導致我們的程式的一部分邏輯出現錯誤,確切的說是有一部分程式碼被架空,更為嚴重的是這個BUG並不會導致程式執行的問題,很難被發現,在此先向各位關注《C++開發人臉性別識別教程》系列部落格的讀者表示歉意,我會在下一篇博文中專門對這個BUG進行更正,同時對介面進行一點小小的美化。

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