OpenCV Mat对象与CImage对象间的数据传输实例

在用MFC写图像处理程序时,使用OpenCV可以做到事半功倍。但是,如果使用OpenCV4.0或OpenCV4.0以后版本,要显示图像可能会遇到麻烦,因为OpenCV去掉了原有的cvGetWindowHandle()函数,没法再用cvGetWindowHandle()函数来获取OpenCV显示窗口的句柄,也就是说,没法用SetWindowPos()来将OpenCV显示窗口嵌入MFC窗口中。既然OpenCV的显示窗口嵌入MFC窗口比较麻烦,我们可以用MFC来显示图像。有一个ATL/MFC 共享类CImage,该类有多个可用于图像显示的成员函数。OpenCV的图像处理功能强大,可以用OpenCV处理数据,然后从内存中将OpenCV Mat对象的图像数据传送到CImage对象中,用CImage的成员函数来显示图像。如何将OpenCV Mat对象的图像数据传送到CImage对象中呢?下面写一个简单的图像处理程序,来做演示。

在VS2022中新建一个对话框程序,对话框界面如下:

该对话框的每一个按钮,我都已经给它添加了事件处理函数,暂不去看该函数的代码,不看该程序的代码,先看看该程序的试运行的效果。试运行,结果如下:

点击打开图像,弹出打开对话框:

点击"打开"按钮,结果如下:

试一试,打开别的格式的图像。点击打开图像按钮,选中1.webp

点击对话框中的"打开"按钮,打开图像如下:

点击打开图像按钮,选中2.bmp,

点击对话框中的"打开"按钮,打开图像如下:

点击打开图像按钮,选中a.jpeg,

点击对话框中的"打开"按钮,打开图像如下:

点击打开图像按钮,选中2.png,

点击对话框中的"打开"按钮,打开图像如下:

可见常见图像格式都可打开。

测试一下改变亮度

为看得更清楚,打开一张更大的图片:

Step 1 在改变亮度按钮旁的编辑框中输入-50,

点击"改变亮度"按钮,结果如下:

可见图像变暗了。

Step 2 在改变亮度按钮旁的编辑框中输入30,

点击"改变亮度"按钮,结果如下:

图像明显变亮了,说明改变亮度是有效的。

测试一下磨皮

打开一张较粗糙的图片,如下:

这张图像有点小,看不太清楚,把它放大一倍。在"图像比例缩放"按钮旁的输入框中输入2:

再点击"图像比例缩放按钮",结果如下:

这个脸麻点够多了,需要磨皮一下。点击"磨皮"按钮,结果如下:

再点击两次"磨皮"按钮,结果如下:

再点击4次"磨皮"按钮,结果如下:

这样看起来好多了,但图像变得有些不清晰,在"增减对比度"按钮旁的按钮中输入1.1,然后再点击"增减对比度"按钮,结果如下:

再点击2次"磨皮"按钮,结果如下:

再点击一次"增减对比度"按钮,结果如下:

在"图像比例缩放"按钮旁的框中输入0.5,点击"图像比例缩放"按钮,结果如下:

测试转成灰度图

点击"转成灰度图"按钮,结果如下:

其它图像处理功能就不逐一测试了,要实现这些功能,单用MFC其代码量是很大的,这里由于是用了OpenCV,代码量大大减小,有的仅需几行代码即可实现。下面讨论一下程序代码。

"打开图像"按钮事件处理函数,代码如下:

