數據壓縮 彩色空間轉換

彩色空間轉換


實驗原理

  • YUV到RGB空間的轉換公式
    由電視原理知識可知,數字視頻系統中對色差信號的壓縮公式爲:
    Cr=0.713(RY)
    Cb=0.564(BY)
    此時色差信號經過歸一化處理,動態範圍爲-0.5-0.5,讓色差零電平對應碼電平128(8比特量化時),則有:
    V=0.713(RY)+128
    U=0.564(BY)+128
    易推得YUV到RGB空間的轉換公式爲:
    R=Y+1.4075(V128)
    G=Y0.3455(U128)0.7169(V128)
    B=Y+1.779(U128)

  • 取樣格式
    YUV空間的取樣格式爲4:2:0,U、V兩個色差信號的亮度信號取樣頻率的四分之一,在水平方向和垂直方向上的取樣點數均爲Y的一半。而RGB空間的取樣格式爲4:4:4,則需要對U、V分量做上採樣,讓水平方向和垂直方向上取樣點數與Y一致。

  • 存儲方式與佔用空間
    YUV格式的視頻將整幀圖像的Y打包存儲,依次再存整幀的U、V,然後再是下一幀的數據;YUV格式按4:2:0取樣的視頻所佔空間大小爲Width*Height*Frame*1.5(Width –寬、Height–高、Frame–幀數)。RGB格式的視頻按每個像素B、G、R的順序儲存數據;所佔空間大小爲Width*Height*Frame*3。
  • YUV轉換爲RGB後顏色溢出的處理
    經過上式計算,原來爲整數的Y、U、V轉換爲浮點數R、G、B,由於RGB文件的顏色是8bit量化,則數值不在0-255範圍內的顏色會出現溢出,顯示錯誤。本實驗採用了定義float型的中間變量存放計算RGB的直接結果,而後通過簡單的判斷防止顏色溢出。
  • 帶參數的主函數的參數輸入
    打開項目屬性窗口,通過設置工作目錄、命令參數完成主函數的參數輸入。
    這裏寫圖片描述

YUV2RGB文件轉換流程分析

  1. 程序初始化(打開兩個文件、定義變量和緩衝區 等)
  2. 讀取YUV文件,抽取YUV數據寫入緩衝區
  3. 調用YUV2RGB的函數實現YUV到RGB數據的轉換
  4. 寫RGB文件
  5. 程序收尾工作(關閉文件,釋放緩衝區)

關鍵代碼及其分析

分析部分寫在代碼註釋中

YUVtoRGB.cpp

    #include "stdlib.h"
    #include "YUVtoRGB.h"
    static float YUVRGB14075[256];
    static float YUVRGB03455[256];
    static float YUVRGB07169[256];
    static float YUVRGB1779[256];
    int YUV2RGB (int Width, int Height, void *rgb_out, void *y_in, void *u_in, void *v_in)
    {
    static int init_done = 0;
    long i, j, size;
    unsigned char *r, *g, *b;
    float rf, gf, bf;//中間變量,用來防止色彩溢出
    unsigned char *y, *u, *v;
    unsigned char *y_buffer, *u_buffer, *v_buffer,*rgb_buffer;
    unsigned char *sub_u_buf, *sub_v_buf;

    if (init_done == 0)
    {
        InitLookupTable();
        init_done = 1;
    }

    // check to see if width and height are divisible by 2
    if ((Width % 2) || (Height % 2)) return 1;
    size = Width * Height;
    y_buffer = (unsigned char *)y_in;
    u_buffer = (unsigned char *)u_in;
    v_buffer = (unsigned char *)v_in;
    rgb_buffer = (unsigned char *)rgb_out;
    sub_u_buf = (unsigned char *)malloc(Width*Height);
    sub_v_buf = (unsigned char *)malloc(Width*Height);
    //sub_u_buf、sub_v_buf用於存放寬高都上採樣以後的U/V,便於計算RGB

    /************************上採樣***********************/
    for (i = 0; i < Height; i++)
    {
        for (j = 0; j < Width; j++)
        {
            *(sub_u_buf + i*Width + j) = *(u_buffer + (i / 2)*Width/2 + j / 2);
            *(sub_v_buf + i*Width + j) = *(v_buffer + (i / 2)*Width /2+ j / 2);
        }
    }
    /****************************************************/

    b = rgb_buffer;//通過r/g/b三個指針改變rgb_buffer的內容值
    y = y_buffer;
    u = sub_u_buf;
    v = sub_v_buf;
    for (i = 0; i < Height; i++)
    {
        for (j = 0; j < Width; j++)
        {
            g = b + 1;//RGB格式文件儲存按照BGR的順序
            r = b + 2;
            rf= (*y + YUVRGB14075[*v]);
            gf = (*y - YUVRGB03455[*u] - YUVRGB07169[*v]);
            bf = (*y + YUVRGB1779[*u]);
            *r =(rf>0?(rf>255? 255:(unsigned char)rf):0);
            *g = (gf>0 ? (gf>255 ? 255 : (unsigned char)gf) : 0);
            *b = (bf>0 ? (bf>255 ? 255 : (unsigned char)bf) : 0);
    /*由YUV到RGB的轉換公式可知,經過計算得到的RGB值都不是整數,由於計算
    係數的精度,RGB值有可能出現超出0-255的範圍。由於unsigned char型
    數據佔1字節,超出0-255的數值會溢出,發生彩色顯示錯誤。例如256會變爲
    0。
    可以採用一個float型中間變量(rf/bf/gf)來暫存由彩色轉換公式計算得
    來的RGB值,經過判斷以後,再給unsigned char型的r/g/b進行賦值,大
    於255則賦值爲255,小於0則賦值爲0,在0-255範圍內的數值則進行強制類
    型轉換。*/
            b += 3;
            y++;
            u++;
            v++;
        }
    }

    if (sub_u_buf != NULL)  free(sub_u_buf);
    if (sub_v_buf != NULL)  free(sub_v_buf);
    return 0;
    }

    void InitLookupTable()
    {
    int i;
    for (i = 0; i < 256; i++) YUVRGB14075[i] = (float)1.4075 * (i-128);
    for (i = 0; i < 256; i++) YUVRGB03455[i] = (float)0.3455 * (i-128);
    for (i = 0; i < 256; i++) YUVRGB07169[i] = (float)0.7169 * (i-128);
    for (i = 0; i < 256; i++) YUVRGB1779[i] = (float)1.779 * (i-128);
    }
    /*提前計算彩色轉換公式中的分量值。需要特別注意的是UV值需要提前
    減128,也就是說,計算分量值時保證UV的範圍在-128~127。*/

main.cpp

    #include<stdio.h>
    #include<stdlib.h>
    #include<malloc.h>
    #include"YUVtoRGB.h"
    #define u_int8_t    unsigned __int8 
    #define u_int       unsigned __int32
    #define u_int32_t   unsigned __int32
//爲了防止不同編程軟件數據類型int的字節數不一致,利用define進行規定
    int main(int argc,char ** argv)
    {
    u_int frameWidth = 352;     
    u_int frameHeight = 240;
    int i;

    char *rgbFileName= NULL;
    char *yuvFileName= NULL;
    FILE *rgbFile=NULL;
    FILE *yuvFile=NULL;
    u_int8_t* rgbBUFFER = NULL;
    u_int8_t* yBUFFER = NULL;
    u_int8_t* uBUFFER= NULL;
    u_int8_t* vBUFFER = NULL;
    u_int32_t videoFramesWritten = 0;
    yuvFileName = argv[1];
    rgbFileName = argv[2];
    frameWidth = atoi(argv[3]);
    frameHeight = atoi(argv[4]);
    //argv[0]是缺省的,軟件自動會裝填Project當中的exe文件名
    yuvFile = fopen(yuvFileName, "rb");
    if (yuvFile == NULL)
    {
        printf("cannot find yuv file\n");
        exit(1);
    }
    else
    {
        printf("The output yuv file is %s\n", yuvFileName);
    }

    rgbFile = fopen(rgbFileName, "wb");
    if (rgbFile == NULL)
    {
        printf("cannot find rgb file\n");
        exit(1);
    }
    else
    {
        printf("The input rgb file is %s\n", rgbFileName);
    }

    /* get an input buffer for a frame */
    rgbBUFFER= (u_int8_t *)malloc(frameWidth * frameHeight * 3);
    /* get the output buffers for a frame */
    yBUFFER= (u_int8_t *)malloc(frameWidth * frameHeight);
    uBUFFER= (u_int8_t *)malloc(frameWidth * frameHeight/4);
    vBUFFER= (u_int8_t *)malloc(frameWidth * frameHeight/4);

    if (rgbBUFFER == NULL || yBUFFER == NULL || uBUFFER == NULL || vBUFFER == NULL)
    {
        printf("no enought memory\n");
        exit(1);
    }
    while(fread(yBUFFER,1,frameWidth*frameHeight,yuvFile)
    &&fread(uBUFFER,1,frameWidth*frameHeight/4,yuvFile)
    &&fread(vBUFFER,1,frameWidth*frameHeight/4,yuvFile))
/*當讀入成功時fread函數返回實際讀取的數據項個數,當整幀的YUV都能讀
入時,進入while循環。由於文件指針自動向後移動到下一個未讀字節,故該
程序也適用於視頻文件,整段視頻被讀完fread函數返回0,則結束循環。*/
    {
        if(YUV2RGB(frameWidth, frameHeight, rgbBUFFER, yBUFFER, uBUFFER, vBUFFER))
        {
            printf("error");
            return 0;
        }
        fwrite(rgbBUFFER, 1, frameWidth * frameHeight*3, rgbFile);
        printf("\r...%d", ++videoFramesWritten);
    }

    printf("\n%u %ux%u video frames written\n", 
        videoFramesWritten, frameWidth, frameHeight);
    if(rgbFile!=NULL)   fclose(rgbFile);
    if(yuvFile!=NULL)   fclose(yuvFile);
    if(rgbBUFFER!=NULL) free(rgbBUFFER);
    if(yBUFFER!=NULL)   free(yBUFFER);
    if(uBUFFER!=NULL)   free(uBUFFER);
    if(vBUFFER!=NULL)   free(vBUFFER);
    return(0);
    }

實驗結果及其分析

將轉換得的RGB文件輸入已有的RGB2YUV程序,得到新的YUV文件,用YUVplayer打開新的YUV文件查看結果。
down.yuv

這裏寫圖片描述
這裏寫圖片描述

由於計算係數精度、強制類型轉換等帶來的誤差,經過彩色空間轉換的YUV值會存在些許差異,但從畫面、數據上看,這個差異都不算太明顯。

沒有采用中間變量,彩色溢出的情況
這裏寫圖片描述

下面給出另外三個YUV視頻文件進行彩色空間轉換的結果,佐證該程序是正確的。
akiyo.yuv
這裏寫圖片描述
這裏寫圖片描述

src01.yuv
這裏寫圖片描述
這裏寫圖片描述

src04.yuv
這裏寫圖片描述
這裏寫圖片描述


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