1.彩色空间转换的基本思想及转换公式
(1)YUV与RGB空间的相互转换
RGB->YUV基本公式:
Y=0.2990R+0.5870G+0.1140B
R-Y=0.7010R-0.5870G-0.1140B
B-Y=-0.2990R-0.5870G+0.8860B为了使色差信号的动态范围控制在0.5之间,需要进行归一化,对色差信号引入压缩系数。归一化后的色差信号为:
U=-0.1684R-0.3316G+0.5B
V=0.5R-0.4187G-0.0813B
YUV->RGB:
R=Y+1.14075(V-128)
G=Y-0.1769(V-128)-0.3455(U-128)
B=Y-1.779(u-128)
2.码电平分配及数字表达式
在对分量信号进行8比特均匀量化时,共分为256个等间隔的量化级,则Y’=Y+128。
色差信号经过归一化处理后,动态范围为-0.5-0.5,让色差零电平对应码电平128,所以RGBàYUV公式中最后U’=U+128,V’=V+128,而YUVàRGB公式中UV也要减去128再做处理。
3.色度格式
YUV4:2:0格式是指色差信号U,V的取样频率为亮度信号取样频率的四分之一,在水平方向和垂直方向上的取样点数均为Y的一半。
RGB格式中每个像素点R,G,B连续存储,取样格式为4:4:4。
而YUV格式有两大类:planar和packed:对于planar的YUV格式,先连续存储所有像素点的Y,紧接着存储所有像素点的U,随后是所有像素点的V;对于packed的YUV格式,每个像素点的Y,U,V是连续存储的。
在此实验中,采取YUVplanar格式。
则在转换过程中,RGB空间每四个像素点需使用同一个色度信号值,不同亮度值进行转换。
二、实验流程
1. 程序初始化(打开两个文件、定义变量和缓冲区等)
2. 读取YUV 文件,抽取YUV 数据写入缓冲区
3. 调用YUV2RGB 的函数实现YUV 到RGB 数据的转换
4. 写RGB 文件
5. 程序收尾工作(关闭文件,释放缓冲区)
三、实验代码及分析
YUV2RGB缓冲区分析
四个缓冲区:y_buf(width*height),u_buf(width*height/4),v_buf(width*height/4),
rgb_buf(width*height*3),分别存放亮度值数据,色差信号数据和输出结果数据。
用指针进行数据操作,实现YUV数据读取与RGB文件写入,具体如以下代码及注释:
main.cpp主要代码 int main(int argc, char** argv) { /* variables controlable from command line */ u_int frameWidth = 352; /* --width=<uint> */ u_int frameHeight = 240; /* --height=<uint> */ 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; /* 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); } 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; } for (i = 0; i < 3*frameWidth*frameHeight; i++) { if (rgbBuf[i] < 16) rgbBuf[i] = 16; if (rgbBuf[i] > 235) rgbBuf[i] = 235; } 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(rgbBuf) free(rgbBuf); if(yBuf) free(yBuf); if(uBuf) free(uBuf); if(vBuf) free(vBuf); //关闭文件 if(yuvFile) fclose(yuvFile); if(rgbFile) fclose(rgbFile); return(0); } yuv.cpp关键代码如下: static float YUVRGB14075[256]; static float YUVRGB03455[256], YUVRGB07169[256]; static float YUVRGB17790[256]; int YUV2RGB (int x_dim, int y_dim, 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; unsigned char *y, *u, *v; unsigned char *rgb_buffer; 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; y= (unsigned char *)y_in; u= (unsigned char *)u_in; v= (unsigned char *)v_in; if (!(rgb_buffer)) { if (rgb_buffer) free(rgb_buffer); return 2; } b=(unsigned char *)rgb_out; // convert RGB to YUV for(j=0; j<y_dim; j+=2) { unsigned char *py=y; unsigned char *py2=y+x_dim; unsigned char *pu=u; unsigned char *pv=v; unsigned char *pOUT=rgb_buffer; unsigned char *pOUT2=rgb_buffer+3*x_dim; float r1,r3,b1,b3,g1,g3,r21,r23,g21,g23,b21,b23; //使用float类型中间变量防失真 for(i=0; i<x_dim; i+=2) { //根据当前的*pY,*pY2,*pU和*pV计算rgb值 g=b+1; r=b+2; r1=(*py) + YUVRGB14075[*pv]; g1=(*py) - YUVRGB03455[*pu]-YUVRGB07169[*pv]; b1=(*py) + YUVRGB17790[*pu]; r3=*(py+1) + YUVRGB14075[*pv]; g3=*(py+1) - YUVRGB03455[*pu]-YUVRGB07169[*pv]; b3=*(py+1) + YUVRGB17790[*pu];r21=(*py2) + YUVRGB14075[*pv]; g21=(*py2) - YUVRGB03455[*pu]-YUVRGB07169[*pv]; b21=(*py2) + YUVRGB17790[*pu]; r23=*(py2+1) + YUVRGB14075[*pv]; g23=*(py2+1) - YUVRGB03455[*pu]-YUVRGB07169[*pv]; b23=*(py2+1) + YUVRGB17790[*pu]; //判断是否溢出再进行数据类型转换,防止溢出带来失真 *r=(r1>255?255:(r1<0?0:(unsigned char)r1)); *g=(g1>255?255:(g1<0?0:(unsigned char)g1)); *b=(b1>255?255:(b1<0?0:(unsigned char)b1)); *(r+3)=(r3>255?255:(r3<0?0:(unsigned char)r3)); *(g+3)=(g3>255?255:(g3<0?0:(unsigned char)g3)); *(b+3)=(b3>255?255:(b3<0?0:(unsigned char)b3)); //将此rgb值填入*pOUT指向的6个字节(重复两次) *(r+3*x_dim)=(r21>255?255:(r21<0?0:(unsigned char)r21)); *(g+3*x_dim)=(g21>255?255:(g21<0?0:(unsigned char)g21)); *(b+3*x_dim)=(b21>255?255:(b21<0?0:(unsigned char)b21)); *(r+3*(x_dim+1))=(r23>255?255:(r23<0?0:(unsigned char)r23)); *(g+3*(x_dim+1))=(g23>255?255:(g23<0?0:(unsigned char)g23)); *(b+3*(x_dim+1))=(b23>255?255:(b23<0?0:(unsigned char)b23)); //将此rgb值填入*pOUT2指向的6个字节(重复两次) b += 6; pu++;pv++;py+=2;py2+=2 ; //指针移动直至读取完两行数据 } //移动指针位置实现yuv数据读取及rgb文件写入 b += 3*x_dim; y+=2*x_dim; u+=x_dim/2; v+=x_dim/2; rgb_buffer+=2*3*x_dim; } 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.7790 * (i-128);}四、实验结果及分析 1.实验过程遇到的问题将实验转换的rgb图片再使用rgb2yuv转换为yuv文件进行对比,如图:在确定rgb数据的一开始,并未使用一个float中间变量,结果造成图片出现许多噪声,因为直接的数据类型转换得到的数据再做防溢出处理已经无用,已经溢出的数据无法再改变。 2.正确的实验结果修改代码,使用一个中间变量做防溢出处理,再写入rgb文件,不会再产生溢出现象。再对原始图片与实验所得图片进行对比如下,由于yuv转换为rgb格式时四个像素点共享同一个色度数据值,yuv格式恢复的rgb图片再度压缩恢复之后的yuv图片与原始图片在颜色上可能会稍有不同,但基本与原始图片没有明显差异 左为原始yuv图片,右为实验所得图接下来,再找三张yuv图片进行转化对比,左边均为原始yuv图片,右侧均为实验所得yuv图片