版权声明:本文为博主原创文章,转载需注明出处。 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
非常感谢您的阅读,如果您觉得这篇文章对您有帮助,欢迎扫码进行赞赏。