void CCImageMatTestDlg::OnBnClickedOpen()
{
	// TODO: 在此添加控件通知处理程序代码
	CFileDialog fdlg(TRUE, NULL, NULL, OFN_HIDEREADONLY | OFN_OVERWRITEPROMPT, _T("All files(*.*)|*.*||"));
	if (fdlg.DoModal() == IDOK)
	{
		m_Path = fdlg.GetPathName();
		m_strEx = fdlg.GetFileExt();
		m_strName = fdlg.GetFileName();

		m_Path.ReleaseBuffer();
		m_strEx.ReleaseBuffer();
		m_strName.ReleaseBuffer();
	
		if(m_strEx == "BMP" || m_strEx == "bmp" || m_strEx == "dib" || m_strEx == "TIF" || m_strEx == "tif" || m_strEx == "tiff" || m_strEx == "PNG" || m_strEx == "png" 
			|| m_strEx == "jpg"|| m_strEx == "JPG"|| m_strEx == "jpe" || m_strEx == "jpeg" || m_strEx == "jp2"  || m_strEx == "webp" || m_strEx == "avif" || m_strEx == "pbm"
			|| m_strEx == "pgm"  || m_strEx == "ppm" || m_strEx == "pxm" || m_strEx == "pnm" || m_strEx == "pfm" || m_strEx == "sr" || m_strEx == "ras" || m_strEx == "exr"
			|| m_strEx == "hdr" || m_strEx == "pic")
		{
			m_str = CT2A(m_Path);
			src = imread(m_str);
			dst = src;
			if (src.empty())
				MessageBox(_T("打开图像文件失败!"));
			else
			{
				MatToCImage(dst, mImage);	//send Mat object data to CImage objiect
				DispalyImage(mImage);		//dispaly image
			}
		}
		else
		{
			MessageBox(_T("你要打开的文件不是本程序支持的图像文件!",MB_OK));
		}
	}
}

其代码量很小,用到了MFC的文件对话框,OpenCV的图像文件读入函数imread(),最后用到了自定义的Mat对象数据传送到CImage 对象函数MatToCImage(),及自定义的图像显示函数DispalyImage()。

Mat对象数据传送到CImage 对象函数, 其代码如下:

void CCImageMatTestDlg::MatToCImage(Mat& src, CImage& dst) {
	// 确保Mat不为空且是可支持的类型
	if (src.empty() || (src.type() != CV_8UC3 && src.type() != CV_8UC1)) {
		return;
	}
	// 如果CImage对象有附加图像就分离并销毁图像
	if (!dst.IsNull())
		dst.Destroy();

	//创建CImage对象附加图像,需与源图像大小类型一致
	dst.Create(src.cols, src.rows, 8 * src.channels());

	if (src.channels() == 1)
	{
		//将源位图转成八位灰度图时,CImage对象需用到颜色表,需定义一个RGBQUAD数组,并填充该数组
		RGBQUAD* colorTable = new RGBQUAD[256];
		for (int i = 0; i < 256; i++)
		{
			colorTable[i].rgbRed = i;
			colorTable[i].rgbGreen = i;
			colorTable[i].rgbBlue = i;
		}

		//设置颜色表RGB分量值
		dst.SetColorTable(0, 255, colorTable);
	}
	
	int rows = src.rows;
	int cols = src.cols;
	uchar channels = src.channels();
	//内存中的数据传送,注意这里是逐行传送。
	for (int i = 0; i < rows; i++) 
	{
		memcpy(dst.GetPixelAddress(0, i), src.ptr<uchar>(i), cols * channels);
	}
}

该函数有几个重点,第一,要将Mat对象的图像数据传向 CImage对象,必须将CImage对象的原有解绑可以用Destroy()或Detach()函数。第二,同时需创建一张与Mat对象载入图像同样大小的图像用以接收传入数据。第三,创建设置颜色表,如果Mat对象载入的是8位位图(灰度图),可以不创建设置颜色表,如果将Mat对象载入的图像转成灰度图,没有颜色表就没法显示。第四,使用Memcpy()将Mat对象中的图像数据Copy到CImage对象的图像数据存储内存中。下面将MatToCImage(Mat& src, CImage& dst)函数中的颜色表部分注释掉,测试一下效果。注释后的代码如下:

