本文介绍在MFC框架下,使用opencv的解码函数对图片进行解码,并最终显示到窗口。在此过程中,遇到了图片显示时的大小问题,以及闪烁问题,本文将一一解决。
【显示图片】
1. 在CImageProcessView::OnDraw(CDC* pDC) 中写绘制图片的代码
我们已经打开图片时,利用opencv对图片文件进行了解码,图像数据已经在src_image中持有,现在需要把src_image中的数据绘制到窗口。
void CImageProcessView::OnDraw(CDC* pDC) { CImageProcessDoc* pDoc = GetDocument(); ASSERT_VALID(pDoc); if (!pDoc) return; // TODO: add draw code for native data here Mat & image = pDoc->src_image; }
Mat是表示图像数据的一个矩阵,它不能直接绘制到窗口DC,通过google,我发现atl的一个类CImage有绘制到DC的方法,所以只需要把Mat在显示之前先转化成CImage,代码如下:
void ImageUtility::MatToCImage( Mat &mat, CImage &cImage) { int width = mat.cols; int height = mat.rows; int channels = mat.channels(); cImage.Destroy(); cImage.Create(width, height, 8*channels ); uchar* ps; uchar* pimg = (uchar*)cImage.GetBits(); int step = cImage.GetPitch(); for (int i = 0; i < height; ++i) { ps = (mat.ptr<uchar>(i)); for ( int j = 0; j < width; ++j ) { if ( channels == 1 ) //gray { *(pimg + i*step + j) = ps[j]; } else if ( channels == 3 ) //color { for (int k = 0 ; k < 3; ++k ) { *(pimg + i*step + j*3 + k ) = ps[j*3 + k]; } } } } }
Mat & image = pDoc->src_image; if (image.empty()) { return; } CImage cimage; ImageUtility::MatToCImage(image,cimage); cimage.Draw(pDC->GetSafeHdc(),0,0,cimage.GetWidth(),cimage.GetHeight(), 0,0,cimage.GetWidth(),cimage.GetHeight());
终于图片可以显示出来了,如下图:
【fit图片到窗口大小】
从上面的结果来看,显示是显示出来了,但是效果不好,因为图片比较大,超过了窗口大小,所以在绘制时,需要做一个缩放,缩放到适合窗口显示的大小,缩放之前,需要先得到窗口大小。
1. override CImageProcessView的OnSize
void CImageProcessView::OnSize(UINT nType, int cx, int cy) { nWidth = cx; nHeight = cy; CView::OnSize(nType, cx, cy); // TODO: Add your message handler code here }
int fixed_width = min(cimage.GetWidth(),nWidth); int fixed_height = min(cimage.GetHeight(),nHeight); double ratio_w = fixed_width / (double)cimage.GetWidth(); double ratio_h = fixed_height / (double)cimage.GetHeight(); double ratio = min(ratio_w,ratio_h); int show_width = (int)(ratio * cimage.GetWidth()); int show_height = (int)(ratio * cimage.GetHeight()); int offsetx = (nWidth - show_width) / 2; int offsety = (nHeight - show_height) / 2; ::SetStretchBltMode(pDC->GetSafeHdc(), COLORONCOLOR); cimage.StretchBlt(pDC->GetSafeHdc(), offsetx,offsety, show_width,show_height,0,0,cimage.GetWidth(),cimage.GetHeight(), SRCCOPY);
这些图片能完整显示了,而且是显示在窗口的中间,如图
【双缓存去闪烁】
当我们resize窗口时,上面的程序会有剧烈的闪动,这谁能受得了了, 为了改进这一体验,我们使用双缓存方案。
1. override CImageProcessView的OnEraseBkgnd
这样就不再画背景画刷到窗口DC了。
BOOL CImageProcessView::OnEraseBkgnd(CDC* pDC) { // TODO: Add your message handler code here and/or call default //return CView::OnEraseBkgnd(pDC); return TRUE; }
#pragma once #include <windows.h> class DoubleBufferSys { public: DoubleBufferSys(); ~DoubleBufferSys(); void Resize(int width,int height); void SetCDC(CDC * pDC); CDC& GetMemDC(); void Present(); private: CDC MemDC; //首先定义一个显示设备对象 CBitmap MemBitmap;//定义一个位图对象 CDC * pDC; int width; int height; };
#include "stdafx.h" #include "DoubleBufferSys.h" DoubleBufferSys::DoubleBufferSys() { MemDC.CreateCompatibleDC(NULL); } DoubleBufferSys::~DoubleBufferSys() { MemBitmap.DeleteObject(); MemDC.DeleteDC(); } void DoubleBufferSys::Present() { pDC->BitBlt(0,0,width,height,&MemDC,0,0,SRCCOPY); } void DoubleBufferSys::Resize(int _width,int _height) { if (_width <=0 || _height <=0) { return; } width = _width; height = _height; MemBitmap.DeleteObject(); MemBitmap.CreateCompatibleBitmap(pDC,width,height); CBitmap *pOldBit = MemDC.SelectObject(&MemBitmap); MemDC.FillSolidRect(0,0,width,height,RGB(0,0,0)); } void DoubleBufferSys::SetCDC(CDC *_pDC) { pDC = _pDC; } CDC& DoubleBufferSys::GetMemDC() { return MemDC; }
dbbufSys.SetCDC(pDC); dbbufSys.Resize(nWidth,nHeight); Mat & image = pDoc->src_image; if (image.empty()) { dbbufSys.Present(); return; } ................. ::SetStretchBltMode(dbbufSys.GetMemDC(), COLORONCOLOR); cimage.StretchBlt(dbbufSys.GetMemDC(), offsetx,offsety, show_width,show_height,0,0,cimage.GetWidth(),cimage.GetHeight(), SRCCOPY); dbbufSys.Present();
这样就不会出现讨厌的闪烁了,另外,DoubleBufferSys这个类可以复用,使用时按照如下流程即可
1. 设置CDC指针到DoubleBufferSys
2. Resize 双缓存大小
3. 在双缓存中的缓存中绘制
4. 将缓存中的内容Present(也就是拷贝到)显存
这样,一个比较完整的利用opencv解码jpeg,并在窗口中显示的小程序就完成了,以后可以基于此实现一些数字处理的算法。