數據壓縮 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圖像文件組成部分 內存大小(字節)
位圖文件頭BITMAPFILEHEADER 14
位圖信息頭BITMAPINFOHEADER 40
調色板Palette biClrUsed=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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章