本文介紹在MFC框架下,使用opencv的解碼函數對圖片進行解碼,並最終顯示到窗口。在此過程中,遇到了圖片顯示時的大小問題,以及閃爍問題,本文將一一解決。
【顯示圖片】
1. 在CImageProcessView::OnDraw(CDC* pDC) 中寫繪制圖片的代碼
我們已經打開圖片時,利用opencv對圖片文件進行了解碼,圖像數據已經在src_image中持有,現在需要把src_image中的數據繪制到窗口。
2 {
3 CImageProcessDoc* pDoc = GetDocument();
4 ASSERT_VALID(pDoc);
5 if (!pDoc)
6 return;
7
8 // TODO: add draw code for native data here
9 Mat & image = pDoc->src_image;
10 }
2. 將Mat轉化成CImage
Mat是表示圖像數據的一個矩陣,它不能直接繪制到窗口DC,通過google,我發現atl的一個類CImage有繪制到DC的方法,所以只需要把Mat在顯示之前先轉化成CImage,代碼如下:
2 {
3 int width = mat.cols;
4 int height = mat.rows;
5 int channels = mat.channels();
6
7 cImage.Destroy();
8 cImage.Create(width,
9 height,
10 8*channels );
11
12 uchar* ps;
13 uchar* pimg = (uchar*)cImage.GetBits();
14 int step = cImage.GetPitch();
15
16 for ( int i = 0; i < height; ++i)
17 {
18 ps = (mat.ptr<uchar>(i));
19 for ( int j = 0; j < width; ++j )
20 {
21 if ( channels == 1 ) // gray
22 {
23 *(pimg + i*step + j) = ps[j];
24 }
25 else if ( channels == 3 ) // color
26 {
27 for ( int k = 0 ; k < 3; ++k )
28 {
29 *(pimg + i*step + j* 3 + k ) = ps[j* 3 + k];
30 }
31 }
32 }
33 }
34
35 }
3. 將圖片顯示在窗口DC
2 if (image.empty())
3 {
4 return;
5 }
6 CImage cimage;
7 ImageUtility::MatToCImage(image,cimage);
8 cimage.Draw(pDC->GetSafeHdc(), 0, 0,cimage.GetWidth(),cimage.GetHeight(),
9 0, 0,cimage.GetWidth(),cimage.GetHeight());
終於圖片可以顯示出來了,如下圖:
【fit圖片到窗口大小】
從上面的結果來看,顯示是顯示出來了,但是效果不好,因為圖片比較大,超過了窗口大小,所以在繪制時,需要做一個縮放,縮放到適合窗口顯示的大小,縮放之前,需要先得到窗口大小。
1. override CImageProcessView的OnSize
2 {
3 nWidth = cx;
4 nHeight = cy;
5 CView::OnSize(nType, cx, cy);
6 // TODO: Add your message handler code here
7 }
2. 將圖像縮放到適合窗口顯示的大小
2 int fixed_height = min(cimage.GetHeight(),nHeight);
3
4 double ratio_w = fixed_width / ( double)cimage.GetWidth();
5 double ratio_h = fixed_height / ( double)cimage.GetHeight();
6 double ratio = min(ratio_w,ratio_h);
7
8 int show_width = ( int)(ratio * cimage.GetWidth());
9 int show_height = ( int)(ratio * cimage.GetHeight());
10 int offsetx = (nWidth - show_width) / 2;
11 int offsety = (nHeight - show_height) / 2;
12 ::SetStretchBltMode(pDC->GetSafeHdc(), COLORONCOLOR);
13 cimage.StretchBlt(pDC->GetSafeHdc(),
14 offsetx,offsety,
15 show_width,show_height, 0, 0,cimage.GetWidth(),cimage.GetHeight(),
16 SRCCOPY);
這些圖片能完整顯示了,而且是顯示在窗口的中間,如圖
【雙緩存去閃爍】
當我們resize窗口時,上面的程序會有劇烈的閃動,這誰能受得了了, 為了改進這一體驗,我們使用雙緩存方案。
1. override CImageProcessView的OnEraseBkgnd
這樣就不再畫背景畫刷到窗口DC了。
2 {
3 // TODO: Add your message handler code here and/or call default
4 // return CView::OnEraseBkgnd(pDC);
5 return TRUE;
6 }
2. 加入雙緩存
首先寫一個雙緩存類DoubleBufferSys
2 #include <windows.h>
3 class DoubleBufferSys
4 {
5 public:
6 DoubleBufferSys();
7 ~DoubleBufferSys();
8 void Resize( int width, int height);
9 void SetCDC(CDC * pDC);
10 CDC& GetMemDC();
11 void Present();
12 private:
13 CDC MemDC; // 首先定義一個顯示設備對象
14 CBitmap MemBitmap; // 定義一個位圖對象
15 CDC * pDC;
16 int width;
17 int height;
18
19 };
實現代碼如下
2 #include " DoubleBufferSys.h "
3 DoubleBufferSys::DoubleBufferSys()
4 {
5 MemDC.CreateCompatibleDC(NULL);
6 }
7
8 DoubleBufferSys::~DoubleBufferSys()
9 {
10 MemBitmap.DeleteObject();
11 MemDC.DeleteDC();
12 }
13 void DoubleBufferSys::Present()
14 {
15 pDC->BitBlt( 0, 0,width,height,&MemDC, 0, 0,SRCCOPY);
16 }
17 void DoubleBufferSys::Resize( int _width, int _height)
18 {
19 if (_width <= 0 || _height <= 0)
20 {
21 return;
22 }
23 width = _width;
24 height = _height;
25
26 MemBitmap.DeleteObject();
27 MemBitmap.CreateCompatibleBitmap(pDC,width,height);
28 CBitmap *pOldBit = MemDC.SelectObject(&MemBitmap);
29 MemDC.FillSolidRect( 0, 0,width,height,RGB( 0, 0, 0));
30 }
31
32 void DoubleBufferSys::SetCDC(CDC *_pDC)
33 {
34 pDC = _pDC;
35 }
36
37 CDC& DoubleBufferSys::GetMemDC()
38 {
39 return MemDC;
40 }
然后在CImageProcessView類中定義一個雙緩存系統對象DoubleBufferSys dbbufSys; 並在繪制函數中如下調用
2 dbbufSys.Resize(nWidth,nHeight);
3
4 Mat & image = pDoc->src_image;
5 if (image.empty())
6 {
7 dbbufSys.Present();
8 return;
9 }
10
11 .................
12
13 ::SetStretchBltMode(dbbufSys.GetMemDC(), COLORONCOLOR);
14 cimage.StretchBlt(dbbufSys.GetMemDC(),
15 offsetx,offsety,
16 show_width,show_height, 0, 0,cimage.GetWidth(),cimage.GetHeight(),
17 SRCCOPY);
18
19 dbbufSys.Present();
這樣就不會出現討厭的閃爍了,另外,DoubleBufferSys這個類可以復用,使用時按照如下流程即可
1. 設置CDC指針到DoubleBufferSys
2. Resize 雙緩存大小
3. 在雙緩存中的緩存中繪制
4. 將緩存中的內容Present(也就是拷貝到)顯存
這樣,一個比較完整的利用opencv解碼jpeg,並在窗口中顯示的小程序就完成了,以后可以基於此實現一些數字處理的算法。