Mat 和 bmp图像格式的相互转换

前言

格式转换很常见,其实在我实现了 Mat 转 bmp 之后才发现原来 imwrite 接口可以直接将 Mat 数据保存为 .bmp图像,不过下文所谈及的转换是在内存中的转换,因为将图像发送给识别服务器时显然不能先将 Mat 保存为 .bmp 文件,然后再读该文件以二进制形式发送给识别服务,而是应该直接在内存中完成其转换。
一、Mat 和 bmp 数据结构
1、Mat数据结构

Mat 数据结构由矩阵头和指向矩阵数据的指针构成:Mat = 矩阵头 + 矩阵数据指针,下面代码是 Mat 类的代码片段,其中 uchar* data 比较常见,UMatData* u 为GPU版的 Mat,将图像交由 GPU 处理之前需要将 Mat 转为 UMat。

int flags;
//! the matrix dimensionality, >= 2
int dims;
//! the number of rows and columns or (-1, -1) when the matrix has more than 2 dimensions
int rows, cols;
//! pointer to the data
uchar* data;
//! helper fields used in locateROI and adjustROI
const uchar* datastart;
const uchar* dataend;
const uchar* datalimit;

//! custom allocator
MatAllocator* allocator;
//! and the standard allocator
static MatAllocator* getStdAllocator();
static MatAllocator* getDefaultAllocator();
static void setDefaultAllocator(MatAllocator* allocator);

//! internal use method: updates the continuity flag
void updateContinuityFlag();

//! interaction with UMat
UMatData* u;

MatSize size;
MatStep step;

2、Mat数据结构

Bitmap 是Windows显示图片的基本格式,Windows下图片的显示都必须先转化为位图然后进行显示。BitMap俗称位图,没有任何压缩,图片比较大,其它的图片格式都是在其基础上采用相应的压缩算法生成的。

Bitmap = 文件头 + 信息头 + 调色板 + 像素数据

文件头:BITMAPFILEHEADER,大小为14个字节

信息头:BITMAPINFOHEADER,大小为40个字节

调色板:RGBQUAD[n], 大小为 4*n 个字节,调色板的作用是为了减少图片尺寸。

比如一个16色灰度图像,每一种颜色取值为0-255,可以使用8bit,即一个字节来表示,一个30万像素的图片需要640x480x8bit数据量,如果采用调色板,16种颜色每种颜色都可以使用8bit数据来表示,罗列成为一个表格,每个像素只需要表示为其在调色板中的位置即可(共16个位置,使用4bit数据来表示),数据量为640x480x4+16x8bit,数据量远远小于不使用调色板的情况。

但是对于颜色种类很多的情况就不适用,比如真彩色共256x256x256=1677万多色,单单调色板就非常大,这种情况调色板就不适用了。目前来看对于单色,16色,256色的位图是用调色板方式进行处理,对于16位以及24位真彩色则直接按照rgb分量进行存储。16位图可以是RGB(556)或者RGB(565),24位图是RGB(888),32位图是RGBA(8888)。

像素数据:uchar * p。
二、实现思路

搞清楚了 Mat 和 BMP 的数据结构,转换也就很简单了,Mat转BMP时将 Mat 头里(也就是Mat类的相关成员变量,长宽等)赋值给 BMP 的文件头和信息头,数据指针指向的数据拷贝到 BMP 的数据部分;BMP转Mat也是同样的道理。
三、代码实现
1、Mat转bmp

int Mat2Bmp(cv::Mat * pMat, uchar * & pBmp, ulong & size)
{
if (!pMat)
{
return -1;
}

/创建bmp空白图片///
// #define CV_8U   0
// #define CV_8S   1
// #define CV_16U  2
// #define CV_16S  3
// #define CV_32S  4
// #define CV_32F  5
// #define CV_64F  6
    // depth 代表每个像素的每个通道的精度,就是每个通道用几个字节表示
int depth = pMat->depth(); 
int channels = pMat->channels();
int width = pMat->cols;
int height = pMat->rows;

// 获取图像每个像素的宽度
uint pixelSize = (8 << (depth / 2)) * channels; // pixelSize >= 8

colorTableSize = 256 * sizeof(RGBQUAD);

RGBQUAD* pColorTable = (RGBQUAD*)(&pBmp[sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER)]);

for (int i = 0; i < 256; ++i)
{
	// 灰阶调色板
	pColorTable[i].rgbRed = i;
	pColorTable[i].rgbGreen = i;
	pColorTable[i].rgbBlue = i;

	// 也可以创建彩色调色版
}

