如何使用QQ::Mat读写BMP图像

版权声明:本文为博主原创文章,转载需注明出处。 https://blog.csdn.net/qianqing13579/article/details/54316499

由于BMP格式的图像简单,读写BMP图的实现也相对容易,所以在构建自己的图像处理库的时候,都是使用BMP作为测试图像。首先说一BMP图像相关的知识。


BMP图像的组成

BMP图像由4个部分组成:
1. 文件头:BITMAPFILEHEADER(14字节)
2. 信息头:BITMAPINFOHEADER(40字节)
3. 颜色表:RGBQUAD(8位的为1024字节)(小于24位的图有,24位没有)
4. 图像数据

文件头 BITMAPFILEHEADER

typedef struct tagBITMAPFILEHEADER 
{ 
WORD  bfType; //位图文件的类型,该值必需是0x4D42,也就是字符'BM'。 
DWORD bfSize; //位图文件大小:文件头+信息头+颜色表+数据部分
WORD  bfReserved1; //保留字,为0,留做扩展,对实际的解码格式没有影响。
WORD  bfReserved2; //同上 
DWORD bfOffBits; //位图文件头到图像数据的偏移量,以字节为单位
} BITMAPFILEHEADER;
这个结构的长度是固定的,为14个字节(WORD为无符号16位整数,DWORD为无符号32位整数)。该结果体作为文件存储的时候用。

结构体的定义参考Windows.h

信息头BITMAPINFOHEADER

这部分告诉应用程序图像的详细信息,在屏幕上显示图像将会使用这些信息,它从文件的第15个字节开始。
typedef struct tagBITMAPINFOHEADER 
{
DWORD biSize; //BITMAPINFOHEADER结构大小,字节为单位 
LONG biWidth; //宽度(以像素为单位,不是每行字节数!!)
LONG biHeight; //高度(以像素为单位)
WORD biPlanes; //目标设备的级别,必须为1 
WORD biBitCount; //颜色深度,每个像素所需要的位数
DWORD biCompression; //位图的压缩类型
DWORD biSizeImage; //位图数据部分的大小,以字节为单位(4字节对齐)
LONG biXPelsPerMeter; //位图水平分辨率,每米像素数
LONG biYPelsPerMeter; //位图垂直分辨率,每米像素数
DWORD biClrUsed; //位图实际使用的颜色表中的颜色数
DWORD biClrImportant; //位图显示过程中重要的颜色数
} BITMAPINFOHEADER;

该结构体大小为40字节。

颜色表RGBQUAD

BMP只能存储单色(1位),16色(4位),256色(8位),和真彩色(24位)4种格式的数据,只有真彩色没有调色板(颜色表)。

8位的BMP中,每个像素的数值就是颜色表数组的下标。而24位的BMP图,每个像素的数值就是表示实际的像素值。

typedef struct tagRGBQUAD {
  BYTE    rgbBlue; 
  BYTE    rgbGreen; 
  BYTE    rgbRed; 
  BYTE    rgbReserved; 
} RGBQUAD;

图像数据

在8位BMP中,每个像素8位,存放的是颜色表的索引,24位每个像素24位,存放的是实际颜色的值,分为三个通道BGR。

要注意两点:

(1) 每一行的字节数必须是4的整倍数,如果不是,则需要补齐。也就是4字节对齐。
(2) bmp文件的数据从下到上,从左到右的。也就是说,从文件中最先读到的是图象最下面一行的左边第一个像素,然后是左边第二个像素……接下来是倒数第二行左边第一个像素,左边第二个像素……依次类推 ,最后得到的是最上面一行的最右一个像素。

下面给出实现


使用QQ::Mat读写图像

Bmp.h

//////////////////////////////////////////////////////////////////////////
// 读写BMP图  Bmp.h
// 2014-11-12,by QQ
//
// Please contact me if you find any bugs, or have any suggestions.
// Contact:
//      Telephone:15366105857
//      Email:[email protected]
//      Blog: http://blog.csdn.net/qianqing13579
//////////////////////////////////////////////////////////////////////////


#ifndef __BMP_H__
#define __BMP_H__
#include "Mat.h"
#include "CommonDefinition.h"
#include"BmpDefinition_Windows.h" // 在Linux中使用 BmpDefinition_Linux.h
#include <string>
using namespace std;

