您的位置:首页 > 其它

BMP2YUV实验报告

2017-03-21 22:11 351 查看

第二次实验报告

一、实验原理

bmp文件的结构

bmp文件格式
位图文件头 BITMAPFILEHEADER
位图信息头 BITMAPINFOHEADER
调色板Palette
实际的位图数据ImageData
文件头包含的内容

WORD为两字节,DWORD为四字节

typedef struct tagBITMAPFILEHEADER {
WORD bfType;/*说明文件的类型*/
DWORD bfSize;/*说明文件的大小,用字节为单位*/
/*注意此处的字节序问题*/
WORD bfReserved1; /* 保留,设置为0 */
WORD bfReserved2; /* 保留,设置为0 */
DWORD bfOffBits; /* 说明从BITMAPFILEHEADER结构开始到实际的图像数据之间的字节偏移量 */
}   BITMAPFILEHEADER;


信息头包含的内容

typedef struct tagBITMAPINFOHEADER {
DWORD biSize; /* 说明结构体所需字节数 */
LONG biWidth; /* 以像素为单位说明图像的宽度 */
LONG  biHeight; /* 以像素为单位说明图像的高速 */
WORD biPlanes; /* 说明位面数,必须为1 */
WORD biBitCount;/*说明位数/像素1、2、4、8、24*/
DWORD biCompression;  /* 说明图像是否压缩及压缩类型  BI_RGB,BI_RLE8,BI_RLE4,BI_BITFIELDS */
DWORD biSizeImage; /* 以字节为单位说明图像大小,必须是4的整数倍*/
LONG biXPelsPerMeter; /*目标设备的水平分辨率,像素/米 */
LONG biYPelsPerMeter; /*目标设备的垂直分辨率,像素/米 */
DWORD biClrUsed; /* 说明图像实际用到的颜色数,如果为0则颜色数为2的biBitCount次方 */
DWORD biClrImportant; /*说明对图像显示有重要影响的颜色索引的数目,如果是0,表示都重要。*/
}  BITMAPINFOHEADER;


调色板包含的内容

图像位深度小于等于8bit时调用调色板,调色板中不保存每个像素的RGB值,而是将颜色信息制成表格,通过索引来确定像素的RGB值。比如图像位深度为8bit时,表格中则有2^8 256个数值。

typedef struct tagRGBQUAD {
BYTE    rgbBlue;           /*指定蓝色分量*/
BYTE    rgbGreen;        /*指定绿色分量*/
BYTE    rgbRed;            /*指定红色分量*/
BYTE    rgbReserved;   /*保留,指定为0*/
}  RGBQUAD;


下面通过具体的bmp文件来进行说明。

图像为24bit的bmp文件,分辨率为256*256。



0x00~0x01:内容为42 4D,标记了此文件类型为BMP文件

0x02~0x05:内容为38 00 03 00,表示该文件大小的十六进制表示为030038个字节(先保存低位,再保存高位)。

0x0a~0x0d:36 00 00 00,因为24bit的bmp文件没有调色板,因此表示bmp文件的文件头和信息头共占了36H字节,表示实际数据字节偏移量。

0x16~0x19:00 01 00 00,表示高度为100000000即256个像素。

0x1c~0x1d:表示图片为深度为18H,即24bit。

图像为8bit的bmp文件,分辨率为256*256。



0x00~0x01:内容为42 4D,标记了此文件类型为BMP文件

0x02~0x05:内容为38 04 01 00,表示该文件大小的十六进制表示为010438个字节(先保存低位,再保存高位)。

0x0a~0x0d:36 04 00 00,因为8bit的bmp文件有调色板,因此表示bmp文件的文件头和信息头共占了36H字节,调色板结构占用了00100000(1024)个字节,,共占用了1078个字节,表示实际数据字节偏移量为1078个字节。

0x16~0x19:00 01 00 00,表示高度为100000000即256个像素。

0x1c~0x1d:表示图片为深度为18H,即24bit。

RGB2YUV转换算法

Y=0.2990R+0.5870G+0.1140B

U=−<
4000
/span>0.1684R−0.3316G+0.5000B+128