// bmp图片的大小, sizeof(BITMAPFILEHEADER) = 14, sizeof(BITMAPINFOHEADER) = 40
size = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER) + colorTableSize + height * lineSize;

pBmp = (uchar*)malloc(size);
if (!pBmp)
{
    return -2;
}
memset(pBmp, 0, size);

/为bmp图片的文件头赋值/
BITMAPFILEHEADER * pFileHead = (BITMAPFILEHEADER *)pBmp;
pFileHead->bfType = 0x4D42; // 0x4D42 代表 “BM”,位图标志
pFileHead->bfSize = size;
pFileHead->bfOffBits = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER) + colorTableSize; // 图像数据偏移量
																					 /为bmp图片的信息头赋值/
BITMAPINFOHEADER * pInfoHead = (BITMAPINFOHEADER *)(&pBmp[sizeof(BITMAPFILEHEADER)]);
pInfoHead->biSize = 40;    // 信息头的大小
pInfoHead->biWidth = width;
pInfoHead->biHeight = height;
pInfoHead->biPlanes = 1;   // 图像平面数,rgb为1?什么时候大于1?
pInfoHead->biBitCount = pixelSize; // 图像每个像素所占的位数
pInfoHead->biCompression = 0;  // 0:不压缩,1:REL8, 2:REL4
pInfoHead->biSizeImage = height * lineSize;  // 图像数据大小
pInfoHead->biXPelsPerMeter = 0;  // 水平方向像素/米,分辨率
pInfoHead->biYPelsPerMeter = 0;  // 垂直方向像素/米,分辨率
pInfoHead->biClrUsed = 0;   // BMP图像使用的颜色,0:表示使用全部颜色
pInfoHead->biClrImportant = 0;  // 重要的颜色数,0:所有的颜色都重要,当显卡不能够显示所有颜色时,辅助驱动程序显示颜色

///为bmp图片的调色板赋值/


/为bmp图片的图像数据赋值/
// BMP 和 Mat 数据都是自左向右,但是BMP是自下而上,Mat是自上而下,故而在数据转换时需要颠倒数据上下位置
uchar * pBmpData = pBmp + pFileHead->bfOffBits + height * lineSize; // 最后一行尾地址
uchar * pMatData = pMat->data; // 第一行首地址

							   // 将Mat从上往下一行一行拷给BMP
for (int i = 0; i < height; ++i)
{
	// 这里的 width 代表水平方向的像素个数,但是每个像素占1个字节,通过查表索引
	// 每次拷贝一行
	pBmpData -= lineSize;
	memcpy(pBmpData, pMatData, lineSize);
	pMatData += lineSize;
}

return 0;

}

2、bmp转Mat

int Bmp2Mat(uchar * pBmp, cv::Mat & mat)
{
// 获取文件头信息
if (!pBmp)
{
return -1;
}

BITMAPFILEHEADER * pFileHead = (BITMAPFILEHEADER *)pBmp;
if (pFileHead->bfType != 0x4D42)
{
	return -2;
}

BITMAPINFOHEADER* pInfoHead = (BITMAPINFOHEADER *)(pBmp + sizeof(BITMAPFILEHEADER));

long height = pInfoHead->biHeight;
long width = pInfoHead->biWidth;
ulong dataSize = pInfoHead->biSizeImage;

uchar * pMatData = (uchar *)malloc(dataSize);
memset(pMatData, 0, dataSize);

// bmp数据填充数据为至下而上、至左而右,mat为至上而下、至左而右
uint lineSize = width * (pInfoHead->biBitCount) / 8;

// 最后一行尾地址
uchar * pBmpData = (uchar *)(pBmp + pFileHead->bfSize); // 每个像素占一个字节
for (int h = 0; h < height; ++h)
{
	pBmpData -= lineSize;
	memcpy(pMatData, pBmpData, lineSize);	// 复制整行
	pMatData += lineSize;
}

// Mat数据指针移到最前面
pMatData -= dataSize;

mat.create(height, width, CV_MAKETYPE(CV_8U, (pInfoHead->biBitCount) / 8));
memcpy(mat.data, pMatData, dataSize);

free(pMatData);

return 0;

}

四、总结

难点在于理解bmp的数据结构,特别是调色板;另外bmp数据填充数据为至下而上、至左而右,mat为至上而下、至左而右也要注意。

  • 0
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值