namespace QQ
{

// C++接口(支持8位单通道和3通道)
DLL_EXPORTS void ReadBmp(const string &fileName,Mat<uchar> &image);// 读BMP
DLL_EXPORTS void WriteBmp(const string &fileName, const Mat<uchar> &image);// 写BMP
DLL_EXPORTS void WriteMarkedBMP(const string &fileName, const Mat<uchar>& image);// 标记BMP图

// C接口
DLL_EXPORTS uchar *ReadBmp_C(const char * fileName, int &width, int &height, int &bitCount_PerPixel);
DLL_EXPORTS bool WriteBmp_C(uchar *image, int width, int height, const char * fileName, int bitCount_PerPixel);
DLL_EXPORTS bool WriteMarkedBmp_C(uchar *image, int width, int height, const char * filename);

}//namespace QQ
#endif

Bmp.cpp

#define DLLAPI_EXPORTS
#include "Bmp.h"
namespace QQ
{


void ReadBmp(const string &fileName, Mat<uchar> &image)
{
    //图像参数
    int width,height,bitCount_PerPixel;

    //读BMP
    uchar *data=ReadBmp_C(fileName.c_str(),width,height,bitCount_PerPixel);

    //设置图像头
    int numberOfChannels = bitCount_PerPixel >> 3;
    Mat<uchar> temp(height, width, numberOfChannels, data, true);

    image = temp;

    delete[] data;

}

void WriteBmp(const string &fileName, const Mat<uchar> &image)
{
    //写入bmp
    int bitCount_PerPixel=image.numberOfChannels<<3;
    WriteBmp_C(image.data,image.cols,image.rows,fileName.c_str(),bitCount_PerPixel);
}
void WriteMarkedBMP(const string &fileName, const Mat<uchar> &image)
{
    WriteMarkedBmp_C(image.data, image.cols, image.rows, fileName.c_str());
}

///读取8位或者24位Bmp
///返回原始图像数据,未对齐的数据
uchar *ReadBmp_C(const char * fileName,
    int &width, int &height,//图像大小(像素的宽度和高度)
    int &bitCount_PerPixel//返回图像的每像素位数
    )
{
    FILE *fp;
    BITMAPFILEHEADER bitmap_FileHeader;
    BITMAPINFOHEADER bitmap_InfoHeader;
    RGBQUAD *colorTable;
    bool isSuccess = true;

    width = height = 0;
    if ((fp = fopen(fileName, "rb")) == NULL)
        return NULL;

    // 读入文件头和信息头内的信息
    if (fread((void *)&bitmap_FileHeader, 1, sizeof(BITMAPFILEHEADER), fp) != sizeof(BITMAPFILEHEADER))
        isSuccess = false;
    if (fread((void *)&bitmap_InfoHeader, 1, sizeof(BITMAPINFOHEADER), fp) != sizeof(BITMAPINFOHEADER))
        isSuccess = false;
    if ((isSuccess == false) || (bitmap_FileHeader.bfOffBits<sizeof(BITMAPFILEHEADER)+sizeof(BITMAPINFOHEADER)))
    {
        fclose(fp);
        return NULL;
    }
    width = bitmap_InfoHeader.biWidth;
    height = bitmap_InfoHeader.biHeight;
    bitCount_PerPixel = bitmap_InfoHeader.biBitCount;//每像素位数
    int ucharCount_PerPixel = bitCount_PerPixel / 8;
    int dataSize = bitmap_InfoHeader.biSizeImage;

    // 读入颜色表(8位的有,24位的没有)
    if (bitCount_PerPixel == 8)
    {
        //申请颜色表所需要的空间,读颜色表进内存
        colorTable = new RGBQUAD[256];
        int count = fread(colorTable, sizeof(RGBQUAD), 256, fp);
        if (count != 256)//读取颜色表错误
        {
            isSuccess = false;
            return NULL;
        }
        //输出颜色表信息结构体
        /*FILE *outFile;
        fopen_s(&outFile, "D:output.txt", "w");
        for (int i = 0; i < 256; i++)
        {
        fprintf_s(outFile, "colorTable[%d]\n", i);
        fprintf_s(outFile, "\tB:%d\n", colorTable[i].rgbBlue);
        fprintf_s(outFile, "\tG:%d\n", colorTable[i].rgbGreen);
        fprintf_s(outFile, "\tR:%d\n", colorTable[i].rgbRed);
        }
        fclose(outFile);*/
    }


    // 读取图像数据   
    // 读取图像数据,由于BMP扫描方式是从下到上,所以BMP图像存储方式是从下到上,所以读的时候,需要倒着读取
    int ucharCount_PerLine = (width*ucharCount_PerPixel + 3) / 4 * 4;//存储中,每行字节数(4字节对齐)
    uchar *image_Src = new uchar[height*width*ucharCount_PerPixel];
    uchar temp;
    int extend = ucharCount_PerLine - width*ucharCount_PerPixel;//每行由于四字节对其而需要补充的字节数
    for (int i = 0; i<height; i++)
    {
        int readCount = fread(image_Src + (height - 1 - i)*width * ucharCount_PerPixel, sizeof(uchar), width* ucharCount_PerPixel, fp);
        if (readCount != width * ucharCount_PerPixel)
        {
            fclose(fp);
            delete[]image_Src;
            image_Src = NULL;
            return NULL;
        }
        for (int k = 0; k<extend; k++) // 扩充的数据
        {
            if (fread(&temp, sizeof(uchar), 1, fp) != 1)
            {
                fclose(fp);
                delete[] image_Src;
                image_Src = NULL;
                return NULL;

            }
        }
    }
    fclose(fp);
    return image_Src;
}



////写入原始图像数据,未对齐的数据
///8位或者24位
bool WriteBmp_C(uchar *imageData, //原始图像数据
    int width, int height, //Bmp图像大小
    const char *fileName,
    int bitCount_PerPixel//Bmp每个像素的位数
    )
{
    FILE * fp;
    BITMAPFILEHEADER bitmapFileHeader;
    BITMAPINFOHEADER bitmapInfoHeader;
    int colorTableSize = 0;
    int i;
    bool isSuccess = true;
    uchar p[4];//颜色表

    if ((fp = fopen(fileName, "wb")) == NULL)
    {
        return false;
    }

    // 写文件头fileHeader
    // 文件头+信息头+颜色表
    bitmapFileHeader.bfType = ((WORD)('M' << 8) | 'B');
    if (bitCount_PerPixel == 8)//8bit Bmp
    {
        colorTableSize = 1024;
    }
    bitmapFileHeader.bfOffBits = sizeof(BITMAPFILEHEADER)+sizeof(BITMAPINFOHEADER)+colorTableSize;
    int ucharCount_PerPixel = bitCount_PerPixel / 8;
    int ucharCount_PerLine = (width*ucharCount_PerPixel + 3) / 4 * 4;//4字节对齐
    bitmapFileHeader.bfSize = bitmapFileHeader.bfOffBits + ucharCount_PerLine*height;
    bitmapFileHeader.bfReserved1 = 0;
    bitmapFileHeader.bfReserved2 = 0;
    if (fwrite((void *)&bitmapFileHeader, 1, sizeof(BITMAPFILEHEADER), fp) != sizeof(BITMAPFILEHEADER))
        isSuccess = false;


    // 写信息头
    bitmapInfoHeader.biSize = 40;
    bitmapInfoHeader.biWidth = width;
    bitmapInfoHeader.biHeight = height;
    bitmapInfoHeader.biPlanes = 1;
    bitmapInfoHeader.biBitCount = bitCount_PerPixel;
    bitmapInfoHeader.biCompression = 0;
    bitmapInfoHeader.biSizeImage = height*ucharCount_PerLine;//图像数据部分,4字节对齐
    bitmapInfoHeader.biXPelsPerMeter = 0;
    bitmapInfoHeader.biYPelsPerMeter = 0;
    bitmapInfoHeader.biClrUsed = 0;
    bitmapInfoHeader.biClrImportant = 0;
    if (fwrite((void *)&bitmapInfoHeader, 1, sizeof(BITMAPINFOHEADER), fp) != sizeof(BITMAPINFOHEADER))
        isSuccess = false;

    // 写入颜色表(8位有颜色表,24位的没有颜色表)
    ///8位BMP图像数据部分存储的是颜色表的索引
    if (bitCount_PerPixel == 8)
    {
        for (i = 0, p[3] = 0; i<256; i++)//8位颜色表长度256,4位为16
        {
            p[3] = 0;
            p[0] = i;//B
            p[1] = i;//G
            p[2] = i; //R
            if (fwrite((void *)p, 1, 4, fp) != 4)
            {
                isSuccess = false;
                break;
            }
        }
    }


    // 写入图像数据
    // 由于BMP扫描方式是从下到上,所以BMP图像存储方式是从下到上,所以写的时候,需要倒着写
    int extend = ucharCount_PerLine - width * ucharCount_PerPixel;
    int writeCount;
    uchar *temp;
    if (extend == 0)
    {
        for (temp = imageData + (height - 1) * ucharCount_PerPixel * width; temp >= imageData; temp -= ucharCount_PerPixel * width)
        {
            writeCount = fwrite((void *)temp, 1, width * ucharCount_PerPixel, fp);
            if (writeCount != (unsigned int)(ucharCount_PerPixel * width))
                isSuccess = false; // 真实的数据
        }
    }
    else
    {
        for (temp = imageData + (height - 1) * ucharCount_PerPixel * width; temp >= imageData; temp -= ucharCount_PerPixel * width)
        {
            writeCount = fwrite((void *)temp, 1, width * ucharCount_PerPixel, fp);
            if (writeCount != (unsigned int)(ucharCount_PerPixel * width))
                isSuccess = false; // 真实的数据
            for (i = 0; i<extend; i++) // 扩充的数据,数据内容不限
            {
                writeCount = fwrite((void *)(temp + ucharCount_PerPixel* (width - 1)), 1, 1, fp);
                if (writeCount != 1)
                    isSuccess = false;
            }
        }
    }
    // Return;
    fclose(fp);
    return isSuccess;
}

//灰度图中用颜色标记信息
bool WriteMarkedBmp_C(uchar *image,
    int width, int height, //Bmp图像大小
    const char * filename
    )
{

    FILE * fp;
    BITMAPFILEHEADER bitmapFileHeader;
    BITMAPINFOHEADER bitmapInfoHeader;
    int colorTableSize = 0;
    int i;
    bool isSuccess = true;
    uchar colorTableItem[4];//颜色表项
    int bitCount_PerPixel = 8;//Bmp每个像素的位数

    if ((fp = fopen(filename, "w+b")) == NULL)
    {
        return false;
    }

    // 写文件头fileHeader
    // 文件头+信息头+颜色表
    bitmapFileHeader.bfType = ((WORD)('M' << 8) | 'B');
    if (bitCount_PerPixel == 8)//8bit Bmp
    {
        colorTableSize = 1024;
    }
    bitmapFileHeader.bfOffBits = sizeof(BITMAPFILEHEADER)+sizeof(BITMAPINFOHEADER)+colorTableSize;
    int ucharCount_PerPixel = bitCount_PerPixel / 8;
    int ucharCount_PerLine = (width*ucharCount_PerPixel + 3) / 4 * 4;//4字节对齐
    bitmapFileHeader.bfSize = bitmapFileHeader.bfOffBits + ucharCount_PerLine*height;
    bitmapFileHeader.bfReserved1 = 0;
    bitmapFileHeader.bfReserved2 = 0;
    if (fwrite((void *)&bitmapFileHeader, 1, sizeof(BITMAPFILEHEADER), fp) != sizeof(BITMAPFILEHEADER))
        isSuccess = false;

    // 写信息头
    bitmapInfoHeader.biSize = 40;
    bitmapInfoHeader.biWidth = width;
    bitmapInfoHeader.biHeight = height;
    bitmapInfoHeader.biPlanes = 1;
    bitmapInfoHeader.biBitCount = bitCount_PerPixel;
    bitmapInfoHeader.biCompression = 0;
    bitmapInfoHeader.biSizeImage = height*ucharCount_PerLine;//图像数据部分,4字节对齐
    bitmapInfoHeader.biXPelsPerMeter = 0;
    bitmapInfoHeader.biYPelsPerMeter = 0;
    bitmapInfoHeader.biClrUsed = 0;
    bitmapInfoHeader.biClrImportant = 0;
    if (fwrite((void *)&bitmapInfoHeader, 1, sizeof(BITMAPINFOHEADER), fp) != sizeof(BITMAPINFOHEADER))
        isSuccess = false;

    // 写入颜色表(8位有颜色表,24位的没有颜色表),注意通道顺序:BGR
    ///8位BMP图像数据部分存储的是颜色表的索引
    if (bitCount_PerPixel == 8)
    {
        for (i = 0; i < 256; i++)
        {
            colorTableItem[3] = 0;
            switch (i) //还可以定义其他颜色R = 0,B = 255, G = 255:青色
            {
            case 255://红色
            {
                         colorTableItem[0] = 0;
                         colorTableItem[1] = 0;
                         colorTableItem[2] = 255;//R
                         break;
            }
            case 254://绿色
            {
                         colorTableItem[0] = 0;
                         colorTableItem[1] = 255;//G
                         colorTableItem[2] = 0;
                         break;
            }
            case 253://蓝色
            {
                         colorTableItem[0] = 255;//B
                         colorTableItem[1] = 0;
                         colorTableItem[2] = 0;
                         break;
            }
            case 252://黄色
            {
                         colorTableItem[0] = 0;//B
                         colorTableItem[1] = 255;//G
                         colorTableItem[2] = 255;//R
                         break;
            }
            case 251://紫色
            {
                         colorTableItem[0] = 255;//B
                         colorTableItem[1] = 0;//G
                         colorTableItem[2] = 255;//R
                         break;
            }
            default:
            {
                       colorTableItem[0] = i;//B
                       colorTableItem[1] = i;//G
                       colorTableItem[2] = i; //R
                       break;
            }
            }
            if (fwrite((void *)colorTableItem, 1, 4, fp) != 4)
            {
                isSuccess = false;
                break;
            }
        }
    }


    // 写入图像数据
    // 由于BMP扫描方式是从下到上,所以BMP图像存储方式是从下到上,所以写的时候,需要倒着写进文件
    int extend = ucharCount_PerLine - width * ucharCount_PerPixel;
    uchar *temp;
    if (extend == 0)
    {
        for (temp = image + (height - 1) * ucharCount_PerPixel * width; temp >= image; temp -= ucharCount_PerPixel * width)
        {
            if (fwrite((void *)temp, 1, width * ucharCount_PerPixel, fp) != (unsigned int)(ucharCount_PerPixel * width))
                isSuccess = false; // 真实的数据
        }
    }
    else
    {
        for (temp = image + (height - 1) * ucharCount_PerPixel * width; temp >= image; temp -= ucharCount_PerPixel * width)
        {
            if (fwrite((void *)temp, 1, width * ucharCount_PerPixel, fp) != (unsigned int)(ucharCount_PerPixel * width))
                isSuccess = false; // 真实的数据
            for (i = 0; i < extend; i++) // 扩充的数据,数据内容不限
            {
                if (fwrite((void *)(temp + ucharCount_PerPixel* (width - 1)), 1, 1, fp) != 1)
                    isSuccess = false;
            }
        }
    }

    // Return;
    fclose(fp);
    return isSuccess;
}

}// namespace QQ

