数据压缩 BMPtoYUV

BMPtoYUV

实验原理

  • BMP格式简介

BMP(全称Bitmap)是Windows操作系统中的标准图像文件格式,可以分成两类:设备相关位图(DDB)和设备无关位图(DIB),使用非常广。它采用位映射存储格式,除了图像深度可选以外,不采用其他任何压缩,因此,BMP文件所占用的空间很大。BMP文件的图像深度可选lbit、4bit、8bit及24bit。BMP文件存储数据时,图像的扫描方式是按从左到右、从下到上的顺序。由于BMP文件格式是Windows环境中交换与图有关的数据的一种标准,因此在Windows环境中运行的图形图像软件都支持BMP图像格式。
典型的BMP图像文件由四部分组成:
1:位图头文件数据结构,它包含BMP图像文件的类型、显示内容等信息;
2:位图信息数据结构,它包含有BMP图像的宽、高、压缩方法,以及定义颜色等信息;
3:调色板,这个部分是可选的,有些位图需要调色板,有些位图,比如真彩色图(24位的BMP)就不需要调色板;
4:位图数据,这部分的内容根据BMP位图使用的位数不同而不同,在24位图中直接使用RGB,而其他的小于24位的使用调色板中颜色索引值。

BMP图像文件组成部分内存大小(字节)
位图文件头BITMAPFILEHEADER14
位图信息头BITMAPINFOHEADER40
调色板PalettebiClrUsed=0,则颜色数为2的biBitCount次方,即调色板数组共有2的biBitCount次方个元素,每个元素4字节(sizeof(RGBQUAD)=4)
实际的位图数据ImageData由biWidth、biHeight、biBitCount共同决定

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

  • BITMAPINFOHEADER
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;

  • RGBQUAD

调色板实际上是一个数组,它所包含的元素与位图所具有的颜色数相同,决定于biClrUsed和biBitCount字段。数组中每个元素的类型是一个RGBQUAD结构。真彩色无调色板部分。

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

结构体BITMAPFILEHEADER/BITMAPINFOHEADER/RGBQUAD定义在Windows.h库中,可直接include使用。


  • 用二进制编辑器查看BMP文件

8bitBMP图像文件:
从开始到第一个粉色标签是FILEHEADER的内存空间,共14个字节。从第一个粉丝标签到第二个粉色标签是INFOHEADER的内存空间,共40个字节。
由于8bitBMP图像非真彩色,故需要调色板。
=sizeof(RGBQUAD)2biBitCount
故第二个粉色标签后的1024个字节属于改8bit BMP文件的调色板部分。
8bit二进制
24bitBMP图像文件:
从开始到第一个粉色标签是FILEHEADER的内存空间,共14个字节。从第一个粉丝标签到第二个粉色标签是INFOHEADER的内存空间,共40个字节。
由于24bitBMP图像是真彩色,故无调色板。第二个粉色标签以后直接开始存实际的位图数据。
24bit二进制

下面是本实验用到的BMP图像预览,前6个图像用于生成实验要求的200帧以上的YUV文件,图像宽高皆为600pixel;本程序考虑到扫描行字节数不为4的整数倍的情况,其他图像用于参与验证。
(部分图片来自网易LOFTER)

全图片预览


实验流程

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

  • 读取BMP文件,抽取或生成RGB数据写入缓冲区

流程1
这里写图片描述

  • 到YUV数据的转换

  • 写YUV文件
    编写将第一部所生成的多个BMP文件转化为YUV文件,要求在命令行中设置各画面出现的帧数。最后形成的YUV文件应至少包含200帧。

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

关键代码及其分析

首先我们对本实验处理1-8bit、16bit BMP图像文件的程序原理进行分析,笔者采用更为直观的图形描述这两段程序。
16bit:

for ( Loop = 0; Loop < Height * Width; Loop += 2)
        {
            *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;
        }

16bit程序示意

上图描述了该程序段对Imagedata区每16位进行的操作,而后采用步长为2的循环遍历整个图像的Imagedata。


1-8bit:

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

4bit程序示意
上图以4bitBMP图像为例,描述该段程序对Imagedata区每8位图像进行的操作,index为索引号,以index为调色板数组下标去查询数据中每info_h.biBitCount位所代表的颜色。


其他代码分析:

  • main.cpp