V=−0.5000R−0.4187G−0.0813B+128

具体算法及代码可参见第一次试验报告的内容。

二、实验流程分析

1.程序初始化(打开两个文件、定义变量和缓冲区等)

2.

读取BMP文件,抽取或生成RGB数据写入缓冲区
读位图文件头:判断是否可读出、判断是否是BMP文件
判断像素的实际点阵数
开辟缓冲区,读数据,倒序存放
根据每像素位数的不同,执行不同的操作:
8bit以下16bit24bit
构造调色板
位与移位取像素数据查调色板写RGB缓冲区位与移位取像素数据转换8bit彩色分量写RGB缓冲区直接取像素数据写RGB缓冲区
3.调用RGB2YUV的函数实现RGB到YUV数据的转换

4.写YUV文件

5.程序收尾工作(关闭文件,释放缓冲区)

三、重要代码及分析

BITMAPFILEHEADER File_header;
BITMAPINFOHEADER Info_header;


文件结构组成包含在windows.h中

/*用于打开bmp文件的代码,与第一次实验相同*/
bmpFile = fopen(bmpFileName, "rb");
if (bmpFile == NULL)
{
printf("cannot find rgb file\n");
exit(1);
}
else
{
printf("The input bmp file is %s\n", bmpFileName);
}


/*读取bmp文件头和信息头*/
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 %c%c\n", File_header.bfType);
}
if (fread(&Info_header, sizeof(BITMAPINFOHEADER), 1, bmpFile) != 1)//读取信息头
{
printf("read info header error!");
exit(0);
}


/*读取文件字节宽度和高度*/
if (((Info_header.biWidth / 8 * Info_header.biBitCount) % 4) == 0)
width = Info_header.biWidth / 8 * Info_header.biBitCount;
else
width = (Info_header.biWidth*Info_header.biBitCount + 31) / 32 * 4;
if ((Info_header.biHeight % 2) == 0)
height = Info_header.biHeight;
else
height = Info_header.biHeight + 1;
/*读取文件的宽和高*/
frameWidth = Info_header.biWidth;
frameHeight = Info_header.biHeight;


BMP文件一行的字节数必须为4的倍数,如果不是4的倍数,就需要补充相应的位数使之成为4的位数,BMP的文件的列数也应该为偶数,如果不是偶数,则应该补一位使之成为偶数。

/*开空间,bmpBuf指向的空间大小为bmp文件的字节数,yuv空间的开辟在此不再赘述*/
rgbBuf = (unsigned char*)malloc(Info_header.biWidth * Info_header.biHeight * 3);
bmpBuf = (unsigned char*)malloc(width * height );


根据位深度的不同进行不同的操作。

先判断是否有调色板

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(2, info_h.biBitCount))
{
fseek(pFile, sizeof(BITMAPFILEHEADER) + info_h.biSize, 0);
fread(pRGB_out, sizeof(RGBQUAD), (unsigned int)pow(2, info_h.biBitCount), pFile);
return true;
}
else
return false;
}


RGBQUAD *pRGB = (RGBQUAD *)malloc(sizeof(RGBQUAD)*(unsigned int)pow(2, Info_header.biBitCount));


调色板的大小为2(biBitCount)。在这里要注意的是,当位深度为8bit时,28是256,所以要用unsigned int进行类型转换,不能用unsigned char,会超出0~255的范围,导致程序出错。

1. 24比特

24bit为真彩色,不会用到调色板,因此直接取像素数据写RGB缓冲区。

fread(rgbBuf, 1, frameWidth * frameHeight*3 , bmpFile);


2. 16比特

16bit也不用到调色板,但由于16bit保存方式rgb不是整字节值,因此采用如下代码进行写入。

xxx xxxxx xxxxxx xx

G1 B R G1

if (Info_header.biCompression == BI_RGB)
{
for (Loop = 0; Loop < height * width; Loop += 2)
{
*rgb = (bmpBuf[Loop] & 0x1F) << 3;//B
*(rgb + 1) = ((bmpBuf[Loop] & 0xE0) >> 2) + ((bmpBuf[Loop + 1] & 0x03) << 6);//G
*(rgb + 2) = (bmpBuf[Loop + 1] & 0x7C) << 1;//R
rgb += 3;
}
}


