在 MFC 框架中,有什麼方法能直接將 OpenCV 2.0 庫中 Mat 格式的圖片傳遞到 Picture Control(圖片控制項)顯示?
本人現在在做一個目標檢測的項目,選用了 OpenCV 2.0 庫和 MFC 框架上,在載入顯示圖片的時候,使用的是 Cvvimage 類里的函數 DrawPicToHDC( IplImage *img, UINT ID) ,將讀入的圖片自適應顯示到 Picture Control(圖片控制項)上,但這個類只支持 OpenCV 1.0的 IplImage 圖片格式操作,每次顯示完之後,還需要把 IplImage 轉為 OpenCV 2.0下的 Mat 格式。然後,對 Mat 格式進行處理並檢測, 又要重新將 Mat 轉為 IplImage ,又要再使用 DrawPicToHDC 將 檢測結果傳遞到畫板上 。
請問在 MFC 框架中,有什麼方法能直接將 Mat 格式的圖片傳遞到 Picture Control(圖片控制項)顯示,而不在 IplImage 和 Mat 兩種圖片格式上反覆轉換了?
另外一個方法,也可以把OpenCV的窗口添加到PictureControl裡面,這樣你可以繼續使用OpenCV的setMouseCallback直接對PictureControl進行滑鼠操作。首先在你的OnInit函數中添加如下
namedWindow("view", WINDOW_AUTOSIZE);
HWND hWnd = (HWND) cvGetWindowHandle("view");
HWND hParent = ::GetParent(hWnd);
::SetParent(hWnd, GetDlgItem(IDC_STATIC_CV)-&>m_hWnd);
::ShowWindow(hParent, SW_HIDE);
上面就是打開一個名為view的OpenCV窗口,並將其放置到一個ID為IDC_STATIC_CV的PictureControl裡面,這個控制項的類型需要是rectangle。當你在需要顯示某圖片的時候,就像在OpenCV裡面顯示一樣就可以了。
imshow("view", mat);
Transformation of OpenCV image to MFC image in MSVC project
@jie wu 的方法更優雅,推薦!OpenCV之前接觸過,但不是很熟悉。我覺得最簡單的方法是,可以用Windows API函數StretchDIBits直接在控制項上畫就行了。因為用OpenCV載入圖像後,IplImage這個結構體裡面會包含圖像的信息以及指向圖像緩衝區的指針,StretchDIBits是可以指針指向的圖像緩衝區畫到DC上的。
一直以來,我使用的方法都是shiqiyu在opencvchina上面提供的引入directshow,並且採用cvvimage和cameraDs的方法。這個方法雖然在xp/win7/win8下面都能夠成果使用,但是一直以來我都沒有動機去深入看一看這個方法。這次在知乎上面看到 jie wu 提出的「將Opencv窗口添加到PictureControl」中的方法,感到思路很好,進行了具體實現
http://pan.baidu.com/s/1nuixdhR
具體可以看代碼,我帖一些主要代碼void CMfcRibbonTemplateView::OnInitialUpdate()
{CFormView::OnInitialUpdate();GetParentFrame()-&>RecalcLayout();ResizeParentToFit();//根據控制項的大小設置初始幀的大小CRect rect;GetDlgItem(IDC_PBSRC) -&>GetClientRect( rect ); // 獲取控制項尺寸位置m_lframe = Mat::zeros(rect.Height(),rect.Width(),CV_8UC3);GetDlgItem(IDC_PBSRC) -&>GetClientRect( rect );m_rframe = Mat::zeros(rect.Height(),rect.Width(),CV_8UC3);
//綁定Mat到Picturebox上去namedWindow("src",WINDOW_AUTOSIZE);HWND hWnd = (HWND)cvGetWindowHandle("src");HWND hParnt = ::GetParent(hWnd);::SetParent(hWnd,GetDlgItem(IDC_PBSRC)-&>m_hWnd);::ShowWindow(hParnt,SW_HIDE);namedWindow("dst",WINDOW_AUTOSIZE);hWnd = (HWND)cvGetWindowHandle("dst");hParnt = ::GetParent(hWnd);::SetParent(hWnd,GetDlgItem(IDC_PBDEST)-&>m_hWnd);
::ShowWindow(hParnt,SW_HIDE);}void CMfcRibbonTemplateView::OnSize(UINT nType, int cx, int cy){CFormView::OnSize(nType, cx, cy);CWnd* pwndsrc = GetDlgItem(IDC_PBSRC);CWnd* pwnddst = GetDlgItem(IDC_PBDEST);//計算出長寬,這裡的長寬是按照比例的,圖像居中顯示
int iblank = 15; //邊界空餘int iwidth = cx/2-iblank*2;int iheight =(int)(iwidth*0.75);if (pwndsrc-&>GetSafeHwnd() pwnddst-&>GetSafeHwnd()){pwndsrc-&>MoveWindow(iblank,(cy-iheight)*0.4,iwidth,iheight);pwnddst-&>MoveWindow(cx/2+iblank,(cy-iheight)*0.4,iwidth,iheight);}}resize(dst,dst,Size(rect.Width(),rect.Height()));
imshow("src",dst);}總體感到jie wu 提出的方法,對於解決比較簡單的問題,的確是不錯的(我記得halcon生成能夠被csharp調用的代碼的時候,好像採用的就是類似的方法)。但是它本身存在以下問題:1、在窗體初始化的時候會有一個黑框彈出來,應該是nameWindow的效果;2、在沒有圖片的時候,會自動將Picturebox的背景繪製成為灰色,而且好像不好控制;3、僅僅是imshow還不能完成全部的調用,比如控制項的大小可能還會變化,那麼就需要用showimage重新進行封裝。如果加上這些7788的東西,那麼最後調用起來,也是比較複雜的。但是jie wu的方法有一個天生的優點,就是可以調用high gui的callback機制,這個是非常強的東西,能夠省不少麻煩事情。這裡只是我粗淺的認識,若有不對之處,歡迎批評指正。轉一個自己四年前寫的,雖然描述很多,但使用會很簡單。
原文地址(論壇改版過,導致有些是亂碼)
http://www.opencv.org.cn/forum.php?mod=viewthreadtid=15224page=1#pid111336自從開始學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 { ColorTable.rgbBlue = (BYTE)i; //BYTE和uchar一回事,但MFC中都用它 ColorTable.rgbGreen = (BYTE)i; ColorTable.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 { pS=img.ptr(i); for(int j=0;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 { pS=img.ptr(i); for(int j=0;j { for(int k=0;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了解有限,都是邊寫程序邊學,有些機制也不是很明白。。歡迎大家討論和完善。首先感謝 @Vinjn張靜的回答.
有一個 匿名用戶 給出的代碼也比較簡潔.除了直接使用opencv的對話框句柄之外.其它方法的原理都是把Mat的data加個MFC的數據頭,然後顯示出來.但是如果MFC的控制項框比Mat圖片小的時候,顯示會出錯.圖片會錯開.這點已經有人注意到了,並給出了解決方案, @小明採用的方法是手動4位元組對齊.不過代碼格式沒排版,比較亂.在網上搜了一下,大家為了解決這個圖片錯位的問題一般手動4位元組對齊./********我給出的改進方案*********/cvtColor(matImg,matImg,CV_BGR2BGRA); // matImg是你要顯示的Mat圖片
下面是詳細解釋,看懂了上面的代碼就不用往下看了:
CV_BGR2RGBA,也就是把BGR轉成RGBA格式,如此一來1個像素就是4個位元組了.為啥一定要4個位元組呢?
Windows中顯示圖像存在一個4位元組對齊的問題,也就是每一行的位元組數必須是4的倍數.而Mat的數據是連續存儲的.一般Mat的數據格式為BGR,也就是一個像素3個位元組,假設我的圖片一行有5個像素,那一行就是15個位元組,這不符合MFC的數據對齊方式,如果我們直接把Mat的data加個數據頭再顯示出來就可能會出錯.手動4位元組對齊,就是計算每行的位元組是不是4的倍數,不是的話,在後面補0但是我們把圖片轉成RGBA之後,一個像素就是4個位元組,不管你一行幾個像素,一直都是對齊的.支持輸入 單通道灰度 / 3通道BGR / 4通道BGRA.如果想傳入RGB,把case 3那裡改一下,改成CV_RGB2BGRA.其實也沒必要寫在函數裡面,只要在調用該函數之前自己先用cvtColor轉成BGRA再輸入就可以了.關於一個像素多少位元組的計算那裡可以刪掉,直接寫 8*通道數 就行了.當時是因為有種奇葩的格式是BGR555和BGR565,一個像素兩個位元組.img.channel()獲得的屬性竟然是2個通道, WTF??.原本就是為了支持傳入這玩意,不過轉換這事還是交給程序員比較好.CV_BGR5552BGRA , CV_BGR5652BGRA// 要顯示的圖 控制項的ID
void CXXXDlg::DrawMat(cv::Mat img, UINT nID)
{
cv::Mat imgTmp;
CRect rect;
GetDlgItem(nID)-&>GetClientRect(rect); // 獲取控制項大小
cv::resize(img, imgTmp, cv::Size(rect.Width(), rect.Height()));// 縮放Mat並備份
// 轉一下格式 ,這段可以放外面,
switch (imgTmp.channels())
{
case 1:
cv::cvtColor(imgTmp, imgTmp, CV_GRAY2BGRA); // GRAY單通道
break;
case 3:
cv::cvtColor(imgTmp, imgTmp, CV_BGR2BGRA); // BGR三通道
break;
default:
break;
}
int pixelBytes = imgTmp.channels()*(imgTmp.depth() + 1); // 計算一個像素多少個位元組
// 製作bitmapinfo(數據頭)
BITMAPINFO bitInfo;
bitInfo.bmiHeader.biBitCount = 8 * pixelBytes;
bitInfo.bmiHeader.biWidth = imgTmp.cols;
bitInfo.bmiHeader.biHeight = -imgTmp.rows;
bitInfo.bmiHeader.biPlanes = 1;
bitInfo.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
bitInfo.bmiHeader.biCompression = BI_RGB;
bitInfo.bmiHeader.biClrImportant = 0;
bitInfo.bmiHeader.biClrUsed = 0;
bitInfo.bmiHeader.biSizeImage = 0;
bitInfo.bmiHeader.biXPelsPerMeter = 0;
bitInfo.bmiHeader.biYPelsPerMeter = 0;
// Mat.data + bitmap數據頭 -&> MFC
CDC *pDC = GetDlgItem(nID)-&>GetDC();
::StretchDIBits(
pDC-&>GetSafeHdc(),
0, 0, rect.Width(), rect.Height(),
0, 0, rect.Width(), rect.Height(),
imgTmp.data,
bitInfo,
DIB_RGB_COLORS,
SRCCOPY
);
ReleaseDC(pDC);
}
void CxxxDlg::DrawcvMat(cv::Mat m_cvImg, UINT ID)//顯示mat在picture控制項中
{
cv::Mat img;
CRect rect;
GetDlgItem(ID)-&>GetClientRect(rect);
cv::Rect dst(rect.left,rect.top,rect.right,rect.bottom);
cv::resize(m_cvImg,img,cv::Size(rect.Width(),rect.Height()));
unsigned int m_buffer[sizeof(BITMAPINFOHEADER) + sizeof(RGBQUAD)*256];
BITMAPINFO* m_bmi = (BITMAPINFO*) m_buffer;
BITMAPINFOHEADER* m_bmih = (m_bmi-&>bmiHeader);
memset(m_bmih, 0, sizeof(*m_bmih));
m_bmih-&>biSize = sizeof(BITMAPINFOHEADER);
m_bmih-&>biWidth = img.cols;
m_bmih-&>biHeight = -img.rows; // 在自下而上的點陣圖中 高度為負
m_bmih-&>biPlanes = 1;
m_bmih-&>biCompression = BI_RGB;
m_bmih-&>biBitCount = 8 * img.channels();
CDC *pDC = GetDlgItem(ID)-&>GetDC();
::StretchDIBits(
pDC-&>GetSafeHdc(),
0, 0, rect.Width(), rect.Height(),
0, 0, rect.Width(), rect.Height(),
img.data,
(BITMAPINFO*) m_bmi,
DIB_RGB_COLORS,
SRCCOPY
);
ReleaseDC(pDC);
}
最近在寫 MFC(初學),善用搜索引擎
這個問題最後怎麼解決的?最後源碼是?
推薦閱讀:
※關於opencv中對齊圖片的問題?
※做增強現實AR,高通sdk與opencv有什麼區別。各有什麼利弊?
※有什麼好的機器視覺相關網站推薦呢?
※opencv Mat類型的轉換問題?
※在圖像處理中用Mat比IplImage 除了不用自己管理內存,Mat有其他的優勢沒?請熟悉OpenCV和圖像處理的大牛指點
TAG:OpenCV |