#include<stdio.h>
#include<stdlib.h>
#include<malloc.h>
#include<Windows.h>
#include<math.h>
#include"BMPtoYUV.h"
#define u_int8_t    unsigned __int8
#define u_int       unsigned __int32
#define u_int32_t   unsigned __int32

BITMAPFILEHEADER File_header;
BITMAPINFOHEADER Info_header;

int main(int argc, char** argv)
{   
    /*input order:
    bmpfilename1  output framenumber1
    bmpfilename2  output framenumber2
    .....
    output yuvfilename
    */
    //input:1bit.bmp 30 4bit.bmp 40 8bit.bmp 30 16bit.bmp 30 24bit.bmp 40 32bit.bmp 40 final.yuv
    bool flip = FALSE;  
    /*flip=TRUE 图像无需上下翻转
      flip=FALSE 图像需要上下翻转
      BMP图像按自下向上、自左向右顺存储像素
      故需要翻转*/
    char* bmpFileName = NULL;
    char* yuvFileName = NULL;
    FILE* bmpFile = NULL;
    FILE* yuvFile = NULL;
    u_int8_t* rgbBuf = NULL;
    u_int8_t* yBuf = NULL;
    u_int8_t* uBuf = NULL;
    u_int8_t* vBuf = NULL;
    u_int32_t videoFramesWritten = 0;
    u_int frameWidth, frameHeight;
    int i=0,j=0;

    yuvFileName = argv[argc-1];
    if ((yuvFile = fopen(yuvFileName, "wb")) == NULL)
    {
        printf("cannot find yuv file\n");
        exit(1);
    }
    else
    {
        printf("The output yuv file is %s\n", yuvFileName);
    }
    /*利用循环依次读bmp文件(像素宽高一致) 按照输入的帧数写到同一个YUV文件*/
    for (i = 0; i < argc/2-1; i++)
    {
        videoFramesWritten = 0;
        bmpFileName = argv[2*i + 1];
        /* open the bmp file */
        if ((bmpFile = fopen(bmpFileName, "rb")) == NULL)
        {
            printf("cannot find bmp file\n");
            exit(1);
        }
        else
        {
            printf("The input bmp file is %s\n", bmpFileName);
        }
        //read BMP fileheader & infoheader
        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);
        }
        printf("bitcount==%d\n", (unsigned char)Info_header.biBitCount);
        //  end read header
        frameWidth = Info_header.biWidth;
        frameHeight = Info_header.biHeight;
        rgbBuf = (unsigned char *)malloc(sizeof(unsigned char)*frameHeight*frameWidth * 3);
        memset(rgbBuf, 0, frameHeight*frameWidth * 3);
        yBuf=(unsigned char*)malloc(sizeof(unsigned char)*frameHeight*frameWidth);
        uBuf = (unsigned char*)malloc(sizeof(unsigned char)*frameHeight*frameWidth/4);
        vBuf = (unsigned char*)malloc(sizeof(unsigned char)*frameHeight*frameWidth/4);
        /*调用定义在"BMPtoYUV.cpp"中的函数得到与像素宽高一致的一帧RGB值,
        由于调色板的读取也在ReadRGB函数中进行,故要给函数传参bmpFile。*/
        ReadRGB(rgbBuf, bmpFile, File_header,Info_header);
        //printf("ok2!!\n");  Debug by Sssssusu 2017/3/21
        if (RGB2YUV(frameWidth, frameHeight, rgbBuf, yBuf, uBuf, vBuf, flip))
        {
            printf("error");
            return 0;
        }
        //根据命令行输入的参数,不同位深度的bmp图像写入不同帧数
        for (j = 0; j <atoi(argv[2*i+2]);j++)
        {
            fwrite(yBuf, 1, frameWidth * frameHeight, yuvFile);
            fwrite(uBuf, 1, (frameWidth * frameHeight) / 4, yuvFile);
            fwrite(vBuf, 1, (frameWidth * frameHeight) / 4, yuvFile);
            printf("\r...%d", ++videoFramesWritten);
        }
        printf("\n\n");
        if (rgbBuf != NULL) free(rgbBuf);
        if (yBuf != NULL) free(yBuf);
        if (uBuf != NULL) free(uBuf);
        if (vBuf != NULL) free(vBuf);
        if (bmpFile != NULL) fclose(bmpFile);
    //  printf("end!!\n"); Debug by Sssssusu 2017/3/21
    }
    if (yuvFile != NULL) fclose(yuvFile);
    return 0;
}

  • BMPtoYUV.cpp
