http://blog.csdn.net/chenyusiyuan/article/details/4744097
原始版本下載頁面(僅顯示圖像):
http://download.csdn.net/source/1779188
P.S.:後面又添加了播放視頻和捕獲攝像頭畫面的功能,其中播放視頻的功能只有 'Play' 和 'Stop',不能實現暫停,《A step-by-step guide to the use of Microsoft Visual C++ and the Intel OpenCV library》 中好像有關於多線程防止任務沖突(Mutex)的內容,深入學習和了解那一部分後再嘗試做暫停的功能和其它更復雜的應用。
下面是新增功能後的項目文件(可直接運行):
http://download.csdn.net/source/2155367
在《OpenCV教程-基礎篇》的2.8節中,所創建的MFC圖像顯示是直接放在對話框面板的左上角的,感覺不大美觀;《MFC中快速應用OpenCV》則是介紹用SDI(單文檔界面)來顯示圖像,《A step-by-step guide to the use of Microsoft Visual C++ and the Intel OpenCV library》使用VS2005來進行圖像和視頻的讀取和處理,但是其圖像和視頻的顯示界面不是在對話框裡面的,而是新建一個窗口來做。所以下面我們就來看看怎麼在對話框裡使用Picture控件來顯示和處理圖像。
一、創建MFC
首先創建一個MFC對話框應用程序(Dialog-based Application)如下:
在VS2005和2008裡,我們可以用一個 Solution 來組合幾個 Project (每個 Project 基本上只包含一個 Program),當我們要構建一個多Program的應用時(例如一個客戶端程序加一個服務器應用程序),利用 Solution 可以將這些 Projects 組合起來、並且共享文件和函數庫。通常需要為Solution創建一個主路徑,其中包含了所有Projects的路徑。不過在這篇文章裡,我們只構建一個簡單的Project,所以在創建MFC的New Project對話框裡,不用勾選「Create directory for solution」這個選項。
點擊OK -- Next進入下一步,在這裡我們創建一個Dialog-based Application,大部分選項按默認設置就行,不過最下面的「Use Unicode libraries」最好去掉。如果勾選了這個選項,程序代碼就會使用16bit的Unicode字符集來編碼,但是很多函數雖然使用 char* (ASCII stings) 類型字符,而將字符串從 Unicode 轉換到 ASCII 是非常麻煩的。使用 Unicode 在編譯時可能會遇到下列錯誤:
cannot convert parameter 1 from 'CString' to 'const char *' cannot convert from 'const char [11]' to 'LPCWSTR'
這意味著在Unicode和Multi-byte字符串的轉換中出現了問題。在上一篇學習筆記中,就提到「成員函數LoadBMP其輸入參數類型應為 const char*」,那應該只是一個治標的方法,這裡的去掉「Use Unicode libraries」選項,才是治本之道。
往後的幾步設置,可以根據自己的需要來操作,我的設置如下:
二、編寫代碼
在Resource View面板->mymfc(工程名稱)->mymfc.rc->Dialog雙擊IDD_MYMFC_DIALOG,可以看到一個初始的GUI界面,往裡面添加兩個 Button 和 一個 Picture 控件,如下:
選中單個控件、右擊選擇屬性(Properties),可以看到控件的ID號,這個號可以自行編輯,例如 Picture 控件的 ID 號我設置為 IDC_ShowImg,這個 ID 號在後面的圖像顯示函數中要用到。
首先在項目屬性中加載lib文件:菜單Project -> Properties -> Configuration Properties -> Linker –> Input -> additional dependencies 中加入 cxcore200.lib cv200.lib highgui200.lib。
然後在 mymfc.h 的 #include "resource.h" 下加入如下代碼:
#include "cv.h" #include "highgui.h" #define IMAGE_WIDTH 256 #define IMAGE_HEIGHT 256 #define IMAGE_CHANNELS 3
在 Class View 面板右擊 CmymfcDlg,選擇 Add –> Add Variable,添加一個 IplImage* 類型的變量 TheImage;再點擊 CmymfcDlg,在下面窗口的列表中雙擊 OnInitDialog,在「// TODO: Add extra initialization here」下面添加 TheImage 的初始化代碼:
CvSize ImgSize; ImgSize.height = IMAGE_HEIGHT; ImgSize.width = IMAGE_WIDTH; TheImage = cvCreateImage( ImgSize, IPL_DEPTH_8U, IMAGE_CHANNELS );
然後雙擊 OnPaint,在 if(IsIconic())…的 else 裡添加以下代碼,用來重繪窗口:
CDialog::OnPaint(); // 重繪對話框 CDialog::UpdateWindow(); // 更新windows窗口,如果無這步調用,圖片顯示還會出現問題 ShowImage( TheImage, IDC_ShowImg ); // 重繪圖片函數
接著在 CmymfcApp 下面的成員列表中雙擊 InitInstance,在兩個「// TODO: Place code here to handle when the dialog is…」下面添加:
cvReleaseImage( &dlg.TheImage );
即按下「OK」或「Cancel」時,釋放TheImage佔用的內存。
接下來就是寫讀取和處理圖片的功能函數了。
回到 mymfc 的 GUI 編輯界面中,右擊按鈕 ReadImg,選擇 Add Event Handler,建立按鈕點擊的消息響應程序:
句柄名設置為 OnBnClickedReadimg,主要的響應操作包括 彈出對話框選擇圖片文件、讀入圖片文件、對圖片統一縮放至256*256的大小、顯示圖像,代碼如下:
// TODO: Add your control notification handler code here CFileDialog dlg( TRUE, _T("*.bmp"), NULL, OFN_FILEMUSTEXIST | OFN_PATHMUSTEXIST | OFN_HIDEREADONLY, _T("image files (*.bmp; *.jpg) |*.bmp; *.jpg | All Files (*.*) |*.*||"), NULL ); // 選項圖片的約定 dlg.m_ofn.lpstrTitle = _T("Open Image"); // 打開文件對話框的標題名 if( dlg.DoModal() != IDOK ) // 判斷是否獲得圖片 return; CString mPath = dlg.GetPathName(); // 獲取圖片路徑 IplImage* ipl = cvLoadImage( mPath, 1 ); // 讀取圖片、緩存到一個局部變量 ipl 中 if( !ipl ) // 判斷是否成功載入圖片 return; if( TheImage ) // 對上一幅顯示的圖片數據清零 cvZero( TheImage ); ResizeImage( ipl ); // 對讀入的圖片進行縮放,使其寬或高最大值者剛好等於 256,再復制到 TheImage 中 ShowImage( TheImage, IDC_ShowImg ); // 調用顯示圖片函數 cvReleaseImage( &ipl ); // 釋放 ipl 佔用的內存
其中包含了兩個新的成員函數 ResizeImage 和 ShowImage,前者的作用是對讀入的不同大小的圖像進行縮放,再通過設置 ROI 的方式將圖像存入 256*256 的 TheImage 中;後者是將圖像 TheImage 顯示到圖片顯示控件 IDC_ShouImg 窗口的正中部位。為了實現這兩個功能,首先在 Class View 面板右擊 CmymfcDlg,選擇 Add –> Add Function,創建兩個函數:void ShowImage( IplImage* img, UINT ID ) 和 void ResizeImage(IplImage* img)。以下是這兩個函數的實現代碼:
void CmymfcDlg::ResizeImage(IplImage* img) { // 讀取圖片的寬和高 int w = img->width; int h = img->height; // 找出寬和高中的較大值者 int max = (w > h)? w: h; // 計算將圖片縮放到TheImage區域所需的比例因子 float scale = (float) ( (float) max / 256.0f ); // 縮放後圖片的寬和高 int nw = (int)( w/scale ); int nh = (int)( h/scale ); // 為了將縮放後的圖片存入 TheImage 的正中部位,需計算圖片在 TheImage 左上角的期望坐標值 int tlx = (nw > nh)? 0: (int)(256-nw)/2; int tly = (nw > nh)? (int)(256-nh)/2: 0; // 設置 TheImage 的 ROI 區域,用來存入圖片 img cvSetImageROI( TheImage, cvRect( tlx, tly, nw, nh) ); // 對圖片 img 進行縮放,並存入到 TheImage 中 cvResize( img, TheImage ); // 重置 TheImage 的 ROI 准備讀入下一幅圖片 cvResetImageROI( TheImage ); } void CmymfcDlg::ShowImage( IplImage* img, UINT ID ) // ID 是Picture Control控件的ID號 { CDC* pDC = GetDlgItem( ID ) ->GetDC(); // 獲得顯示控件的 DC HDC hDC = pDC ->GetSafeHdc(); // 獲取 HDC(設備句柄) 來進行繪圖操作 CRect rect; GetDlgItem(ID) ->GetClientRect( &rect ); int rw = rect.right - rect.left; // 求出圖片控件的寬和高 int rh = rect.bottom - rect.top; int iw = img->width; // 讀取圖片的寬和高 int ih = img->height; int tx = (int)(rw - iw)/2; // 使圖片的顯示位置正好在控件的正中 int ty = (int)(rh - ih)/2; SetRect( rect, tx, ty, tx+iw, ty+ih ); CvvImage cimg; cimg.CopyOf( img ); // 復制圖片 cimg.DrawToHDC( hDC, &rect ); // 將圖片繪制到顯示控件的指定區域內 ReleaseDC( pDC ); }
函數 ResizeImage 是參考了學習筆記(5)中單窗口顯示多幅圖像的函數 cvShowMultiImages 修改而成的,函數 ShowImage 則是參考了帖子《OpenCV如何把圖像顯示到MFC的picture控件上》的代碼,另外下面幾個帖子也可以參考:
1、《MFC picture control 畫框的問題》
2、《MFC picture control控件實現(隱藏)文字顯示》
3、《MFC在Picture Control中顯示圖片(jpg)遇到的問題》
4、《vc怎樣在picture control中顯示jpg,jif,bmp格式圖象》
5、《使用Picture Control顯示BMP圖片》
最後是要對讀入的圖像做簡單的Canny邊緣處理,為此,建立一個按鈕 EdgeDetect,相應的響應代碼如下:
void CmymfcDlg::OnBnClickedEdgedetect() { // TODO: Add your control notification handler code here IplImage *gray = 0, *edge = 0; gray = cvCreateImage( cvSize(IMAGE_WIDTH, IMAGE_HEIGHT), IPL_DEPTH_8U, 1 ); edge = cvCreateImage( cvSize(IMAGE_WIDTH, IMAGE_HEIGHT), IPL_DEPTH_8U, 1 ); cvCvtColor( TheImage, gray, CV_BGR2GRAY ); cvCanny( gray, edge, 30, 100, 3 ); cvCvtColor( edge, TheImage, CV_GRAY2BGR ); ShowImage( TheImage, IDC_ShowImg ); // 調用顯示圖片函數 cvReleaseImage( &gray ); cvReleaseImage( &edge ); }
這裡主要是參考了《OpenCV教程-基礎篇》P33的代碼,不過並沒有像書中那樣創建新的 MyIplClass 類來進行圖像的讀取和處理操作。我覺得這篇文章中直接對 TheImage 進行處理可能不大妥當,按照教程那樣新建一個類來處理才是比較穩妥吧。這裡涉及到函數返回對象實例的過程、深拷貝和淺拷貝、拷貝構造、動態內存的使用、opencv相關接口的代碼等方面,以後還要進一步深入學習和理解!
最後我們還可以在Resource View面板->mymfc(工程名稱)->mymfc.rc->Version中修改程序的產品版本、名稱等信息。
來看看程序生成後的效果: