程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> C語言 >> C++ >> 關於C++ >> C++開發人臉性別識別教程(16)——視頻人臉性別識別

C++開發人臉性別識別教程(16)——視頻人臉性別識別

編輯:關於C++

  在之前的博文中我們已經能夠順利驅動攝像頭來采集源圖像,在這篇博文中將正式為其加入性別識別的代碼,實現攝像頭視頻的人臉性別識別。

  一、人臉檢測

  在得到攝像頭采集的源圖像之後,首先要做的就是對其進行人臉檢測,將人臉區域分割出來。這步相對來說比較簡單,只需在定時器時間觸發函數中加入人臉檢測的代碼即可,這裡給出OnTimer()函數的整體代碼:

void CGenderRecognitionMFCDlg::OnTimer(UINT_PTR nIDEvent)
{
    /***********人臉檢測並識別**********/
    m_pVideoInfo->m_pFrameImage = cvQueryFrame(m_pVideoInfo->m_pCapture);//得到視頻流中的下一幀
    IplImage* IplImg;
    IplImg = m_pVideoInfo->m_pFrameImage;
    detect_and_draw(IplImg);

    /***********顯示圖像**********/
    CvvImage cvvImage;
    cvvImage.CopyOf(IplImg);
    cvvImage.DrawToHDC(m_pPicCtlHdc,m_PicCtlRect);
    CDialogEx::OnTimer(nIDEvent);
}

  注意這裡相對於上一篇博客中的OnTimer()函數有一些改動,主要體現在兩個方面:一是添加了detect_and_draw(IplImg)人臉檢測與性別識別操作(detect_and_draw()函數內部默認調用了性別識別函數GenderRecognition()),完成性別識別操作;二是在通過CvvImage類進行圖片的顯示時,推薦顯示人臉檢測之後的圖像(這裡的變量IplImg),這樣在顯示結果中就能夠將人臉檢測過程中所畫的人臉框一並顯示出來,顯得更為形象。

  OK,此時的程序已經具備了基本的攝像頭視頻性別識別功能,F5運行,初始化,打開視頻,程序正常工作。

  二、性別識別函數改進

  雖然此時程序能夠正常運行,但在運行過程中會發現程序的識別結果有時(甚至大部分時候)會不太穩定,即不斷的在“帥哥”和“美女”之間變來變去,這直接說明了我們所采用的識別算法的魯棒性很不好,不過也情有可原,畢竟這裡只是使用了OpenCv提供的最基本的人臉檢測方法和人臉識別方法,但這裡我仍然希望在算法受限的條件下對其魯棒性進行一下改進,這就用到了視頻識別中常用的手段——多幀聯合。

  所謂多幀聯合,就是對多幀圖像進行識別分析,得到多個識別結果,然後在這個基礎上通過加權融合,給出最後的識別結果。理論上多幀聯合識別的手段能夠排除某些突發的、極端錯誤的干擾,給出“整體正確”的判別結果,我們這裡就采用這個手段對算法的魯棒性稍微做一點點改進。

  2.1 確定識別幀數

  要進行多幀聯合識別,首先要確定一次處理多少幀,這裡我們可以讓用戶來進行選擇。與之前分類器的選擇方法類似,我們在這裡同樣提供一個Combo-box控件,供用戶在其列表中選擇聯合識別的幀數。

  仿照之前下拉選擇列表(Combo Box)控件的添加方法,這裡再次添加一個同樣類型的控件,將ID更改為:IDC_COMBO_NUM:

\  對應的,在CMFCShowVideoTestDlg類的OnInitDialog()函數中對其進行初始化,確定顯示內容以及默認選項:

    ((CComboBox*)GetDlgItem(IDC_COMBO_NUM))->AddString("1");
    ((CComboBox*)GetDlgItem(IDC_COMBO_NUM))->AddString("3");
    ((CComboBox*)GetDlgItem(IDC_COMBO_NUM))->AddString("5");
    ((CComboBox*)GetDlgItem(IDC_COMBO_NUM))->AddString("7");
    ((CComboBox*)GetDlgItem(IDC_COMBO_NUM))->AddString("9");
    ((CComboBox*)GetDlgItem(IDC_COMBO_NUM))->AddString("11");
    ((CComboBox*)GetDlgItem(IDC_COMBO_NUM))->AddString("13");
    ((CComboBox*)GetDlgItem(IDC_COMBO_NUM))->AddString("15");
    ((CComboBox*)GetDlgItem(IDC_COMBO_NUM))->SetCurSel(4);

  由於這裡是二分類問題,因此聯合幀數必須是奇數。這裡同樣需要提前指定Combo Box的下拉范圍,以保證選項能夠正常顯示,同時將Combo Box控件的”sort“屬性,應該置為”false“,此時運行程序,控件正常工作:

