数据压缩实验一:YUV转RGB

来源:互联网 发布:mac office 2011 汉化 编辑:程序博客网 时间:2024/05/20 21:24

一、基本原理
1.RGB->YUV彩色空间转换公式及其分析

1)由电视原理可知,亮度和色差信号的构成如下:

Y=0.2990R+0.5870G+0.1140B
R-Y=0.7010R-0.5870G-0.1140B
B-Y=-0.2990R-0.5870G+0.8860B

2)如果亮度信号Y的动态取值为0~1(黑0、白1),那么根据计算R-Y的动态取值为-0.701~0.701(红0.701、青-0.701),B-Y的动态取值为-0.886~0.886(蓝0.886、黄-0.886),为了将R-Y和B-Y的动态取值宽度也变成1,要进行归一化,把动态取值变为-0.5~0.5,由±0.701 * 0.713=±0.5、±0.886 * 0.564=±0.5,得:

V=0.713(R-Y)= -0.1684R-0.3316G+0.5B
U=0.564(B-Y)= 0.5R-0.4187G-0.0813B

3)对亮度和色差信号进行8bit量化,为防止信号变动造成过载:亮度信号码电平0255,上端留20级,下端留16级作为信号超越动态范围的保护带,即Y值大于235的一律变成235,小于16的一律变成16;因为色差信号的动态范围在0.5之间,因此色差信号的零电平对应,码电平的128,上端留15级,下端留16级,即大于240的变成240,小于16的变成16。

V=-0.1684R-0.3316G+0.5B+128
U= 0.5R-0.4187G-0.0813B+128

4)色度取样格式
实验中采用4:2:0的取样格式,即U、V的取样频率在水平和垂直方向均为亮度取样的1/2。

2.YUV->RGB彩色空间转换公式及其分析

1)YUV->RGB是RGB->YUV相反的过程,因此根据转换公式可得:

R=Y+1.4075V
G=Y-0.3455U-0.7169V
B=Y+1.779U

2)由于色差信号整体抬高了128的码电平,因此需要减去:

R=Y+1.4075(V-128)
G=Y-0.3455(U-128)- 0.7169(V-128)
B=Y+1.779(U-128)

3.yuv和rgb存储方式分析

1)yuv的存储方式
在内存中yuv(4:2:0)格式的文件,以Y(0帧)U(0帧)V(0帧)、Y(1帧)U(1帧)V(1帧)……的方式存储在连续的内存中,其中U、V的数据量是Y的1/4,那么:一帧的数据量 = 水平像素数 * 垂直像素数 * 量化比特数 * 1.5

2)rgb的存储方式
和yuv以帧为单位存储不同,内存中rgb是以像素为单位存储的,每个像素都对应了1字节的R、1字节的G、1字节的B,一个像素用3个字节表示,所以:一帧的数据量 = 水平像素数 * 垂直像素数 * 量化比特数 * 3

二、实验流程分析

  • 主函数流程
    1.程序初始化(打开两个文件、定义变量和缓冲区等)
    2.读取YUV文件,抽取YUV数据写入缓冲区
    3.调用YUV2RGB的函数实现YUV到RGB数据的转换
    4.写RGB文件
    5.程序收尾工作(关闭文件,释放缓冲区)

  • YUV2RGB函数流程
    1.从主函数中获得输入的yuv文件指针
    2.定义相关中间变量及指针(开辟缓冲区)
    3.u、v上采样
    4.数据转换算法
    5.释放缓冲区

三、关键代码及其分析

1)头文件yuv2rgb.h
在同一文件中只能包含同一头文件一次,但main函数和yuv2rgb都要包含,所以加上编译预处理命令。

#ifndef YUV2RGB_H_ /*编译预处理命令*/#define YUV2RGB_H_ /*编译预处理命令*/int YUV2RGB(int x_dim,int y_dim,void *bmp,void *y_out,void *u_out,void *v_out,int flip);void InitLookupTable();#endif /*编译预处理命令*/

2)主函数main.cpp

#include<stdio.h>#include <stdlib.h>#include <malloc.h>#include "yuv2rgb.h"#define u_int8_t  unsigned __int8#define u_int     unsigned __int32#define u_int32_t unsigned __int32#define FALSE     false#define TRUE      true /* * yuv2rgb * required arg1 should be the input RAW YUV24 file * required arg2 should be the output RAW RGB12 file */ int main (int argc,char** argv){    /* variables controlable from command line */    u_int frameWidth = 352;  /* --width=<uint> */     u_int frameHeight =240; /* --height=<uint> */     bool flip = TRUE;      /* --flip */    unsigned int i;    /* internal variables */    char* rgbFileName = NULL;    char* yuvFileName = NULL;    FILE* rgbFile = 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;    /*处理视频需要定义视频帧数,通过外部参数传入,图片就将参数写为1*/    long frameNum;    /* begin process command line */    /* point to the specified file names */    rgbFileName = argv[1];    yuvFileName = argv[2];    frameWidth = atoi(argv[3]);    frameHeight = atoi(argv[4]);    frameNum = atoi(argv[5]);    /* open YUV file */    yuvFile = fopen(yuvFileName, "rb");    if(yuvFile == NULL)    {        printf("cannot find yuv file\n");        exit(1);    }    else    {        printf("The input yuv file is %s\n", yuvFileName);    }    /* open rgb file */    rgbFile = fopen(rgbFileName, "ab");    if(rgbFile == NULL)    {        printf("cannot open rgb file\n");        exit(1);    }    else    {        printf("The output rgb file is %s\n", rgbFileName);    }    /* get an input buffer for a frame */    yBuf = (u_int8_t*)malloc(frameWidth * frameHeight * frameNum );//视频转换要乘以帧数    uBuf = (u_int8_t*)malloc((frameWidth * frameHeight * frameNum)/4);    vBuf = (u_int8_t*)malloc((frameWidth * frameHeight * frameNum)/4);    /* get the output buffers for a frame */    rgbBuf = (u_int8_t*)malloc(frameWidth * frameHeight * 3);    if(rgbBuf == NULL||yBuf == NULL||uBuf == NULL||vBuf ==NULL)    {        printf("no enough memory\n");        exit(1);    }    while(fread(yBuf, 1, frameWidth * frameHeight, yuvFile),fread(uBuf, 1,(frameWidth * frameHeight)/4 ,yuvFile),        fread(vBuf, 1, (frameWidth * frameHeight)/4, yuvFile))//顺序读取YUV文件,一次读一帧,直至读到空,循环结束    {        if(YUV2RGB(frameWidth, frameHeight, rgbBuf, yBuf, uBuf, vBuf, flip))        {            printf("error");        }        fwrite(rgbBuf, 1, frameWidth * frameHeight * 3, rgbFile);        printf("\r...%d", ++videoFramesWritten);    }    printf("\n%u %ux%u video frames written\n", videoFramesWritten, frameWidth, frameHeight);    /* clean up */    free(yBuf);    free(uBuf);    free(vBuf);    free(rgbBuf);    fclose(yuvFile);    fclose(rgbFile);    return (0);}

3)转换函数yuv2rgb.cpp

/* This file contains YUV to RGB transformation functions.                */#include "stdio.h" //包含printf函数打印溢出像素信息#include "stdlib.h"#include "yuv2rgb.h"static float YUVRGB14075[256];static float YUVRGB03455[256], YUVRGB07169[256];static float YUVRGB1779[256];int YUV2RGB(int x_dim, int y_dim, void *bmp, void *y_in, void *u_in, void *v_in, int flip){    static int init_done = 0;    int m,n;//用于表示溢出像素的行和列    long i, j, size, k;    unsigned char *b;    float rgb[3];//作为判断越界的中间变量    unsigned char *y, *u, *v;    unsigned char *pu1, *pu2, *pv1, *pv2, *psu, *psv;    unsigned char *y_buffer, *u_buffer, *v_buffer;    unsigned char *sub_u_buf, *sub_v_buf;    if(init_done == 0)    {        InitLookupTable();        init_done = 1;    }    //check to see if x_dim and y_dim are divisible by 2    if((x_dim%2)||(y_dim%2)) return 1;    size = x_dim * y_dim;    //allocate memory    y_buffer = (unsigned char*)y_in;    sub_u_buf = (unsigned char*)u_in;    sub_v_buf = (unsigned char*)v_in;    u_buffer = (unsigned char*)malloc(size *sizeof(unsigned char));    v_buffer = (unsigned char*)malloc(size *sizeof(unsigned char));    if(!(u_buffer && v_buffer))    {        if(u_buffer) free(u_buffer);        if(v_buffer) free(v_buffer);        return 2;    }    b=(unsigned char*)bmp;    y = y_buffer;    u = u_buffer;    v = v_buffer;    /*************** uv 上采样******************    对示例中的下采样程序稍作修改即可:    下采样后的u、v是Y的1/4数据量,为了计算rgb方便,先进行上采样转换到和Y一样的数据量,就是将一个值变成周围像素的四个值,输出的数据量变成输入的四倍。    ********************************************/    for(j =0;j < y_dim/2; j++)    {     //psu、psv指向sub_u_buf、sub_v_buf输入的u、v数据在第一层循环中依次指向下一行第一个数据        psu = sub_u_buf + j * x_dim/2;        psv = sub_v_buf + j * x_dim/2;    //pu1、pu2(pv1、pv2)指向输出的u_buffer(v_buffer)偶数行和奇数行,在第一层循环中一次下移两行        pu1 = u_buffer + 2 * j * x_dim;        pu2 = u_buffer +( 2 * j +1) * x_dim;        pv1 = v_buffer + 2 * j * x_dim;        pv2 = v_buffer +( 2 * j +1) * x_dim;        for(i = 0;i < x_dim/2; i++ )        {            //u转换            *pu1 = *psu;             *(pu1+1) = *psu;//为偶数行相邻两元素赋相同的值            *pu2 = *psu;            *(pu2+1) = *psu;//为奇数行相邻两元素赋相同的值            //v转换            *pv1 = *psv;             *(pv1+1) = *psv;//为偶数行相邻两元素赋相同的值            *pv2 = *psv;            *(pv2+1) = *psv;//为奇数行相邻两元素赋相同的值            //移动指针            psu ++;//取下一个值            psv ++;            pu1 += 2;//跳过已赋值的一个            pu2 += 2;            pv1 += 2;            pv2 += 2;        }    }    /*********** convert YUV to RGB ************/    for(i=0;i<size;i++)    {        /******************************** debug by zml         1、直接用了unsigned char强制类型转换float,超出0~255范围的数以补码的形式直接截取后8位,导致一些点颜色不对          2、在这里对u、v减了128导致整个画面偏紫色(错误图在实验结果分析中)        g=b+1;        r=b+2;        *r = (unsigned char)((*y) + YUVRGB14075[(*v)-128]);        *g = (unsigned char)((*y) - YUVRGB03455[(*u)-128]-YUVRGB07169[(*v)-128]);        *b = (unsigned char)((*y) + YUVRGB1779[(*u)-128]);         ************************************************/        rgb[2]=((*y) + YUVRGB14075[(*v)]);//计算R,存储是bgr的顺序,方便起见数组倒过来        rgb[1]=((*y) - YUVRGB03455[(*u)]-RYUVRGB07169[(*v)]);        //计算G        rgb[0]=((*y) + YUVRGB1779[(*u)]);//计算B        for(k=0;k<3;k++)        {            if((rgb[k]>=0)&&(rgb[k]<=255))//判断计算值是否越界                //不越界,float强制类型转换为unsigned char                *(b++) = rgb[k];            else            {                 //越界则赋值为上下限                 *(b++) = (rgb[k] < 0)?0:255;                 m=(int)(i/256);                 n= i%256;                 printf("第%d行,第%d个像素溢出,%d值为%f\n",m,n,k,rgb[k]);//打印溢出像素值及位置            }        }        y++;        u++;        v++;    }    //释放空间    free(u_buffer);    free(v_buffer);    return 0;}void InitLookupTable(){    int i;    for(i=0;i<256;i++)    {        /**** debug by zml 一开始在转换循环里减了128 ****/        YUVRGB14075[i] = (float)1.4075 * (i-128);        YUVRGB03455[i] = (float)0.3455 * (i-128);        YUVRGB07169[i] = (float)0.7169 * (i-128);        YUVRGB1779[i] = (float)1.779 * (i-128);    }}

四、实验结果及分析

左边是yuv格式的原图像,右边是转成rgb格式再转回yuv的图像。

  • Figure 1 示例图片256*256

Figure 1示例图片256*256

错误图: 1、直接用了unsigned char强制类型转换float,超出0~255范围的数以补码的形式直接截取后8位,导致一些点颜色不对 2、在这里对u、v减了128导致整个画面偏紫色

强制类型转换导致出错1

根据打印的信息可知,若没有判断计算出的RGB值是否在0~255内,并为其赋值为上下限,以下这些点会出错(ctrl+F5“开始执行,不调试”能够使窗口不一闪而过):

错误信息较多,仅截屏两张作为示例

 从打印的信息可以看出,错误集中在图片下方;错误大部分对应参数rgb[k=2],即r(红色):与出现的错误图符合。

128减错了地方出错2

  • Figure 2自选视频cif格式352*288 24帧

Figure 2自选视频cif格式352*288 24帧

  • Figure 3自选视频cif格式352*288 300帧

Figure 2自选视频cif格式352*288 24帧

  • Figure 4自选视频cif格式352*288 250帧

 Figure 4自选视频cif格式352*288 250帧

五、结论
通过这次实验,对yuv和rgb图像格式的转换原理有了更直观的认识,了解了它们是如何存储的,深刻地理解了色度取样是如何进行的。在仿写代码和不断查错的过程中,增加了编程的知识,比如怎么在vs中配置命令参数,怎么解决“fatal error LNK1123: 转换到 COFF 期间失败”这样的错误等,除了基本的算法,还有很多细节的问题需要考虑,比如说越界、强制类型转换带来的问题。

0 0