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

目录

  • 一、前言
  • 二、Mat 和 BMP 数据结构
  • 三、转换实现思路
  • 四、Mat 转 BMP
  • 五、BMP转Mat
  • 六、一点体会

一、前言

? 格式转换很常见,其实在我实现了 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. BMP 数据结构

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

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

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

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

    调色板:RGBQUAD[n], 大小为 4*n 个字节,可以理解为一个数组,每个元素对应一种颜色,根据像素值索引颜色,通常调色板分为灰阶调色板和彩色调色板,顾名思义灰阶调色板为黑白图像使用,彩色调色板为彩色图像使用;使用调色板主要是为减小图片尺寸,只有1、4、8位图才需要调色板,对应的颜色种类是 2、16、256。16、24、32位图有足够的空间来自由组合出各种颜色,如果使用调色板反而会占用过多的空间,16位图可以是RGB(556)或者RGB(565),24位图是RGB(888),32位图是RGBA(8888)。

    像素数据:uchar * p。

三、转换实现思路

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

四、Mat 转 BMP

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

    /////////////////////////////////创建bmp空白图片///////////////////////////
    int depth = pMat->depth();
    int channels = pMat->channels();
    int width = pMat->cols;
    int height = pMat->rows;

    // 获取图像每个像素的位数
    // depth 代表每个通道元素的宽度,0:CV_8U
    uint pixelSize = (8 << (depth / 2)) * channels;

    // bmp规定每一行的长度必须是4的整数倍, pMat->cols * pixelSize / 8 为每一行有效数据长度
    // 其实OpenCV已经规定每行长度必须是4的整数倍,多以Mat也是
    //uint lineSize = ((width * pixelSize / 8) + 3) / 4 * 4;
    uint lineSize = width * pixelSize / 8;

    // 计算需要的调色板的大小,以便为bmp图像申请内存空间
    // 只有1、4、8位图才需要调色板(1->2, 4->16, 8->256),因为16、24、32位有足够的空间以便自由组合

    uint colorTableSize = 0;

    // 当前只支持8位图
    if (1 == pixelSize)
    {
        colorTableSize = 2 * sizeof(RGBQUAD);

        RGBQUAD* pColorTable = (RGBQUAD*)(&pBmp[sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER)]);
        for (int i = 0; i < 2; ++i)
        {
            // 灰阶调色板
            pColorTable[i].rgbRed = i;
            pColorTable[i].rgbGreen = i;
            pColorTable[i].rgbBlue = i;

            // 也可以创建彩色调色版
        }
    }
    else if (4 == pixelSize)
    {
        colorTableSize = 16 * sizeof(RGBQUAD);

        RGBQUAD* pColorTable = (RGBQUAD*)(&pBmp[sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER)]);
        for (int i = 0; i < 16; ++i)
        {
            // 灰阶调色板
            pColorTable[i].rgbRed = i;
            pColorTable[i].rgbGreen = i;
            pColorTable[i].rgbBlue = i;

            // 也可以创建彩色调色版
        }
    }
    if (8 == pixelSize)
    {
        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图片的调色板赋值/////////////////////////////////
    //// 8位图
    //if (8 == pixelSize)
    //{
    //  // 只有1、4、8位图才需要调色板(1->2, 4->16, 8->256),因为16、24、32位有足够的空间以便自由组合
    //  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图片的图像数据赋值/////////////////////////////////
    // BMP 和 Mat 数据都是自左向右,但是BMP是自下而上,Mat是自上而下,故而在数据转换时需要颠倒数据上下位置

    //uchar * pBmpData = pBmp + pFileHead->bfOffBits;
    uchar * pBmpData = pBmp + pFileHead->bfOffBits + height * lineSize; // 最后一行尾地址
    uchar * pMatData = pMat->data;  // 第一行首地址

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

    return 0;
}

五、BMP转Mat

int CAlgorithm::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;
}

六、一点体会

? 在一份 Mat 转 BMP 的代码基础上进行了改进,BMP 转 Mat 是自己实现的,做完之后发现实现功能其实不难,花了一个周末的时间,了解到了很多感兴趣的东西,期间也学到了很多。

原文地址:https://www.cnblogs.com/huluwa508/p/10983562.html

时间: 2024-10-07 20:13:57

Mat 图像和 BMP格式图像的相互转换的相关文章

bmp格式图像的读写函数(对一个开源代码的封装)

在网上看到一段读写bmp格式图像的代码,本文对这段代码分成两个函数封装起来方便使用,一个函数是读取bmp格式的图像,一个是向指定文件写入bmp格式的图像. 前提 我们不需要知道这段代码是如何读取bmp格式图像的,不需要知道bmp格式的图像时如何存储的,我们只需要知道有三个参数可以确定图像的尺寸大小,他们是图像的宽度.高度.通道数(例如灰度图像有一个通道,rgb图像有三个通道(rgb)).图像包含高度X宽度个像素,每个像素有相同的通道,他们在内存中按照一定的顺序存储,例如三通道bmp图像,在内存中

