BMP文件格式
位图文件(Bitmap-File,BMP)格式是Windows操作系统中的标准图像格式。采用位映射存储,图像深度可选lbit、4bit、8bit及24bit,图像的扫描方式是按从左到右、从下到上的顺序,即阵列的第一个字节表示图像最后一行的第一个像素,默认的文件扩展名是bmp或者dib。
BMP文件大体上分为四个部分:
位图文件头BITMAPFILEHEADER |
位图信息头 BITMAPINFOHEADER |
调色板Palette |
实际的位图数据ImageData |
typedef struct tagBITMAPFILEHEADER {
WORD bfType; /* 说明文件的类型 */
DWORD bfSize; /* 说明文件的大小,用字节为单位 ,注意此处的字节序问题*/
WORD bfReserved1; /* 保留,设置为0 */
WORD bfReserved2; /* 保留,设置为0 */
DWORD bfOffBits; /* 说明从BITMAPFILEHEADER 开始到实际的图像数据之间的字节偏移量 */
}BITMAPFILEHEADER;
WORD:unsigned short 占2字节
DWORD:unsigned int占4字节
byType:表示文件类型,16进制:0x4D42,代表bmp文件
位图信息头包括:
typedef struct tagBITMAPINFOHEADER
{
DWORD biSize; /* 说明结构体所需字节数 */
long biWidth; /* 以像素为单位说明图像的宽度 00 00 02 58*/
long biHeight; /* 以像素为单位说明图像的高度 00 00 01 c2*/
WORD biPlanes; /* 说明位面数,必须为1 00 01*/
WORD biBitCount; /* 说明位数/像素,1、2、4、8、24 00 18*/
DWORD biCompression; /* 说明图像是否压缩及压缩类型 BI_bmp,BI_RLE8,BI_RLE4,BI_BITFIELDS */
DWORD biSizeImage; /* 以字节为单位说明图像大小 ,必须是4 的整数倍*/
long biXPelsPerMeter; /* 目标设备的水平分辨率,像素/米 */
long biYPelsPerMeter; /*目标设备的垂直分辨率,像素/米 */
DWORD biClrUsed; /* 说明图像实际用到的颜色数,如果为0
则颜色数为2的biBitCount次方 */
DWORD biClrImportant; /*说明对图像显示有重要影响的颜色
索引的数目,如果是0,表示都重要。*/
} BITMAPINFOHEADER;
biWidth:4字节,表示图像的宽度:如 00 00 02 58 宽度600(单位像素)
biHeight:4字节,表示图像的高度:如 00 00 01 C2 高度450(单位像素)
biBitCount:2字节,表示位数,如所选择的bmp为24位,则为 00 18
用二进制编辑器打开如下:
调色板
typedef struct tagRGBQUAD {
BYTE rgbBlue; /*指定蓝色分量*/
BYTE rgbGreen; /*指定绿色分量*/
BYTE rgbRed; /*指定红色分量*/
}RGBQUAD
调色板实际上是一个数组,它所包含的元素与位图所具有的颜色数相同,决定于biClrUsed和biBitCount字段。数组中每个元素的类型是一个RGBQUAD结构。真彩色图(24位的bmp)无调色板部分。
紧跟在调色板之后的是图像原始数据Matadata,随位图所使用的的位数不同而不同,24位图中直接使用RGB部分,小于24位的使用调色板颜色索引值。
每一扫描行由表示像素的连续字节组成,每一行的字节数取决于颜色数目和图像宽度,且必须是4的倍数。
读取BMP,提取RGB的流程如下:
根据每像素所占比特的不同,采用不同的处理方法
并调用RGB2YUV的函数实现到YUV的转换,转换关系如下:
Y=0.2990*R+0.5870*G+0.1140*B
R-Y=0.7010*R-0.5870*G-0.1140*B
B-Y=-0.2990*R-0.5870*G+0.8860*B
归一化后:
转换后的YUV文件是4:2:0格式,即U、V分量的宽度和高度各是Y分量的一半。
部分代码如下:
BITMAPFILEHEADER File_header;
BITMAPINFOHEADER Info_header;
// read file & info header
if(fread(&File_header,sizeof(BITMAPFILEHEADER),1,bmpFile) != 1)
{
printf("read file header error!");
exit(0);
}
if (File_header.bfType != 0x4D42)
{
printf("Not bmp file!");
exit(0);
}
else
{ printf("this is a bmp\n");
}
if(fread(&Info_header,sizeof(BITMAPINFOHEADER),1,bmpFile) != 1)
{
printf("read info header error!");
exit(0);
}
利用typedef 定义的BITMAPFILEHEADER和 BITMAPINFOHEADER 结构化定义数组 File_header和Info_header数组,存储读取的文件头和信息头,并进行判断是否可读且是BMP文件,以备使用。
if (((Info_header.biWidth/8*Info_header.biBitCount)%4) == 0)
frameWidth= Info_header.biWidth;
else
frameWidth = (Info_header.biWidth*Info_header.biBitCount+31)/32*4;
if ((Info_header.biHeight%2) == 0)
frameHeight = Info_header.biHeight;
else
frameHeight = Info_header.biHeight + 1;
printf("The width is %d\n",frameWidth);
printf("The height is %d\n",frameHeight);
判断读取的宽度和高度是否满足条件,并传递参数。
之后调用RGB2YUV的函数以实现到YUV的转换
实验中出现的问题如下:
1.读取参数错误:
宽度和高度与文件的实际数据不符,经在网上查询后,发现是结构体会按照DWORD长度, 即4进行对齐,第一个WORD占用2字节,文件头占用16字节,使后续字节读取错误,所以在定义文件头结构体和信息头结构体前面加上:
#pragma pack(1), 使结构体内部按1字节排列,这样便可读出正确的文件头和信息头。
实验结果 生成一个包含5张图像的200帧的yuv文件(24比特),使用YUVviewerPlus播放:
16bit的BMP文件:
16比特的有555和565两种,默认是555格式,当biCompression成员的值是BI_RGB时,它没有调色板。16位中,最低的5位表示蓝色分量,中间的5位表示绿色分量,高的5位表示红色分量,一共占用了15位,最高的一位保留,设为0,即xrrrrrgggggbbbbb
通过移位进行转换:
for (Loop = 0;Loop < height * width;Loop +=2)
{
*rgbData = (Data[Loop]&0x1F)<<3;
*(rgbData + 1) = ((Data[Loop]&0xE0)>>2) + ((Data[Loop+1]&0x03)<<6);
*(rgbData + 2) = (Data[Loop+1]&0x7C)<<1;
rgbData +=3;
}
1~8bit
先判断调色板:
bool MakePalette(FILE * pFile,BITMAPFILEHEADER &file_h,BITMAPINFOHEADER & info_h,RGBQUAD *pRGB_out)
{
if ((file_h.bfOffBits - sizeof(BITMAPFILEHEADER) - info_h.biSize) == sizeof(RGBQUAD)*pow(float(2),info_h.biBitCount))
{
fseek(pFile,sizeof(BITMAPFILEHEADER)+info_h.biSize,0);
fread(pRGB_out,sizeof(RGBQUAD),(unsigned int)pow(float(2),info_h.biBitCount),pFile);
return true;
}
else
return false;
// free(pRGB);
}
RGBQUAD *pRGB = (RGBQUAD *)malloc(sizeof(RGBQUAD)*(unsigned long)pow(float(2),info_h.biBitCount));
int temp = sizeof(pRGB);
if(!MakePalette(pFile,file_h,info_h,pRGB))
printf("No palette!\n\n");
for (Loop=0;Loop<height*width;Loop++)
{
switch(info_h.biBitCount)
{
case 1:
mask = 0x80;
break;
case 2:
mask = 0xC0;
break;
case 4:
mask = 0xF0;
break;
case 8:
mask = 0xFF;
}
int shiftCnt = 1;
while (mask)
{
unsigned char index = mask == 0xFF ? Data[Loop] : ((Data[Loop] & mask)>>(8 - shiftCnt * info_h.biBitCount));
* rgbData = pRGB[index].rgbBlue;
* (rgbData+1) = pRGB[index].rgbGreen;
* (rgbData+2) = pRGB[index].rgbRed;
if(info_h.biBitCount == 8)
mask =0;
else
mask >>= info_h.biBitCount;
rgbData+=3;
shiftCnt ++;
}
}
if(Index_Data)
free(Index_Data);
if(Data)
free(Data);
// if(pRGB)
// free(pRGB);
}
(其实不太理解原理)
实验结果如下:
其中bmp2为16bit,最后一张为4bit,其余为8bit
16比特及以下实验中出现的问题:
运行8比特文件时会停止运行,加断点调试后,发现是
RGBQUAD *pRGB = (RGBQUAD *)malloc(sizeof(RGBQUAD)*(unsigned char)pow(float(2),info_h.biBitCount));
有问题,然后将char改为long,long为4字节,而8比特bmp调色板索引值为0~255,超出了char型所能表示的范围。
通过本次实验:
1、学习了解BMP文件的组成结构
2、复习RGB和YUV文件之间的转换
3、复习了c语言中的函数的调用和参数传递
8bit 16bit 等BMP文件的下载地址:
http://www.hlevkin.com/06testimages.htm