#include<Windows.h>
#include<stdio.h>
#include<math.h>
#include"BMPtoYUV.h"
static float RGBYUV02990[256], RGBYUV05870[256], RGBYUV01140[256];
static float RGBYUV01684[256], RGBYUV03316[256];
static float RGBYUV04187[256], RGBYUV00813[256];
bool MakePalette(FILE * pFile, BITMAPFILEHEADER &file_h, BITMAPINFOHEADER & info_h, RGBQUAD *pRGB_out)
{
    /*若图像开始位置与INFOHEADER结束处位置,还有pow(2, info_h.biBitCount)个
    结构体RGBQUAQ的空间(颜色数为2的biBitCount次方,调色板为数组),
    则说明该bmp图像有调色板。*/
    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;
}
void ReadRGB(unsigned char * rgbbuf, FILE *bmpfile, BITMAPFILEHEADER &file_h ,BITMAPINFOHEADER & info_h )
{
    u_int Height=0, Width=0;
    long Loop=0;
    unsigned char *bmpbuf=NULL;
    unsigned char *rgb=NULL;
    unsigned char mask=0;
    int deltaw = 0;
    rgb = rgbbuf;

    if (((info_h.biWidth  * info_h.biBitCount / 8) % 4) == 0)
        Width = info_h.biWidth*info_h.biBitCount / 8;
    else
        Width = (info_h.biWidth  * info_h.biBitCount + 31)/32*4;

    if ((info_h.biHeight % 2) == 0)
        Height= info_h.biHeight;
    else
        Height = info_h.biHeight + 1;
    /* 考虑到规定每一扫描行的字节数必须是4的整数倍,也就是与DWORD对齐的。
    通常我们使用的视频像素宽高值都为偶数,可能出现扫描行字节数不为4的整数倍
    情况的位深度为 1bit、2bit、4bit、8bit、24bit(若宽为奇数,则16bit也
    可能不整除与4,在这里不考虑了,因为原理一致)。根据bmp图像特点,若扫
    描行字节数不为4的整数倍,则补零至为4的整数倍。为了生成与原图像像素宽高
    一致的yuv文件,本程序采用了舍去扫描行后面补的字节的方法。
    易知,字节数不整除于4的情况无非三种,余数分别为1、2、3。余数为1,则需
    补上3个字节,其他情况以此类推。
    deltaw用来计算实际字节数和用于图像信息存储的字节数之差,也就是需要舍去
    的字节数。*/
    deltaw = Width - info_h.biWidth * info_h.biBitCount / 8 ;
    bmpbuf = (unsigned char *)malloc(sizeof(unsigned char)*Height*Width);
    printf("word_width :%d , word_height: %d \n", Width,Height);
    RGBQUAD *pRGB = (RGBQUAD *)malloc(sizeof(RGBQUAD) * (unsigned int)pow(2.0,(double) info_h.biBitCount));
    //printf("size of ==%d\n", sizeof(pRGB)); Debug by Sssssusu 2017/3/21
    if (!MakePalette(bmpfile, file_h, info_h, pRGB))
    {
        printf("No palette!\n");
    }
    //printf("%d\n", pRGB); Debug by Sssssusu 2017/3/21
    fread(bmpbuf, 1, Height*Width, bmpfile);
    if (info_h.biBitCount == 24)
    {
        for (Loop=0; Loop<Height*Width; Loop++)
        {
        //判断扫描行是否补零字节,是则跳过零字节
            if (deltaw != 0)
            {
                if (deltaw == 1)
                {
                    if ((Loop + 1) % Width == 0)
                        continue;
                }
                else if (deltaw == 2)
                {
                    if ((Loop + 1) % Width == 0 || (Loop + 2) % Width == 0)
                        continue;
                }
                else
                {
                    if ((Loop + 1) % Width == 0 || (Loop + 2) % Width == 0 || (Loop + 3) % Width == 0)
                        continue;
                }
            }
            *rgb = *(bmpbuf+ Loop);
            rgb++;
        }
    }
    else if (info_h.biBitCount == 32)
    {
        /*位深度为32bit的图像扫描行字节数一定为4的整数倍。24bit的图像已经是真彩色,
        32bit即用4个字节存一个像素的RGB值,其中最后一个字节为0x00,舍去。*/
        for (Loop = 0; Loop < info_h.biWidth*info_h.biHeight; Loop++)
        {
            *(rgb + 3* Loop) = *(bmpbuf+4* Loop);
            *(rgb + 3 * Loop +1) = *(bmpbuf + 4 * Loop +1);
            *(rgb + 3 * Loop +2) = *(bmpbuf + 4 * Loop +2);
        }
    }
    else if (info_h.biBitCount == 16)
    {
        for ( Loop = 0; Loop < Height * Width; Loop += 2)
        {
            *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;
        }
    }
    else
    {
        for (Loop = 0; Loop < Height*Width; Loop++)
        {
            if (deltaw !=0)
            {
                if (deltaw == 1)
                {
                    if ((Loop + 1) % Width == 0)
                        continue;
                }
                else if( deltaw==2)
                {
                    if ((Loop + 1) % Width == 0||(Loop+2)%Width==0)
                        continue;
                }
                else
                {
                    if ((Loop + 1) % Width == 0 || (Loop + 2) % Width == 0|| (Loop + 3) % Width == 0)
                        continue;
                }
            }

            switch (info_h.biBitCount)
            {
            case 1:
                mask = 0x80;
                break;
            case 2:
                mask = 0xC0;
                break;
            case 4:
                mask = 0xF0;
                break;
            case 8:
                mask = 0xFF;
                break;
            }
            int shiftCnt = 1;
            while (mask)
            {
            /*根据从数据中提取出的索引号index,以index为调色板数组下标去查询
            数据中每info_h.biBitCount位所代表的颜色。
            while 循环的次数:
                1bit 图像 每字节循环8次
                2bit 图像 每字节循环4次
                4bit 图像 每字节循环2次
                8bit 图像 每字节循环1次。
            */
                unsigned char index =
                    mask == 0xFF ? bmpbuf[Loop] : ((bmpbuf[Loop] & mask) >> (8 - shiftCnt *info_h.biBitCount));
                *rgb = pRGB[index].rgbBlue;
                *(rgb + 1) = pRGB[index].rgbGreen;
                *(rgb+ 2) = pRGB[index].rgbRed;
                if (info_h.biBitCount == 8) mask = 0;
                else    mask >>= info_h.biBitCount;
                rgb += 3;
                shiftCnt++;
            }
            /*if (Loop == Width*Height - 1)
                printf("Last Pixel= %d\n", *(rgbbuf + 3 * Loop));*/
            //Debug by Sssssusu 2017/3/21
        }

    }
    // printf("%d\n", pRGB);    Debug by Sssssusu 2017/3/21
    if(pRGB!=NULL) free(pRGB);
    if (bmpbuf != NULL)free(bmpbuf);
    //printf("OK\n");          Debug by Sssssusu 2017/3/21
}
///此处省略RGB2YUV的转换函数//

