在用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