MFC中显示Mat格式图像——Mat转CImage方法

        Mat是opencv中的图像格式,如果想要图像在MFC中显示,将Mat格式转为MFC支持的CImage格式是一种较为优秀的解决方案,这篇博客就来介绍在MFC中显示Mat图像的方法。虽然原理相同,但博主还是要介绍两种方式:一种是通过子类化显示控件的方式进行显示,第二种是在父窗口中调用。如果你做的工程比较庞大,博主建议使用子类化控件的方法,当然,如果你是小工程,博主也依然建议你使用子类化的方法,这样不仅使代码结构简单封装良好,而且方便了控件的拓展。

        下面直接给出子类化的代码,里面注释已经很详细,直接读代码,所以无需多言。

public:
	void	ShowMat(cv::Mat &);

protected:
	cv::Mat	m_img;
	CImage	CI;

        如果你没有采用子类化的方法,代码差别极小,只是GetWindowRect的时候需要使用控件对象的指针,下面给出的是子类化的成员函数:

void CILabelControl::ShowMat(cv::Mat & img){
	m_img = cv::imread("temp.png");
	cv::swap(m_img, img);
	int w = img.cols;//宽   
	int h = img.rows;//高   
	int chinnels = img.channels();//通道数   
	CI.Destroy();//创建前,最好使用它,防止重复创建,程序崩溃   
	CI.Create(w, h, 8 * chinnels);

	//3.下来就是对CI进行赋值了,这里是最核心的地方,分二类讨论   
	//  (1)如果是1个通道的图像(灰度图像) DIB格式才需要对调色板设置   
	//  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(GetSafeHwnd());//通过变量得到dc比较复杂,但很好用   
	CPaintDC dc(pCWnd);
	Invalidate(false);
	CRect rect;
	this->GetWindowRect(&rect);  // m_pic.GetWind...
	this->ScreenToClient(&rect); 
	//SetStretchBltMode(dc.m_hDC, COLORONCOLOR);
	//这个需要百度看看为什么这样设置   
	CI.StretchBlt(dc.m_hDC, rect, SRCCOPY);
	//这里显示大小rect(CRect类型)也由自己定义,这个函数有许多重载函数   
	//图像显示的大小和效果,在你能显示出来后,可以慢慢考虑   

	//这里的控件的dc还可以由下面方式取得   
	//	CPaintDC dc(GetDlgItem(IDC_IMAGE));//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)为起点
}

        使用你的新类来在MFC中进行显示,在对话框的相应函数中使用如下代码,其中m_pic是你新控件类的实例:

	m_img = cv::imread("temp.png");
	m_pic.ShowMat(m_img);

如果不是采用子类化的方法,如果放在OnPaint中进行绘制,代码是几乎一样的,可能需要修改Rect区域,然后就是如果出现bug,可能需要注释掉Invalid(false); 就这么多。

        OK, See You Next Chapter!

发表评论