BMP2YUV实验报告

第二次实验报告

一、实验原理

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=0.1684R0.3316G+0.5000B+128
V=0.5000R0.4187G0.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.把结构的地址传给函数,即传递指向结构的指针(希望改变结构变量的值)
本次实验采取的是第二种。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值