先保存低位,再保存高位,高位字节的前六位保存红色R的内容,后两位及低位字节的前三位保存绿色G,后五位保存蓝色B。

3. 8bit及以下

for (Loop = 0; Loop< height * width; Loop++)
{

switch (Info_header.biBitCount)
{
case 1:
mask = 0x80;//1000 0000
break;
case 2:
mask = 0xC0;//1100 0000
break;
case 4:
mask = 0xF0;//1111 0000
break;
case 8:
mask = 0xFF;//1111 1111
}
/*对于1bit的图像,一个字节中的第一位为第一个像素的值。4bit图像,一个字节中的前四位为第一个像素的值。具体操作是将通过不需要的位与0,需要的位与1.2bit及8bit同理。*/
int shiftCnt = 1;

while (mask)
{
unsigned char index = mask == 0xFF ? bmpBuf[Loop] : ((bmpBuf[Loop] & mask) >> (8 - shiftCnt * Info_header.biBitCount));
*rgb = pRGB[index].rgbBlue;
*(rgb + 1) = pRGB[index].rgbGreen;
*(rgb + 2) = pRGB[index].rgbRed;

if (Info_header.biBitCount == 8)
mask = 0;
else
mask >>= Info_header.biBitCount;
rgb += 3;
shiftCnt++;
}


进行rgb2yuv操作

if (RGB2YUV(frameWidth, frameHeight, rgbBuf, yBuf, uBuf, vBuf, flip))
{
printf("error");
return 0;
}
for (i = 0; i < bmpframe; i++)
{
fwrite(yBuf, 1, frameWidth * frameHeight, yuvFile);
fwrite(uBuf, 1, (frameWidth * frameHeight) / 4, yuvFile);
fwrite(vBuf, 1, (frameWidth * frameHeight) / 4, yuvFile);
printf("\r...%d", ++videoFramesWritten);
}


具体函数与第一次实验中的相同,在此不再赘述。

四、实验结果及分析

实验测试采用的是分辨率为256x256的位深度分别为1bit、4bit、8bit、16bit、24bit的五张图像。



在命令行参数中写入读取的文件名,最终的yuv文件名及,每一幅图像出现的帧数。



最终结果

1bit4bit8bit



16bit24bit


五、结论

在这里写出调试程序是时出现的几个主要错误:

出现错误1

*rgb = (bmpBuf[Loop] & 0x1F) << 3;
*(rgb + 1) = ((bmpBuf[Loop] & 0xE0) >> 2) + ((bmpBuf[Loop + 1] & 0x03) << 6);
*(rgb + 2) = (bmpBuf[Loop + 1] & 0x7C) << 1;
rgb += 3;


在这段代码中一开始使用的是rgbbuf指针,在执行完这段循环后,rgbbuf已经指向了图像的最后,在接下来调用YUV2RGB函数是,仍要使用rgbbuf,因此此时的rgbbuf已经超出了开空间的范围,在运行时出现了错误。因此通过rgb=rgbbuf,将这段代码中的rgbbuf改为rgb,来使得rgbbuf指向的值改变的同时,rgbbuf指向的地址值不变。

出现错误2

RGBQUAD *pRGB = (RGBQUAD *)malloc(sizeof(RGBQUAD)*(unsigned char)pow(2, Info_header.biBitCount));


在这段程序中,unsigned char是1字节的,当位深度为1bit和4bit时不会出现问题,但当8bit时,28为256,超出了0~255的范围,因此在运行时到8比特时会出错。因此将次改为unsigned int,为2字节即可。

通过本次实验,可以掌握bmp文件格式和bmp2yuv文件转换流程,同时也可以学习复杂数据的组织。我也复习了结构体的相关知识,用函数处理结构中的数据有三种情况

1.把结构成员的值个别地传给函数处理

2.把整个结构作为值,通过参数传递给函数(不希望改变结构变量的值)

3.把结构的地址传给函数,即传递指向结构的指针(希望改变结构变量的值)

本次实验采取的是第二种。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  yuv bmp 位图 struct