图像BMP格式介绍

1 图像BMP格式说明 BMP是一种与硬件设备无关的图像文件格式,使用非常广.它采用位映射存储格式,除了图像深度可选以外,不采用其他任何压缩,因此,BMP文件所占用的空间很大.BMP文件的图像深度可选lbit.4bit.8bit及24bit.BMP文件存储数据时,图像的扫描方式是按从左到右.从下到上的顺序. 由于BMP文件格式是Windows环境中交换与图有关的数据的一种标准,因此在Windows环境中运行的图形图像软件都支持BMP图像格式. 2 图像BMP文件结构以及结构说明 典型的BMP图像

数字图像处理 CImage类的使用与封装(jpg png gif tif bmp等格式图像的加载、数据读写、保存等功能)

引入CImage类的原因 原有的CBitmap 类只能处理BMP格式的图片,非常受限.而CImage可以处理JPGE.GIF.BMP.PNG等多种格式图片,扩展了图片处理功能且能与CBitmap 进行转换( 因为所载入的位图句柄都是HBITMAP,所以可相互转换),因此引入CImage类进行图像处理. CImage类简介 CImage是MFC和ATL共享的新类,它能从外部磁盘中调入一个JPEG.GIF.BMP和PNG格式的图像文件加以显示,而且这些文件格式可以相互转换. CImage提供增强型的

图像的一般格式

摘要:描述了各种图像文件结构,尤其着重讲述了PPM文件格式以及PPM文件的读写.因为最近在使用PPM文件做图像处理,以前并未接触到,所以会特别关注. 一般的图像文件结构主要都包含有文件头.文件体和文件尾等三部分: 文件头:软件ID.软件版本号.图像分辨率.图像尺寸.图像深度.彩色类型.编码方式.压缩算法 文件体:图像数据.彩色变换表 文件尾:用户名.注释.开发日期.工作时间   以上是一个大概的图像文件结构说明,实际的结构根据不同的格式其中的条目要细得多,结构也复杂得多,各个条目所占空间及条目间

在VC下显示JPEG、GIF格式图像的一种简便方法

在VC下显示JPEG.GIF格式图像的一种简便方法 一. 引言  JPEG图像压缩标准随然是一种有损图像压缩标准,但由于人眼视觉的不敏感,经压缩后的画质基本没有发生变化,很快便以较高的压缩率得到了广泛的认可.GIF格式虽然仅支持256色但它对于颜色较少的图像有着很高的压缩率,甚至超过JPEG标准,也得到了广泛的认同.但作为众多程序员的一个重要的开发工具--Microsoft Visual C++ 6.0的MFC库却仅对没有经过任何压缩的BMP位图文件有着良好的支持,可以读取.显示.存储甚至在内存

Agg:PPM格式图像生成

PPM是一个Linux下的简单图像格式,可以用Xnview打开.Agg的教程第一个,就是生成PPM格式的图像.PPM格式定义参见:http://en.wikipedia.org/wiki/Netpbm_format.以下是一个简单的画点程序: buffer.cpp #include <stdio.h> #include <string.h> #include "agg_rendering_buffer.h" const int HEIGHT = 480; con

转化图像大小和格式

function imageconverJPG( readdir ,QF) % 图像批量改大小函数 % 输入参数:readdir 隿¦?改大小的图像懿?¨目录(要求只有图像文件,格式无懿°?) % 写入图像格式 writetype = 'jpg'; %质量因子 %QF = 80; % 写入图像目录,直接附加在读取目录后加?png resize" writedir = [readdir,' conver ','QF=',num2str(QF),' ' writetype]; % 大小改变因子,有两

YUY2(YUV) 与 RGB 格式图片的相互转换 以及 基于YUY2(YUV)的blending

这是一个项目里使用的,API里从pool里取出的格式都是YUY2,但是图像处理的API库中要求都是jepg格式. YUY2经常用于电视制式以及许多摄像头的输出格式.而我们在处理时经常需要将其转化为RGB进行处理,这里简单介绍下YUY2(YUV)与RGB之间相互转化的关系: http://msdn2.microsoft.com/en-us/library/ms893078.aspx YUY2(YUV) To RGB: C = Y - 16 D = U - 128 E = V - 128 R = c

MFC中显示 .bmp格式的位图

最近在看VisualC++ 图像处理的书籍,表示一直在从基础做起,今天就记录一个简单功能的实现,显示.bmp格式的位图. 首先需要理解的是窗口创建的过程包括两个步骤:首先擦除窗口的背景,然后在对窗口进行重新绘制. 一般而言,对于单文档或多文档的MFC程序,显示图像的代码要放在OnDraw函数之中.刚刚说过,窗口重绘时,要先将窗口的背景擦除,也就是发送WM_ERASEBKGND消息,然后用OnEraseBkgnd()函数处理这个消息,所以我们的显示图像的代码也可以放在这个函数之中.当然,这里只是为