在之前的博文中已經完成了針對圖片的人臉性別識別功能,在這篇文章中我們開始引入攝像頭設備,為程序添加第二個功能:視頻人臉性別識別。
一、添加控件
這裡需要新添加兩個與視頻人臉性別識別相關的功能控件,一個是“打開視頻”按鈕(ID為IDC_OpenVideo),一個是“暫停按鈕”按鈕。為了適當減少主窗口中的按鈕控件的數量,這裡再次采用一種復用策略,即將視頻識別模式中的“暫停”功能與之前圖片文件夾識別模式中的“下一張”功能合並,通過一個按鈕來控制:
二、CVideoInfo類
2.1 添加視頻流類
接下來需要開啟視頻攝像頭,將攝像頭得到的圖像實時的顯示在主程序的picture控件中。有關OpenCv中的攝像頭操作我在C++開發人臉性別識別教程(4)——OpenCv的人臉檢測函數這篇博文中進行了較為詳細的介紹。這裡我們對攝像頭的開啟進行一下小小的封裝,即將其聲明為一個類CVideoInfo,這樣做的原因主要是為了能夠方便後期的處理,我們將攝像頭輸出的視頻流、幀圖像、幀圖像的尺寸(寬度和高度)都封裝在同一個對象中,方便讀取,也方便獲取圖像的相關屬性。
在VS中切換到類視圖窗口,右擊工程名,添加類:
指定添加類的類型為“C++類”:
輸入類名CVideoInfo,對應的.h文件和.cpp文件名稱系統會自動生成,無繼承,訪問屬性默認使用public,單擊完成:
此時在類視圖下可以看到工程中多了一個名為CVideoInfo的類:
2.2 編輯CVideoInfo類
接下來為CVideoInfo添加相應的代碼。切換到解決方案資源管理器窗口,會發現此時工程目錄下會多出兩個文件,VideoInfo.h和VideoInfo.cpp:
首先編輯類對應的頭文件VideoInfo.h,發現VS已經在頭文件中提供了基本的類聲明,並給出了缺省的構造函數和析構函數,我們在這裡只需向其中添加若干成員變量即可,這裡我們添加四個成員變量:CvCapture*格式的視頻流變量,IplImage*格式的幀圖像變量,以及兩個整型變量用以表示幀圖像的寬度和高度:
CvCapture* m_pCapture; //用於存儲攝像頭輸入的視頻流 IplImage* m_pFrameImage; //存儲圖片 int m_FrameWidth; //圖片寬度 int m_FrameHeight; //圖片高度
最終VideoInfo.h文件中的類聲明代碼如下:
#pragma once #include "opencv2/opencv.hpp" class CVideoInfo { public: CvCapture* m_pCapture; //用於存儲攝像頭輸入的視頻流 IplImage* m_pFrameImage; //存儲圖片 int m_FrameWidth; //圖片寬度 int m_FrameHeight; //圖片高度 public: CVideoInfo(void); //缺省構造函數 public: ~CVideoInfo(void); //缺省析構函數 };
至於VideoInfo.cpp,由於VideoInfo類目前只是負責圖像的存儲和顯示,沒有其他額外的功能,因此在這裡只需要完成構造函數對成員變量的初始化,而析構函數采用系統提供的缺省函數即可,VideoInfo.cpp文件的代碼如下:
#include "StdAfx.h" #include "VideoInfo.h" CVideoInfo::CVideoInfo(void) //缺省構造函數 { m_pCapture = NULL; m_pFrameImage = NULL; } CVideoInfo::~CVideoInfo(void)//缺省析構函數 { }
完成了類的定義之後,需要向CGenderRecognitionMFCDlg類中添加一個VideoInfo*形式的變量m_pVideoInfo用來保存當前的視頻流信息,當然使用全局變量也可以實現這個功能,不過會是代碼變得更復雜,不推薦。
在添加變量之前需要先在CGenderRecognitionMFCDlg.h中包含一下VideoInfo.h頭文件,使得VideoInfo類可見:
接下來就可以正式著手開啟攝像頭了。
三、開啟攝像頭
雙擊“打開視頻”按鈕,添加對應的事件處理函數:
首先,判斷程序是否進行了分類器加載初始化:
if (m_boolInitOK == FALSE) { MessageBox("請先進行初始化"); return; }
然後使用cvCreateCameraCapture函數開啟攝像頭,並將視頻流賦值給m_pVideoInfo:
m_pVideoInfo->m_pCapture = cvCreateCameraCapture(0);//創建一個Capture(攝像頭)
注意此時運行程序的話在這句代碼會報錯,原因是m_pVideoInfo這個成員變量尚未被分配內存空間,因此需要在程序開始運行時手動通過new操作符對其進行內存空間的分配,這裡將內存分配的語句放置在OnInitDialog()這個成員函數中:
/*********為m_pVideoInfo變量分配內存空間**********/ m_pVideoInfo = new CVideoInfo;
四、顯示圖像
在攝像頭捕捉到圖像之後,接下來需要將幀圖像實時的顯示在picture界面中,實現這個功能的方法有很多,這裡采用定時器的方法。即設置一個定時器,每隔一定時間就觸發,在對應的回調函數中完成幀圖像的顯示工作。因此在OnBnClickedButton1Video()函數中需要對定時器進行初始化,單擊“打開視頻”按鈕後,就開始觸發定時器,輪詢播放攝像頭畫面:
SetTimer(1,1,NULL);//觸發一個計數器,在響應函數中完成圖像顯示
有關MFC中定時器的使用大家參見網上資料,這裡就不再贅述。
接下來添加定時器消息響應函數,在類視圖中右鍵單擊CGenderRecognitionMFCDlg類,選擇屬性,彈出屬性對話框,在消息欄中找到WM_TIMER,單擊右邊的下拉箭頭,添加消息響應函數OnTimer:
在OnTimer()函數體中完成幀圖像的繪制,這裡直接給出代碼:
void CGenderRecognitionMFCDlg::OnTimer(UINT_PTR nIDEvent) { m_pVideoInfo->m_pFrameImage = cvQueryFrame(m_pVideoInfo->m_pCapture);//得到視頻流中的下一幀 CvvImage cvvImage; cvvImage.CopyOf(m_pVideoInfo->m_pFrameImage); cvvImage.DrawToHDC(m_pPicCtlHdc,m_PicCtlRect); CDialogEx::OnTimer(nIDEvent); }
此時運行程序,程序正常執行,能夠在picture界面上實時的顯示攝像頭采集的圖像,在下一篇博客中將介紹如何向其中添加性別識別的步驟。
五、注意事項
1、CVideoInfo類的封裝問題
說實話這裡將攝像頭輸入的視頻流封裝為CVideoInfo類的工作方式顯得有點小題大做,似乎直接在類中添加一個CvCapture*類型的成員變量就能夠完成任務,但隨著程序的改進,我們可能需要得到越來越多的視頻幀圖像的屬性值(如尺寸,通道數等等),甚至需要在讀取幀圖像之前對原圖像做一些必要的、固化的初始化操作,如果我們將視頻流封裝成CVideoInfo類,就能夠方便的以成員函數的形式給出這些操作、屬性值,避免了在程序的大框架下添加,也避免了對各個屬性值的頻繁讀取。
2、類成員訪問屬性問題
C++中對類成員變量的訪問屬性推薦設置為private,對成員函數的訪問屬性推薦設置為public,並且推薦通過成員函數來訪問成員變量,而非直接讀取。
3、定時器問題
關於VC中定時器的用法網上有很多詳細的說明,例如VC定時器的用法:SetTimer和Ontimer,這裡將時間間隔設置為1毫秒,即每一毫秒就刷新一幀,大家可以嘗試更改其他的時間間隔,觀察效果。
4、OnInitDialog函數問題
OnInitDialog()函數在對話框生成的時候調用,因此在基於對話框的程序中,這個函數用來執行程序必要的初始化操作,目前為止在我們的工程中這個函數承擔了初始化Combo Box控件、初始化Combo Box控件、為m_pVideoInfo變量分配內存空間的任務,隨著程序的編寫,還會有更多的初始化操作被加入到這個函數體中。
5、致歉
這篇博客是我在寒假之後寫的,中間有一個多月的時間沒寫過代碼,所以思路有點混亂,敘述的不是很清晰,大家見諒。