1、实验原理
由电视原理可知,亮度和色差信号的构成,为了使色差信号的动态范围控制在0.5之间,需要进行归一化,对色差信号引入压缩系数。在对分量信号进行8比特均匀量化时,共分为256个等间隔的量化级,让色差零电平对应码电平128。
所以RGB转YUV的公式为:
Y=0.2990R+0.5870G+0.1140B
U=0.493(B-Y)=-0.1684R-0.3316G+0.5B+128
V=0.877(R-Y)=0.5R-0.4187G-0.0813B+128
由此推导出YUV转RGB的公式为:
R=Y+1.4075(V-128)
G=Y-0.3455(U-128)-0.7169(V-128)
B=Y+1.779(U-128)
色度格式 4:2:0是指色差信号U,V的取样频率为亮度信号取样频率的四分之一,在水平方向和垂直方向上的取样点数均为Y的一半。因此在已知YUV文件,转成RGB时,要对色差信号U,V进行上取样,使U,V的取样点数在水平和垂直方向上均与Y相同,然后再套用公式将YUV转为RGB。
2、实验的流程分析
1)读入待转换的文件名,输出文件名,宽 ,高。
2)设置初始化的参数。
3)打开两个文件,变量赋值,开buffer。
4)读数据到yBuf,yBuf, yBuf中,然后对U,V进行上采样,使U,V的取样点数在水平和垂直方向上均与Y相同。
5)套用公式进行转换,此时应判断得到的r、g、b的值是否在0~255的范围内,若大于255,而让其等于255;若小于0,则让其等于0,最后得到rgbBuf,再将rgbBuf中的数据写入到rgb文件中。
6)由于没有RGB播放器,要将生成的RGB文件,通过老师提供的RGB转YUV程序,将其转为YUV文件。播放原有的YUV文件与转变后的YUV文件进行对比。
3、代码,重要部分参看代码注释
程序文件分为以下三个部分:
yuvtorgb.h
int YUV2RGB(int x_dim, int y_dim, void *rgb, void *y_in, void *u_in, void *v_in); void InitLookupTable();//函数原型 #ifndef YUV2RGB_H_ #define YUV2RGB_H_ #endif #ifndef stdlib_H_ #define stdlib_H_ #endif //基于编译预处理命令 yuvtorgb.c #include "stdlib.h" #include "yuvtorgb.h" static float YUVRGB14075[256]; static float YUVRGB03455[256], YUVRGB07169[256]; static float YUVRGB17790[256]; int YUV2RGB (int x_dim, int y_dim, void *rgb, void *y_in, void *u_in, void *v_in) { static int init_done = 0; long i, j, size; unsigned char *y, *u, *v; unsigned char *r, *g, *b; float tmpr,tmpg,tmpb;//中间变量声明,用来判定YUV是否处于0~255 unsigned char *pu1, *pu2, *pv1, *pv2, *psu, *psv; unsigned char *y_buffer, *u_buffer, *v_buffer; unsigned char *up_u_buf, *up_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; u_buffer =(unsigned char *)u_in; v_buffer =(unsigned char *)v_in; up_u_buf = (unsigned char *)malloc(size * sizeof(unsigned char)); up_v_buf = (unsigned char *)malloc(size * sizeof(unsigned char)); //分别用来存放上采样后的U、V,上采样后的U、V的取样点数与Y相同,均为size if (!(up_u_buf && up_v_buf)) { if (up_u_buf) free(up_u_buf); if (up_v_buf) free(up_v_buf); return 2; } /*upsample UV*/ for (j = 0; j < y_dim/2; j ++) { pu1 =up_u_buf+2 * j * x_dim; pu2 =up_u_buf + (2*j + 1) *x_dim; pv1 =up_v_buf + 2 * j * x_dim; pv2 =up_v_buf + ( 2*j + 1) *x_dim; psu = u_buffer + j * x_dim/2 ; psv = v_buffer+ j * x_dim/2; for (i = 0; i < x_dim/2; i ++) { *pu1=*psu; //由于4:2:0的色度格式,将原文件中U、V的值进行上采样 *(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; } } y = y_buffer; u = up_u_buf; v = up_v_buf; /*convert YUV to RGB*/ b=(unsigned char *)rgb;//RGB中是r、g、b三个指针放在一个buffer中,选择b为指针变量,r、g可以用b来表示 for (i = 0; i < size; i++) { g=b+1; r=b+2; /*通过中间变量来判断r、g、b是否在0—255范围内,大于255,使其等于255,小于0,使其等于0*/ tmpr=( (*y)+ YUVRGB14075[*v]); if(tmpr>255 ) tmpr=255; if(tmpr<0) tmpr=0; *r=(unsigned char)tmpr; tmpg=( (*y)- YUVRGB03455[*u]-YUVRGB07169[*v]); if(tmpg>255 ) tmpg=255; if(tmpg<0) tmpg=0; *g=(unsigned char)tmpg; tmpb=( (*y)+ YUVRGB17790[*u]); if(tmpb>255) tmpb=255; if(tmpb<0) tmpb=0; *b=(unsigned char)tmpb; b+= 3; y ++; u ++; v ++; } free(up_u_buf); free(up_v_buf); return 0;} void InitLookupTable()//将公式中的系数定义成数组的形式 { int i; for (i = 0; i < 256; i++) YUVRGB14075[i] = (float)1.0475 * (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.7790 * (i-128); }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 #define FALSE false #define TRUE true //用define定义,便于不同编程软件之间各种数据、字符类型的兼容 /*YUVtoRGB*/ int main(int argc, char** argv) { u_int frameWidth = 352; u_int frameHeight = 240; unsigned int i; 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; /*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 rgb file is %s\n", rgbFileName); } /* get an output buffer for a frame*/ rgbBuf = (u_int8_t*)malloc(frameWidth * frameHeight * 3); /*get the input buffers for a frame*/ yBuf = (u_int8_t*)malloc(frameWidth * frameHeight); uBuf = (u_int8_t*)malloc((frameWidth * frameHeight) / 4); vBuf = (u_int8_t*)malloc((frameWidth * frameHeight) / 4); if (rgbBuf == NULL || yBuf == NULL || uBuf == NULL || vBuf == NULL) { printf("no enought memory\n"); exit(1); } /*YUV 中Y、U、V分别存放在三个Buffer中,要分别读取*/while ((fread(yBuf, 1, frameWidth * frameHeight,yuvFile)) &&(fread(uBuf, 1, frameWidth * frameHeight/4,yuvFile)) &&(fread(vBuf, 1, frameWidth * frameHeight/4,yuvFile))) {if( YUV2RGB(frameWidth, frameHeight, rgbBuf, yBuf, uBuf, vBuf))//调用函数 {printf("error");return 0;} /*RGB中R、G、B存放在一个Buffer中*/ 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 (yBuf) free(yBuf); if (uBuf) free(uBuf); if (vBuf) free(vBuf); return(0); }命令行设置如图:
说明:1)YUV、RGB文件名根据自己的喜好设置,640、272为该文件的宽高数据,不同的文件数值不同。
2)工作路径目录一定要准确。
4、实验结果
(原始YUV文件(左)VS转换后的YUV文件(右))
示例1(宽,高:256,256)
示例2(宽,高:640,360)
示例3(宽,高:320,180)
示例4(宽,高:640,272)
以上示例说明,经程序YUV->RGB->YUV过程后的文件,与原始的YUV文件,并没有大的差异,进一步说明,上面给出的程序代码是可行的。
5、实验总结
1、将程序分为头文件、两个源代码文件,清晰,便于调试找错。
2、在此程序中,充分理解RGB、YUV文件中数据的存储方式,4:2:0色度格式的原理以及指针的使用。
3、注意在用for循环表示第j行第i个像素的算式。
4、程序写完只是一小步,调试找错的过程才是最重要的。
最后谢谢阅读,初出茅庐,望各位大神多加指教!