OpenCV作为主流的计算机视觉库,其图像处理、特征检测、视频分析等算法能力强大,被广泛应用于视觉项目开发。但 OpenCV的核心图像容器Mat属于纯数据矩阵格式,仅用于算法运算和数据处理,无法直接在对话框、静态图片框、视图等 MFC 常用控件中渲染显示。
而CImage是MFC 框架提供的原生图像类,完美适配 Windows 图形接口,支持在 MFC 各类控件上直接绘制、显示、保存图像(BMP/JPG/PNG 等格式),是VC++界面端展示图像的标准载体。因此,实现Mat与CImage的双向格式转换,是OpenCV 算法处理后的图像能在 MFC 对话框界面可视化的前提。
要想实现CImage与Mat的相互转换,首先需要明确的是两者数据布局、通道顺序及内存结构上的异同,才能构建出稳定、无错配的转换逻辑,从而使得OpenCV的算法结果能在MFC界面上完美呈现。
一、OpenCV::Mat的基础属性
OpenCV::Mat有五大基础属性:rows(行数)、cols(列数)、dims(维度)、channels(通道数)、type(数据类型)是描述矩阵形态的五大基础属性。同时,掌握Mat数据指针的正确访问方式,是实现格式互转的基本操作。
1.1 rows/cols --- 矩阵的行与列
行与列是二维矩阵的专属属性,对应的是图像空间尺寸:
- rows:矩阵的行数,对应的是图像的高度
- cols:矩阵的列数,对应的是图像的高度
注意,只有当该矩阵为二维矩阵时,这两个属性才有效。这两个参数直接决定了图像在MFC应用中显示的尺寸。
1.2 dims --- 矩阵维度
矩阵有多维的,但图像一般都是二维的,因此dims一般为2。然而OpenCV的三维重建技术可能需要描述三维图像。
1.3 channels --- 通道数
图像通道数描述的是每个像素点是由几个数值分量组成的,是区分彩色图和灰度图的关键。
- channels=1:灰度图,每个像素仅一个亮度值
- channels=3:彩色图,通常为BGR三通道
- channels=4:在BGR三通道的基础上增加透明度,采用BGRA格式
在图像格式转换中,通道数直接决定了图像创建的格式(8位/24位/32位),同时需要注意BGR与RGB之间的通道顺序转换。
1.4 type --- 数据类型
Mat的数据类型是定义矩阵中每个像素值的存储格式、位数、通道数的核心标识。Mat数据类型的命名是有固定统一规范的(CV_位数+数据类型+C通道数),如CV_8UC1。
位数表示每个数值占用的二进制位数;数据类型主要有三个选择(U表示无符号整数;S代表有符号整数;F代表浮点型);通道数表示每个像素包含的分量数(同1.3)
在图像格式转换中,常用的数据类型只有CV_8UC1、CV_8UC3、CV_8UC4三种。
二、OpenCV::Mat数据指针的访问方法
Mat::data是Mat像素数据的首地址指针,指向矩阵内存块的起始地址。用户可通过该指针直接读写内存。
Mat::ptr<uchar>[i]是第i行数据的起始指针,该指针能自动处理行对齐补齐问题,从而避免内存越界,是像素访问的最常用安全的方式。
Mat::step是指矩阵中每一行占用的总字节数,包含对齐补齐的字节。
三、CImage的基础属性
CImage是MFC提供的一个功能强大图像处理类,封装了加载、保存、绘制和像素访问等功能,是连接图像文件与界面控件的核心桥梁。
CImage最常用的成员包括Load、Create、Destory等用于生命周期管理;Draw、StretchBlt用于图像显示;GetBits、GetPixelAddress用于直接像素操作。
CImage封装了获取图像基本信息的方法,是格式转换的关键依据:
- GetWidth()/GetHeight():获取图像的宽度和高度,等同于Mat的cols和rows;
- GetBPP():获取每像素的位数,决定了图像的颜色深度(8位,24位,32位),等同于Mat的channels;
- GetPitch:获取图像每行的字节跨度(包含内存对齐字节),等同于Mat::step
- GetBits():获取指向像素数据缓冲区的首指针,用于直接操作像素,等同于Mat的Mat::data,下面再详细介绍;
CImage提供了增强型的(DDB和DIB)位图支持,可以装入、显示、转换和保存多种格式的图像文件,包括BMP、GIF、JPG、PNG、TIF等
3.1 设备无关位图(DIB)
DIB是一种旨在保证图像在不同显示设备上保持一致外观的位图格式。其核心特点是不依赖于特定硬件的颜色格式、分辨率或调色板,而是通过内嵌的元信息(如颜色表、分辨率、位深度等)来实现跨平台、跨设备的图像一致性。颜色表即调色板。
CImage的调色板主要用于索引颜色模式的图像,如8位的BMP图像,这种图像最多支持256种颜色,通过颜色表映射像素值到实际的RGB颜色。调色板仅用于单通道图像,对于多通道图像,像素值通常直接存储颜色信息,无需通过颜色表确定颜色。
DIB的图像有两种存储方式:Top-Down与Bottom-Up。对于Bottom-Up位图,在内存中是从位图的底部行开始保存,然后一行接着一行,直到位图顶部。因而,内存空间的起始处为图像的左下角像素点。在GDI中,所有的位图都是Bottom-Up类型的位图。对于Top-Down位图,它在内存中的保存顺序是相反的。位图的顶部行保存在内存空间的开始处。因而,内存空间的起始处为图像的左上角像素点。DirectDraw编程采用是Top-Down类型位图。在位图结构体(BITMAPINFOHEADER.biHeight)中可设置该图像的存储方式, biHeight= TRUE,则表示Bottom-Up类型,反之为Top-Down类型。
3.2 CImage的调色板
CImage的调色板本质上是一个由256个RGBQUAD结构组成的颜色表,每个索引对应一组RGB颜色,决定了像素最终显示效果。
调色板主要应用于8位灰度图像展示,24位/32位的真彩色未采用调色板,像素直接存储RGB/RGBA值,无需调色板映射。
CImage可用函数GetColorTable读取调色板颜色表,并用SetColorTable设置调色板颜色表。
四、函数源码
cpp
void MatToCImage(Mat& mat, CImage& cImage)
{
int channels = mat.channels();
if (!(channels == 1 || channels == 3))
return;
int width = mat.cols;
int height = mat.rows;
cImage.Destroy(); //这一步是防止重复利用造成内存问题
cImage.Create(width, height, 8 * channels);
uchar* ps;
uchar* pimg = (uchar*)cImage.GetBits();//获取CImage的像素存贮区的指针
int step = cImage.GetPitch(); //每行的字节数,注意这个返回值有正有负
// 如果是1个通道的图像(灰度图像) DIB格式才需要对调色板设置
// CImage中内置了调色板,我们要对他进行赋值:
if (1 == channels)
{ RGBQUAD ColorTable[256];
for (int i = 0; i < 256; i++)
{
ColorTable[i].rgbBlue = (BYTE)i;
ColorTable[i].rgbGreen = (BYTE)i;
ColorTable[i].rgbRed = (BYTE)i;
ColorTable[i].rgbReserved = 0;
}
cImage.SetColorTable(0, 256, ColorTable);
}
for (int i = 0; i < height; i++)
{ ps = mat.ptr<uchar>(i);
for (int j = 0; j < width; j++)
{ if (1 == channels)
*(pimg + i * step + j) = ps[j];
else if (3 == channels)
{ *(pimg + i * step + j * 3) = ps[j * 3];
*(pimg + i * step + j * 3 + 1) = ps[j * 3 + 1];
*(pimg + i * step + j * 3 + 2) = ps[j * 3 + 2];
}
}
}
}