实验结果

  • 实验过程中出现的问题
    1. 图像宽为600,当位深度为1bit时,每行上有75个字节的图像数据,但75不被4整除,BMP图像会自动补零至一行上有76个字节。因为主函数里为了写进规定好图像宽高(biWidth*biHeight )的yuv文件里,如果不考虑这个细节,1bitBMP图像转YUV文件就会失败。
    2. 给调色板开空间时使用了unsigned char类型,当位深度为8bit时, 2infoh.biBitCount=256 ,超出了unsigned char类型的数据范围(0-255),程序中动态空间无法正常释放,会触发断点。而后改为了unsigned int类型。
RGBQUAD *pRGB = (RGBQUAD *)malloc(sizeof(RGBQUAD) * (unsigned char)pow(2.0,(double) info_h.biBitCount));

生成的210帧YUV文件截图:

1bit.bmp 30帧
1bit

4bit.bmp 40帧
4bit

8bit.bmp 30帧
8bit

16bit.bmp 30帧
16bit

24bit.bmp 40帧
24bit

32bit.bmp 40帧
32bit

下面的图像用来佐证对扫描行字节数不为4的整数倍的情况有效。

Width/Height:302pixel biBitCount:4
一行上共有151个字节的图像,补零至152个字节。
302_4bit

Width/Height:584pixel biBitCount:1
一行上共有73个字节的图像,补零至76个字节。
584_1bit

Width/Height:592pixel biBitCount:1
一行上共有74个字节的图像,补零至76个字节。
592_1bit

Width/Height:594pixel biBitCount:24
一行上共有1782个字节的图像,补零至1784个字节。
594_24bit

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值