BmpDefinition_Windows.h

//////////////////////////////////////////////////////////////////////////
// Windows下Bmp图定义 Bmp_Definition_Windows.h
// 2014-11-13,by QQ
//
// Please contact me if you find any bugs, or have any suggestions.ww
// Contact:
//      Telephone:15366105857
//      Email:[email protected]
//      Blog: http://blog.csdn.net/qianqing13579
//////////////////////////////////////////////////////////////////////////


#ifndef __BMP_DEFINITION_WINDOWS_H__
#define __BMP_DEFINITION_WINDOWS_H__
//
//
/*************************Windows BMP 类型定义******************************/
typedef unsigned long       DWORD;
typedef int                 BOOL;
typedef unsigned char       BYTE;
typedef unsigned short      WORD;
typedef long                LONG;

#pragma  pack(1)//禁止VC字节对齐
typedef struct tagBITMAPFILEHEADER {
    WORD    bfType;
    DWORD   bfSize;
    WORD    bfReserved1;
    WORD    bfReserved2;
    DWORD   bfOffBits;
} BITMAPFILEHEADER;

typedef struct tagBITMAPINFOHEADER{
    DWORD      biSize;
    LONG       biWidth;
    LONG       biHeight;
    WORD       biPlanes;
    WORD       biBitCount;
    DWORD      biCompression;
    DWORD      biSizeImage;
    LONG       biXPelsPerMeter;
    LONG       biYPelsPerMeter;
    DWORD      biClrUsed;
    DWORD      biClrImportant;
} BITMAPINFOHEADER;

typedef struct tagRGBQUAD {
    BYTE    rgbBlue;
    BYTE    rgbGreen;
    BYTE    rgbRed;
    BYTE    rgbReserved;
} RGBQUAD;

#pragma pack()
//
//
//
#endif

测试程序

void TestReadAndWrite()
{
    //读取
    Mat<uchar> srcImage;//建立一个空图像
    ReadBmp("D:/Test.bmp", srcImage);

    //// 将第100行设置为0
    for (int i = 0; i <= srcImage.cols - 1; ++i)
    {
        srcImage.At<Vec3b>(100, i) = Vec3b(0,0,0);
    }

    //保存
    WriteBmp("D:/1.bmp", srcImage);


}

2017-1-10 18:35:33

非常感谢您的阅读,如果您觉得这篇文章对您有帮助,欢迎扫码进行赞赏。
这里写图片描述

猜你喜欢

转载自blog.csdn.net/qianqing13579/article/details/54316499
今日推荐