数据压缩原理实验1

    xiaoxiao2021-03-25  107

    数据压缩原理实验1_彩色空间转换实验(yuv转rgb)

    一、本实验涉及到的基本原理

    (1)RGB转为YUV

    Y  =   0.299*R + 0.587*G + 0.114*B

    B-Y =  -0.299*R + 0.413*G - 0.114*B

    R-Y =   0.701*R - 0.587*G - 0.114*B

    为了使色差信号的动态范围控制在-0.5至0.5之间,需要进行归一化,所以对色差信号引入了压缩系数。

    归一化后的色差信号为:

    U = 0.492*(B- Y) =  -0.147*R - 0.289*G + 0.436*B

    V = 0.877*(R- Y) =   0.615*R - 0.515*G - 0.100*B

    色差信号经过归一化处理后,动态范围控制在-0.5—0.5,让色差零电平对应码电平128,色差信号总共占225个量化级。

    256级上端留15级,下端留16级作为信号超越动态范围的保护带,因此真正在代码中所实现的UV转换公式如下:

    U =  -0.1684*R - 0.3316*G + 0.5*B   + 128

    V =     0.5*R - 0.4187*G - 0.0813*B + 128

    (2)YUV转RGB

    R = Y + 1.140*V

    G = Y - 0.394*U - 0.581*V

    B = Y + 2.032*U

    同样经过归一化处理,使其动态范围控制在-0.5—0.5之间,并让零电平对应码电平128,因此在代码中真正的RGB转换公式如下:

    R=Y+1.1402*(V-128)

    G=Y-0.34414*(U-128)-0.71414*(V-128)

    B=Y+1.772*(U-128)

    (3)YUV文件和RGB文件

    YUV420文件是指4:2:0格式即色差信号UV在水平方向和水平方向上的取样点数均为亮度信号Y的一半,因此UV的取样频率为Y取样频率的四分之一。

    YUV文件在存储上按先把一帧图像中每个像素的Y先存储再存储UV信号,存储方式如下:

    RGB文件中每个像素都是由红绿蓝三色混合所得,需要注意的是在文件存储中每个像素按BGR进行逐个存储,存储方式如下:

    二、实验的流程分析

    1、确定指针并进行定义(包括文件指针,开辟空间等)

    2、读入YUV文件

    3、将U、V块分别进行上采样为U’、V’,使其与Y块的大小相同

    下图为上采样的图示,yuv原来的4:2:0格式扩展为4:4:4格式,形式为等值赋值(u和sub_u_buf为指针)

    4、将4:4:4的Y、U’、V’通过转换公式计算得到R、G、B值

    5、写出RGB文件

     

    三、关键代码及其分析(分析附在代码里)

    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 YUV12 file * required arg2 should be the output RAW RGB24 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* yuvBuf = NULL; u_int32_t videoFramesWritten = 0; /* begin process command line */ /* point to the specified file names */ yuvFileName = argv[1]; rgbFileName = argv[2]; frameWidth = atoi(argv[3]); frameHeight = atoi(argv[4]); /* open the 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 the RAW file */ rgbFile = fopen(rgbFileName, "wb"); if (rgbFile == NULL) { printf("cannot find rgb file\n"); exit(1); } else { printf("The output yuv file is %s\n", rgbFileName); } /* get an output buffer for a frame */ //因为rgb文件中每个像素都是有RGB三个分量所构成,所以其存储大小应为一帧图像大小的三倍 rgbBuf = (u_int8_t*)malloc(frameWidth * frameHeight * 3); /* get the input buffers for a frame */ /*因为yuv4:2:0文件每个像素都有y分量,而uv都是每四个像素取一个值, 所以总体的存储大小为一帧图像大小的1.5倍*/ yuvBuf = (u_int8_t*)malloc(frameWidth * frameHeight * 3/2); if (rgbBuf == NULL || yuvBuf==NULL) { printf("no enought memory\n"); exit(1); } /* while循环里按照yuv文件一帧一帧的读入,若是视频文件,就自动按帧取值直到最后一帧*/ while (fread(yuvBuf, 1, frameWidth * frameHeight * 1.5, yuvFile)) { if (YUV2RGB(frameWidth, frameHeight, yuvBuf, rgbBuf, flip)) { printf("error"); return 0; } fwrite(rgbBuf, 1, (frameWidth * frameHeight) * 3, rgbFile); } printf("\r...%d", ++videoFramesWritten); printf("\n%u %ux%u video frames written\n", videoFramesWritten, frameWidth, frameHeight); /* cleanup 释放所开辟的缓冲区 */ if (rgbFile) fclose(rgbFile); if (yuvFile) fclose(yuvFile); if (rgbBuf) free(rgbBuf); if (yuvBuf) free(yuvBuf); return(0); } yuv2rgb.cpp文件,代码如下: #include "stdlib.h" #include "yuv2rgb.h" //定义列表中变量存数更加方便 static float YUVRGB1402[256]; static float YUVRGB034414[256], YUVRGB071414[256]; static float YUVRGB1772[256]; int YUV2RGB(int x_dim, int y_dim, void *yuv, void *rgb_out, int flip) { static int init_done = 0; long i, j, size; unsigned char *r, *g, *b; //用于分别指向输出的rgb文件中的R、G、B三个部分 unsigned char *y, *u, *v; //用于分别指向读入的yuv文件中的Y、U、V三个部分 unsigned char *pu1, *pu2, *pv1, *pv2, *psu, *psv; //用于UV上采样的中间指针 unsigned char *rgb_buffer; //用于寄存输出rgb文件的缓冲区 unsigned char *sub_u_buf, *sub_v_buf; //用于寄存UV上采样后文件的缓冲区 float rj, gj, bj; //判断数据是否溢出的中间变量 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 rgb_buffer = (unsigned char *)rgb_out; //UV上采样后 大小和Y所占大小一样 sub_u_buf = (unsigned char *)malloc(size * sizeof(unsigned char)); sub_v_buf = (unsigned char *)malloc(size * sizeof(unsigned char)); y = (unsigned char *)yuv; u = y + size; v = u + size / 4; // uv上采样 for (j = 0; j < y_dim/2 ; j++) { psu = u + j * x_dim / 2; psv = v + j * x_dim / 2; pu1 = sub_u_buf + 2 * j * x_dim; pu2 = sub_u_buf + (2 * j + 1) * x_dim; pv1 = sub_v_buf + 2 * j * x_dim; pv2 = sub_v_buf + (2 * j + 1) * x_dim; for (i = 0; i < x_dim/2 ; i++) { *pu1 = *psu; *(pu1 + 1) = *psu; *pu2 = *psu; *(pu2 + 1) = *psu; *pv1 = *psv; *(pv1 + 1) = *psv; *pv2 = *psv; *(pv2 + 1) = *psv; psu++; psv++; pu1 += 2; pu2 += 2; pv1 += 2; pv2 += 2; } } //yuv转rgb b = rgb_buffer; for (i = 0; i < size; i++) { g = b + 1; r = b + 2; //bj,gj,rj 为中间变量,用于判断是否有数据溢出,将数据控制在0-255之间 bj = (*y + YUVRGB1772[*sub_u_buf ]); gj = (*y - YUVRGB034414[*sub_u_buf ] - YUVRGB071414[*sub_v_buf ]); rj = (*y + YUVRGB1402[*sub_v_buf ]); *b = (bj>0 ? (bj>255 ? 255 : (unsigned char)bj) : 0); *g = (gj>0 ? (gj>255 ? 255 : (unsigned char)gj) : 0); *r = (rj>0 ? (rj>255 ? 255 : (unsigned char)rj) : 0); b += 3; y++; sub_u_buf++; sub_v_buf++; } /* 注意:下面两行代码必不可少;因为前面开了sub_u_buf,sub_v_buf这两个空间 但在上面循环中,这两个指针不再是空间的开头,变为空间的最后一个元素, 如果要释放这两个空间,就必须使指针再重新指回空间的开头,因此下面两行就是该作用 */ sub_u_buf -= size; sub_v_buf -= size; if (sub_u_buf) free(sub_u_buf); if (sub_v_buf) free(sub_v_buf); return 0; } /* 以下为查找表,便于求得yuv转rgb的过程中所需要计算的参数 公式中UV为了使码电平维持在0-255间,因此都各自减去128,这个计算应放在查找表中进行, 而非在上述循环中进行,原因如下:以数组YUVRGB034414[x]为例, 该数组中x范围为0-255,而计算的值i-128可能不在该范围内, 若放在上面循环内进行,则系统会因为数据溢出而使计算后的值不对, 而放在查找表中,int i 可以确保数值的正确性 */ void InitLookupTable() { int i; for (i = 0; i < 256; i++) YUVRGB1402[i] = (float)1.402 * (i-128); for (i = 0; i < 256; i++) YUVRGB034414[i] = (float)0.34414 * (i-128); for (i = 0; i < 256; i++) YUVRGB071414[i] = (float)0.71414 * (i-128); for (i = 0; i < 256; i++) YUVRGB1772[i] = (float)1.772 * (i-128); }

    yuv2rgb.h文件,代码如下:

    #ifndef YUV2RGB_H_ #define YUV2RGB_H_ int YUV2RGB(int x_dim, int y_dim, void *yuv, void *rgb_out, int flip); void InitLookupTable(); #endif

    四、实验结果及分析

    1)完成后结果

    (以下yuv文件在  http://trace.eas.asu.edu/yuv/index.html  网站上下载)

    1、以down.yuv为例

    当程序能够成功运行后,会跳出下图所示命令框,其中有一些参数细节,包括输入输出文件名,图像的长宽情况

    yuv播放器打开后如下所示细节: 

    左边为原始yuv图像,右边为经过yuv2rgb以及rgb2yuv的验证图像(以下四幅图均如此放置)

    下图为上图中蓝色方框内的yuv细节信息

    2、

    3、

    4、

    通过四张图像的前后对比,可以看到YUV三个变量都有些许差别,是算法中的一些误差所导致的

    误差总结如下:

    1、在数据类型转换的时候会出现误差,如将float型强制转换为unsigned char型等

    2、在uv分量上采样以及下采样过程中的一些计算误差

    3、在rgb与yuv之间相互转换的公式在来回运算中出现了计算误差

    但整体图像在人眼角度观察并没有太大的差错,因此可以大致认为两幅图是一样的

    2)完成前的一系列设置及出错问题及原因

    1、关于参数设置

    在项目属性中进行设置,命令参数中分别填入输入文件,输出文件,宽,高并以空格间隔

    在工作目录中填入存放输入输出文件的地址

    2、关于vs2013版fopen函数的设置

    同样在项目属性当中进行如下设置:

    3、算法错误1:转换公式中数据类型没有考虑严密(具体原因在代码中进行了解释)

    原代码:在循环中对数组进行了128的转换

    下图为错误代码产生的图像

    下图为纠正后的代码:在查找表中进行对128的计算

    4、算法错误2:因为指针指向问题,导致开的malloc空间没有释放完全(具体解释在代码中)

    下图为错误提示:

    下图为纠正后的代码

     

    五、结论

    掌握了函数定义,部分查找表的初始化和调用,缓冲区的分配,并且懂得了rgb与yuv之间在代码实现中的转换公式

    并且着重的认识到数据类型转换在代码中是需要重点注意

     

    转载请注明原文地址: https://ju.6miu.com/read-22882.html

    最新回复(0)