void CCImageMatTestDlg::MatToCImage(Mat& src, CImage& dst) {
	// 确保Mat不为空且是可支持的类型
	if (src.empty() || (src.type() != CV_8UC3 && src.type() != CV_8UC1)) {
		return;
	}
	// 如果CImage对象有附加图像就分离并销毁图像
	if (!dst.IsNull())
		dst.Destroy();

	//创建CImage对象附加图像,需与源图像大小类型一致
	dst.Create(src.cols, src.rows, 8 * src.channels());

	/*
	if (src.channels() == 1)
	{
		//将源位图转成八位灰度图时,CImage对象需用到颜色表,需定义一个RGBQUAD数组,并填充该数组
		RGBQUAD* colorTable = new RGBQUAD[256];
		for (int i = 0; i < 256; i++)
		{
			colorTable[i].rgbRed = i;
			colorTable[i].rgbGreen = i;
			colorTable[i].rgbBlue = i;
		}

		//设置颜色表RGB分量值
		dst.SetColorTable(0, 255, colorTable);
	}
	*/

	int rows = src.rows;
	int cols = src.cols;
	uchar channels = src.channels();
	//内存中的数据传送,注意这里是逐行传送。
	for (int i = 0; i < rows; i++) 
	{
		memcpy(dst.GetPixelAddress(0, i), src.ptr<uchar>(i), cols * channels);
	}
}

试运行,打开图像,结果如下:

可正常显示,点击"转成灰度图"按钮,结果如下:

没法正常显示图像。打开一张灰度图:

结果如下:

点击"转成灰度图"按钮,结果如下:

同样没法显示。

图像显示函数其代码如下:

void CCImageMatTestDlg::DispalyImage(CImage mImage)
{
	// TODO: 在此处添加实现代码.
	if (mImage.IsNull())
		MessageBox(L"No Image to Display!",L"系统提示",MB_ICONWARNING | MB_OK);
	else
	{
		//
		Invalidate();
		OnPaint();

		CClientDC dc(this);
		mImage.BitBlt(dc.GetSafeHdc(), 0, 0, SRCCOPY);
	}
}

该函数使用了BitBlt()函数来显示图像,在前面调用了Invalidate()及OnPaint()函数,目的是刷新窗口,避免图像重叠。

上面每一个图像处理的函数都调用了MatToCImage()函数与DispalyImage()函数,其它函数与本主题无关这里就不逐一解说了。该示例的源代码已上传到CSDN,如果要查看其源代码可以去下载。示例程序是基于VS2022及OpenCV4.8,在Win10 平台调试通过,OpenCV4.8放在D盘下。示例程序的源代码的下载链接为:https://download.csdn.net/download/billliu66/89248781

相关推荐
IT古董21 分钟前
【漫话机器学习系列】017.大O算法(Big-O Notation)
人工智能·机器学习
凯哥是个大帅比21 分钟前
人工智能ACA(五)--深度学习基础
人工智能·深度学习
m0_7482329240 分钟前
DALL-M:基于大语言模型的上下文感知临床数据增强方法 ,补充
人工智能·语言模型·自然语言处理
szxinmai主板定制专家1 小时前
【国产NI替代】基于FPGA的32通道(24bits)高精度终端采集核心板卡
大数据·人工智能·fpga开发
海棠AI实验室1 小时前
AI的进阶之路:从机器学习到深度学习的演变(三)
人工智能·深度学习·机器学习
机器懒得学习1 小时前
基于YOLOv5的智能水域监测系统:从目标检测到自动报告生成
人工智能·yolo·目标检测
QQ同步助手1 小时前
如何正确使用人工智能:开启智慧学习与创新之旅
人工智能·学习·百度
AIGC大时代1 小时前
如何使用ChatGPT辅助文献综述,以及如何进行优化?一篇说清楚
人工智能·深度学习·chatgpt·prompt·aigc
流浪的小新1 小时前
【AI】人工智能、LLM学习资源汇总
人工智能·学习
martian6652 小时前
【人工智能数学基础篇】——深入详解多变量微积分:在机器学习模型中优化损失函数时应用
人工智能·机器学习·微积分·数学基础