自從開始學C++的OpenCV,就想把問題都用一個簡單的類來解決。原來使用VC6.0使用的是StretchDIBits這類函數來畫圖,一旦畫新的映像或者顯示多張位元影像,勢必遇到多次對環境的調色盤重新載入。如今都已經VS2010,MFC下的映像顯示功能有所加強。使用MFC下的CImage類(貌似VC2005開始支援)可以很輕鬆的和快速的在MFC下顯示映像;(到底是不是很快速還需要更多的考證,但把已經存在的Mat矩陣lena映像顯示出來,使用getTimeGet函數測得,貌似低於1ms)。由於網上太多雜亂的內容和很多不夠詳細的講解,讓我嘗試了一天,才把彩色和灰階映像正確顯示到介面上。
我這裡使用C++的OpenCV,如果你使用c語言的OpenCV一樣可以實現出來。
1.讀入Mat矩陣(cvMat一樣),Mat img=imread("*.*");//cvLoadImage
確保轉換前矩陣中的資料都是uchar(0~255)類型(不是的話量化到此區間),這樣才能顯示。(初學者,包括我經常忘了此事)
2.根據矩陣大小建立(CImage::Create)新的的CImage類
CImage CI;
int w=img.cols;//寬
int h=img.rows;//高
int chinnels=img.channels();//通道數
CI.Destroy();//建立前,最好使用它,防止重複建立,程式崩潰
CI.Create(w,h,8*chinnels);
3.下來就是對CI進行賦值了,這裡是最核心的地方,分二類討論
(1)如果是1個通道的映像(灰階映像)
CImage中內建了調色盤,我們要對他進行賦值:
RGBQUAD* ColorTable;
int MaxColors=256;
//這裡可以通過CI.GetMaxColorTableEntries()得到大小(如果你是CI.Load讀入映像的話)
ColorTable = new RGBQUAD[MaxColors];
CI.GetColorTable(0,MaxColors,ColorTable);//這裡是取得指標
for (int i=0; i<MaxColors; i++)
{
ColorTable[i].rgbBlue = (BYTE)i;
//BYTE和uchar一回事,但MFC中都用它
ColorTable[i].rgbGreen = (BYTE)i;
ColorTable[i].rgbRed = (BYTE)i;
}
CI.SetColorTable(0,MaxColors,ColorTable);
delete []ColorTable;
然後就是資料拷貝了(這裡的矩陣表示方法,根據需要(cvMat or Mat)修改):
if(chinnels==1)
{//灰階映像
uchar *pS;
uchar *pImg=(uchar *)CI.GetBits();
int step=CI.GetPitch();
for(int i=0;i<h;i++)
{
pS=img.ptr<uchar>(i);
for(int j=0;j<w;j++)
{
*(pImg+i*step+j)=pS[j];
}
}
}
(2)如果是3個通道(彩色映像)
沒有調色盤,直接賦值
if(chinnels==3)
{//彩色映像
uchar *pS;
uchar *pImg=(uchar *)CI.GetBits();//得到CImage資料區地址
int step=CI.GetPitch();
//這個是一行像素站的儲存空間w*3,並且結果是4的倍數(這個不用關注,到底是不是4的倍數有待考證)
for(int i=0;i<h;i++)
{
pS=img.ptr<uchar>(i);
for(int j=0;j<w;j++)
{
for(int k=0;k<3;k++)
*(pImg+i*step+j*3+k)=pS[j*3+k];
//注意到這裡的step不用乘以3
}
}
}
4.至此已經構建好CImage,下來就是顯示它。我們可以直接在對話方塊、單文檔等地方顯示他,還可以使用CPictureCtrl空間顯示他。下面給出幾個顯示方法:
//顯示前,這裡有個問題,等會討論
(1)放在一個按鈕響應或者函數中
//這裡的m_Pic是一個CPictureCtrl的control,其他控制項等也一樣
//CStatic m_Pic;
//DDX_Control(pDX, IDC_STATIC_Img, m_Pic);
CWnd * pCWnd = CWnd::FromHandle(m_Pic.GetSafeHwnd());
//通過變數得到dc比較複雜,但很好用
CPaintDC dc(pCWnd);//如果這個不能使用就換成CClientDC 。。。。
Invalidate(false);
SetStretchBltMode(dc.m_hDC,COLORONCOLOR);
//這個需要百度看看為什麼這樣設定
CI.StretchBlt(dc.m_hDC,rect,SRCCOPY);
//這裡顯示大小rect(CRect類型)也由自己定義,這個函數有許多重載函數
//映像顯示的大小和效果,在你能顯示出來後,可以慢慢考慮
這裡的控制項的dc還可以由下面方式取得
CPaintDC dc(GetDlgItem(IDC_STATIC_Img));//IDC_STATIC_Img是空間的ID
(2)直接顯示(下面就寫得簡單點,少的部分自己加)
CDC *pDC=GetDC();
Invalidate(false);
CI.StretchBlt(pDC->m_hDC,rect,SRCCOPY);
或者
CPaintDC dc(this);
CI.Draw(dc.m_hDC,0,0);//這個以某個dc(可以是視窗)的(0,0)為起點
5.問題
前面提到一個問題,現在討論下,就是使用StretchBlt可以對映像進行展開顯示。一般的映像大小和你顯示的控制項或者地區大小是不一樣的,這時使用它顯示可以展開它到合適的大小。(注意選擇合適參數)。但我們還可以直接使用opencv內建的函數對映像進行resize,可以同樣達到類似的效果。目前我嘗試的結果的是使用opencv經過形變後的顯示效果比MFC下的StretchBlt展開好看,也不知道這二種的效率如何。
6.小結
操作資料和演算法實現時都是用opencv來實現,當顯示時,構造CImage顯示到windows系統的視窗上。不得不說,MFC,特別是高版本的MFC,學習起來相當困難。一方面,它本身做的十分複雜,需要很長時間熟悉它才能按照你的想法實現想要的東西。另一方面,缺乏好書來指導,比如說CImage類的用法,我是通過多種途徑和以前的積累才實現了最基礎的使用方法。雖然最後給出的代碼不多,但讓代碼不出錯,弄明白它的簡單機制卻花費很長時間。到底CImage類+CPictureCtrl是否適合用來顯示映像,特別是對即時影像處理的顯示,還需要進一步考證。本人對MFC瞭解有限,都是邊寫程式邊學,有些機制也不是很明白。。歡迎大家討論和完善。