\  既然用戶指定了聯合識別的幀數,在進行視頻性別識別之前,就需要先讀取用戶選擇的幀數,與之前的分類器識別相似,需要在性別識別函數GenderRecognition()內部,通過switch語句來讀取對應的選項值:

    /***********根據當前用戶選擇的方法來確定聯合識別的幀數**********/
    int iFrameNum = 0;
    index         = ((CComboBox*)GetDlgItem(IDC_COMBO_NUM))->GetCurSel();
    switch (index)
    {
    case 0:
        {
            iFrameNum = 1;
            break;
        }
    case 1:
        {
            iFrameNum = 3;
            break;
        }
    case 2:
        {
            iFrameNum = 5;
            break;
        }
    case 3:
        {
            iFrameNum = 7;
            break;
        }
    case 4:
        {
            iFrameNum = 9;
            break;
        }
    default:
        {
            break;
        }
    }

  2.2 標記工作模式

  在采用了幀聯合判別的視頻識別模式之後,導致了程序對單張圖片和視頻的識別的處理方式是不同的:單張圖片意味著只有一幀圖像,無法進行幀聯合判決,只能一次給出結果;而在處理視頻時則需要進行幀聯合判決。因此需要設置一個標志位來告訴程序當前的工作模式是單張圖片還是視頻流。向CGenderRecognitionMFCDlg類中添加一個布爾型變量m_boolModelFlag指示當前的工作模式,ture代表單張圖片,False代表視頻流:

\

  在“圖像文件夾”按鈕對應的處理函數OnBnClickedButtonImagefile()中,將該標志位置為真:

\

  同樣,在“打開視頻”按鈕對應的事件處理函數OnBnClickedButton1Video()中,將該標志位置為False。

  2.3 融合識別結果

  接下來繼續改造性別識別函數GenderRecognition()。這裡首先需要向CGenderRecognitionMFCDlg類中添加兩個整型變量m_ManNum和m_WomenNum,分別保存每一幀的識別結果,在添加一個整型變量m_FrameNum來保存當前已識別的幀數。然後就可以進行多幀聯合判別了,這裡先給出整體代碼,稍後解釋:

    if (m_boolModelFlag == TRUE)    //若當前為圖像模式
    {
        if (1 == m_genderLabel)
        {
            GetDlgItem(IDC_EDIT_RESULT)->SetWindowText("帥哥");
        }
        else if(2 == m_genderLabel)
        {
            GetDlgItem(IDC_EDIT_RESULT)->SetWindowText("美女");
        }
    }
    else                            //若當前為視頻模式
    {
        if (1 == m_genderLabel)     //若當前幀識別結果為男性
        {
            m_ManNum = m_ManNum  + 1;
        }
        else if(2 == m_genderLabel) //若當前幀識別結果為女性
        {
            m_WomenNum = m_WomenNum + 1;
        }
        m_FrameNum = m_FrameNum + 1;
        if (m_FrameNum = iFrameNum) //達到指定識別幀數
        {
            if (m_ManNum > m_WomenNum)
            {
                GetDlgItem(IDC_EDIT_RESULT)->SetWindowText("帥哥");
            }
            else
            {
                GetDlgItem(IDC_EDIT_RESULT)->SetWindowText("美女");
            }
            m_ManNum   = 0;         //各個計數器清零
            m_WomenNum = 0;
            m_FrameNum = 0;
        }
    }

  這段代碼看似繁瑣,其實很容易理解,目的就是對各個幀的識別結果進行累計,在達到指定聯合幀數之後根據之前的奇數幀中所含男性識別結果和女性識別結果的數量來給出最終的判斷,類似於一種投票表決的工作方式。

  由於這裡我們對性別識別函數做了較大的改動,這裡給出目前GenderRecognition()函數的完整代碼:

void CGenderRecognitionMFCDlg::GenderRecognition(IplImage* img)
{
    Mat image(img);
    Mat trainImg;
    resize(image,image,Size(92,112));

    /***********根據當前用戶選擇的方法來使用對應的分類器進行分類**********/
    int index = 0;
    index     = ((CComboBox*)GetDlgItem(IDC_COMBO_FUNCTION))->GetCurSel();
    switch (index)
    {
    case 0:
        {
            m_genderLabel = model_PCA->predict(image);
            break;
        }
    case 1:
        {
            m_genderLabel = model_Fisher->predict(image);
            break;
        }
    case 2:
        {
            m_genderLabel = model_LBP->predict(image);
            break;
        }
    case 3:
        {
            resize(image, trainImg, cv::Size(64,64), 0, 0, INTER_CUBIC);
            HOGDescriptor *hog=new HOGDescriptor(cvSize(64,64),cvSize(16,16),cvSize(8,8),cvSize(8,8), 9);  
            vectordescriptors; 
            hog->compute(trainImg, descriptors,Size(1,1), Size(0,0)); 
            Mat SVMtrainMat =  Mat::zeros(1,descriptors.size(),CV_32FC1);    
            int n=0;    
            for(vector::iterator iter=descriptors.begin();iter!=descriptors.end();iter++)    
            {    
                SVMtrainMat.at(0,n) = *iter;    
                n++;    
            }  
            m_genderLabel = svm.predict(SVMtrainMat);
            break;
        }
    default:
        {
            break;
        }
    }

    /***********根據當前用戶選擇的方法來確定聯合識別的幀數**********/
    int iFrameNum = 0;
    index         = ((CComboBox*)GetDlgItem(IDC_COMBO_NUM))->GetCurSel();
    switch (index)
    {
    case 0:
        {
            iFrameNum = 1;
            break;
        }
    case 1:
        {
            iFrameNum = 3;
            break;
        }
    case 2:
        {
            iFrameNum = 5;
            break;
        }
    case 3:
        {
            iFrameNum = 7;
            break;
        }
    case 4:
        {
            iFrameNum = 9;
            break;
        }
    default:
        {
            break;
        }
    }

    /**********顯示識別結果**********/
    if (m_boolModelFlag == TRUE)    //若當前為圖像模式
    {
        if (1 == m_genderLabel)
        {
            GetDlgItem(IDC_EDIT_RESULT)->SetWindowText("帥哥");
        }
        else if(2 == m_genderLabel)
        {
            GetDlgItem(IDC_EDIT_RESULT)->SetWindowText("美女");
        }
    }
    else                            //若當前為視頻模式
    {
        if (1 == m_genderLabel)     //若當前幀識別結果為男性
        {
            m_ManNum = m_ManNum  + 1;
        }
        else if(2 == m_genderLabel) //若當前幀識別結果為男性
        {
            m_WomenNum = m_WomenNum + 1;
        }
        m_FrameNum = m_FrameNum + 1;
        if (m_FrameNum == iFrameNum) //達到指定識別幀數
        {
            if (m_ManNum > m_WomenNum)
            {
                GetDlgItem(IDC_EDIT_RESULT)->SetWindowText("帥哥");
            }
            else
            {
                GetDlgItem(IDC_EDIT_RESULT)->SetWindowText("美女");
            }
            m_ManNum   = 0;         //各個計數器清零
            m_WomenNum = 0;
            m_FrameNum = 0;
        }
    }
}

  OK,此時在此運行程序,發現結果顯示比之前要穩定許多,說明我們的改進是有效果的。

  三、注意事項

  1、人臉檢測的准確度,性別識別的穩定性

  在對視頻圖像進行識別的過程中,充分暴露了我們所采用算法的不可靠性,即結果變來變去的,因此如果需要真正開發性別識別的相關應用時,還是要尋求更准確,魯棒性更高的算法。

  2、性別識別的速度,圖像顯示的流暢性問題

  這裡由於人臉檢檢測、性別識別的過程存在較大消耗,因此會使得在顯示過程中視頻出現一定程度的卡頓現象。

  3、變量的默認初始化

  在向類中添加成員變量的時候一定要注意及時在構造函數中對成員變量進行初始化操作,這裡VS的MFC類向導會默認幫助我們完成一些(但不是全部)初始化操作,方便許多。

  4、圖片較少,不願爆照

  這篇博客中我沒怎麼粘貼程序的運行效果圖,主要是因為在下相貌不夠可人,不好意思爆照,不過代碼都沒有問題,親測可用。

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved