分为三个分量,“Y”表示明亮度,也就是灰度值,而“U”和“V” 表示的则是色度,作用是描述影像色彩及饱和度,用于指定像素的颜色。 本实验中YUV文件采用取样格式是4:2:0,即为U、V两个色差信号地取样频率是Y亮度信号的1/4,水平和垂直方向的取样点数均为Y的1/2。 而RGB取样格式为4:4:4,所以将YUV文件转换成RGB,要将U和V进行变化,即取样点数和Y一样可以一一对应进行计算。
原始公式 Y=0.30 * R+0.59 * G+0.11 * B U=0.493 * (B-Y) V=0.877 * (R-Y) 由Cr=0.713 * (R-Y) Cb=0.564 *(B-Y) 然后再进行归一化处理,即将两个色差信号电平控制在-0.5~0.5,零电平对应128的码电平 U=Cr+128 V=Cb+128 再结合原始公式得出结果 R = Y + 1.4075 *(V-128) G = Y – 0.3455 (U –128) – 0.7169 (V –128) B = Y + 1.779 *(U – 128)
YUV是按帧存储,即先存储一帧的Y,再存储U和V。而RGB是按像素存储,即存储每个像素的R,G,B,需要3倍的存储空间。 图片链接
在Visual studio的开发环境下,打开项目属性,在配置属性->调试中更改工作目录和命令参数,其中命令参数根据自己定义的手动输入。
实验中很可能得到经过将此转换的yuv文件出现红色或者蓝色的区域,这是由于由初始yuv文件计算得到的rgb值,而RGB为8bit量化,得到的值可能会超过0-255的范围,造成溢出,所以会定义临时变量r_temp,g_temp,b_temp,判断范围后再赋给r、g、b。
1.程序初始化(打开两个文件、定义变量和缓冲区等) 2.读取YUV文件,抽取YUV数据写入缓冲区 3.调用YUV2RGB的函数实现YUV到RGB的转换 4.写RGB文件 5.程序收尾工作(关闭文件,释放缓冲区)
main.cpp(部分代码)
while (fread(yBuf, 1, frameWidth * frameHeight, yuvFile) && fread(uBuf, 1, frameWidth * frameHeight / 4, yuvFile) && fread(vBuf, 1, frameWidth * frameHeight / 4, yuvFile)) /*读取文件,若读取成功,则返回yuv文件中的可读取的数据个数。在随后的测试中,读取的yuv是视频文件,因为用了while循环,可以不断的读取下一个数据,所以返回了yuv视频文件中的帧数。文件被读取完结束循环,返回0。*/ { if (YUV2RGB(frameWidth, frameHeight, rgbBuf, yBuf, uBuf, vBuf, 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);/*从后来的测试结果可以看出videoFrameWritten表示文件的数据项个数,图像为1,视频为其帧数*/ /*clean up*/ if(rgbFile != NULL) fclose(rgbFile); if(rgbBuf != NULL) free(rgbBuf); if(yuvFile != NULL) fclose(yuvFile); if(yBuf != NULL) free(yBuf); if(uBuf != NULL) free(uBuf); if(vBuf != NULL) free(vBuf);yuv2rgb.cpp(部分代码)
static float YUVRGB14075[256]; static float YUVRGB03455[256], YUVRGB07169[256]; static float YUVRGB17790[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; long i, j, size; unsigned char *r, *g, *b; unsigned char *y, *u, *v; unsigned char *y_buffer, *u_buffer, *v_buffer; float r_temp, g_temp, b_temp;//设置临时变量,防止溢出 if (init_done == 0) { InitLookupTable(); init_done = 1; } if ((x_dim % 2) || (y_dim % 2)) return 1; size = x_dim * y_dim; // allocate memory y_buffer = (unsigned char *)y_in; u_buffer = (unsigned char *)u_in; v_buffer = (unsigned char *)v_in; 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; if (!flip) { /*flip变量实现倒序,暂时未用到*/ } else { for (j = 0; j < y_dim; j++) { for (i = 0; i < x_dim; i++) { g = b + 1; r = b + 2;//bgr的顺序排放 r_temp = (*y) + YUVRGB14075[*v]; g_temp = (*y) - YUVRGB03455[*u] - YUVRGB07169[*v]; b_temp = (*y) + YUVRGB17790[*u]; if (r_temp < 0) r_temp = 0; if (r_temp > 255) r_temp = 255; if (g_temp < 0) g_temp = 0; if (g_temp > 255) g_temp = 255; if (b_temp < 0) b_temp = 0; if (b_temp > 255) b_temp = 255; *r = (unsigned char)r_temp; *g = (unsigned char)g_temp; *b = (unsigned char)b_temp; //防止溢出 b += 3; y++; u = u_buffer + j / 2 * x_dim / 2 + i /2 ; v = v_buffer + j / 2 * x_dim / 2 + i / 2; /*上变换的思路:没有采用用u/v填补为对应部分,而是只用u/v进行计算,在计算完一个值后,y直接加一,进行下一步计算,u/v进行跳转,而跳转的距离是y的一半,所以是x_dim/2。 if(j%2==0){ if (i % 2 == 0) { u = u_buffer + j / 2 * x_dim / 2 + i / 2; v = v_buffer + j / 2 * x_dim / 2 + i / 2; } else { u = u_buffer + j / 2 * x_dim / 2 + (i-1) / 2; v = v_buffer + j / 2 * x_dim / 2 + (i-1) / 2; } } else { if (i % 2 == 0) { u = u_buffer + (j-1) / 2 * x_dim / 2 + i / 2; v = v_buffer + (j-10 / 2 * x_dim / 2 + i / 2; } else { u = u_buffer + (j-1) / 2 * x_dim / 2 + (i - 1) / 2; v = v_buffer + (j-1) / 2 * x_dim / 2 + (i - 1) / 2; } } 以上是刚开始写的时候的程序,因为过于繁琐,而i,j是int型变量,做除法后会取整,所以简化*/ } } /* free(u_buffer); free(v_buffer);一开始没有注意, 运行过程中出错,经过逐步调试,并在该处出现断点,然后发现并没有开拓u_buffer,v_buffer的空间,不需要free这块空间*/ } 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++) YUVRGB17790[i] = (float)1.779 * (i-128); }//查找表法,在查找表中减去128,一是为了计算方便,二是以防止在整体计算时超出范围,保证U/V的范围在-128-127。图为转换两次的yuv文件,以及没有限制溢出的yuv文件,可以很明显看出右侧的红色区域和蓝色区域。 选取了几个较为明显的部分的宏块进行对比,三个文件分别是原始的yuv,有溢出的yuv,转换两次的yuv。
实验的工程共有3个,两个cpp文件,一个头文件,因为两个cpp文件都有调用头文件,所以在修改函数的时候,需要相互对应以防产生错误。以下是函数内的参数没有对应出现的结果
另外三组对比试验,可以证明该程序的准确性,图左侧为转换后的yuv,右侧为原始的yuv bus.yuv coastguard.yuv waterfall.yuv