另一方面,前言本次试验内容是将256*256、4:2:0采样的yuv图像转换为rgb格式。 老师提供了rgb2yuv的源代码,分析后发现源代码已经极其优雅高效,命名方式合理,内存分配适中,采用查找表的方法,在空间上改变时间责任度。 因此,这次以直接源代码为基础,进行了一些变更,得到了逆变换。
二、由公式推导出1.rG b2yuv在电视系统中红、绿、蓝称为三基色,分别用(R e )、G e )、gb )表示。 电视监视器上的各色光用这三种颜色的荧光体调制。
亮度方程:Y=0.2990R+0.5870G+0.1140B
从这里得到的东西:
Y0.2990R+0.5870G+0.1140B
U = B Y 0.2990 R 0.5870 G + 0.8860 B
V = R Y 0.7010 R 0.5870 G 0.1140 B
2.yuv2rgb从上述公式进行逆变换,可以得到以下内容。
转换后的最终形式如下
R = (298 Y+411 V - 57376) / 256
G = (298 Y - 101 U - 211 V + 35168) / 256
B = (298 Y + 519 U - 71200) / 256
三、代码实现1 .读取图像YUVfile=fopen(YUVfilename,' rb ' ); if(YUVfile==null ) printf(cannotfindYUVfile(n ); 退出(1; } else { printf (theinputyuvfileis % s (n ),yuvFileName ); }/* opentherawfile */RGB file=fopen (RGB filename,' wb ' ); if(RGBfile==null ) printf(cannotfindRGBfile(n ); 退出(1; } else { printf (' theoutputrgbfileis % sn ',RGB文件名称); }/* getanoutputbufferforaframe */RGB buf=(u _ int8_ t * ) malloc ) framewidth*frameheight*3);/* gettheinputbuffersforaframe */ybuf=(u _ int8_ t * ) malloc (帧宽度*帧高度); Ubuf=(u_int8_t* ) malloc ) )框架宽度*框架高度)/4 ); vbuf=(u_int8_t* ) malloc ) )框架宽度*框架高度)/4 ); fread(ybuf,1,帧宽*帧高,YUV文件); fread(Ubuf,1,帧宽*帧高/4,YUV文件); fread(vbuf,1,frameWidth * frameHeight/4,yuvFile ) )这次使用的是c语言,但rgb和yuv的读取思路与上次基本一致。 通过限制每次读取的大小,将文件保存到每个预先分配了空间的buffer中。
2.yuv转换为rgb注意事项:使用YUVviewerPlus (如下图所示)打开rgb文件时,图像将被识别为bmp图像,打开后上下翻转。 但是,这次实验直接使用上次自己写的python文件打开rgb图像,所以不需要反转图像。
if (! flip(else ) for ) intI=0; i x_dim; I ) for(intj=0; j y_dim; j () {g=b 1; r=b 2; int b_tmp、g_tmp、r_tmp; b_tmp=() YUV RGB 298 [ * y ] YUV RGB 519 [ * u ]-71200 )/256 ); g_tmp=(YUVRGB298(*y ) yuvrgbf101 ) ) u ) yuvrgbf211(*v ) 35168 ) )/256 ); r _ tmp=(YUV RGB 298 [ * y ] YUV RGB 411 [ * v ]-57376 )/256 ); if(b_tmp0) b_tmp=0; if(b_tmp255 ) b_tmp=255; * b=(未指定char ) b_tmp; if(g_tmp0) g_tmp=0; if(g_tmp255 ) g_tmp=255; * g=(未指定char ) g_tmp; if(r_tmp0) r_tmp=0; if(r_tmp255 ) r_tmp=255; *r=(unsign
ed char)r_tmp;b += 3;y++;if (i % 2 == 0 && j == 0) //奇数行开头{u -= y_dim / 2;v -= y_dim / 2;k -= y_dim / 2;}if (j % 2 != 0)//偶数列{u++;v++;k++;}}}}代码关键解读:
a) 使用查找表 void InitLookupTable(){int i;for (i = 0; i < 256; i++) YUVRGB298[i] = (float)298 * i;for (i = 0; i < 256; i++) YUVRGB411[i] = (float)411 * i;for (i = 0; i < 256; i++) YUVRGBF101[i] = (float)-101 * i;for (i = 0; i < 256; i++) YUVRGBF211[i] = (float)-211 * i;for (i = 0; i < 256; i++) YUVRGB519[i] = (float)519 * i;}在进行运算之前,提前将可能的取值(0-255)全部计算,并将结果保存在数组中。如此在实际运算中直接查找数组提取结果值就可以了,大大提升了代码效率,是一种空间换取时间的做法。
在日后编写代码中也要常记这个tip。
若是直接将运算结果赋值给rbg指针,将会是下图结果。
(在运算上图时有纰漏,误写了算法的公式导致天空色彩有误)
可以看到图像出现了很多饱和度较高,色彩一致的斑点。这是由于在计算后数值可能低于0或高于255,若是直接赋值给unsigned char类型变量会导致数值溢出。故先赋值给int类型变量,做一个溢出判定。
int b_tmp, g_tmp, r_tmp;b_tmp = ((YUVRGB298[*y] + YUVRGB519[*u] - 71200) / 256);g_tmp = ((YUVRGB298[*y] + YUVRGBF101[*u] + YUVRGBF211[*v] + 35168) / 256);r_tmp = ((YUVRGB298[*y] + YUVRGB411[*v] - 57376) / 256);if (b_tmp < 0) b_tmp = 0;if (b_tmp > 255) b_tmp = 255;*b = (unsigned char)b_tmp;if (g_tmp < 0) g_tmp = 0;if (g_tmp > 255) g_tmp = 255;*g = (unsigned char)g_tmp;if (r_tmp < 0) r_tmp = 0;if (r_tmp > 255) r_tmp = 255;*r = (unsigned char)r_tmp; c) 指针移动此实验yuv图像为4:2:0格式,采样点如下图:
在rgb2yuv代码中,需要对运算后的u,v的buffer进行下采样,将256256的空间转化为128128的尺寸,代码如下:
而在yuv2rgb时,需要将128128的u,v buffer上采样为256256。但为了代码的简洁高效,没有这样做。而是直接利用指针的移动达到此目的。
if (i % 2 == 0 && j == 0) //奇数行开头{u -= y_dim / 2;v -= y_dim / 2;k -= y_dim / 2;}if (j % 2 != 0)//偶数列{u++;v++;k++;}在横向,每当偶数列时u,v向下移动一个;
在纵向,每当到达奇数行开头时,u,v往回移动128个,重新遍历一遍本行数值。
最终结果
原图转换后四、误差分析虽然输出图像肉眼看起来无异,但在计算过程中,有以下几个环节会产生误差:
1.采样。在rgb图像yuv图像过程中,由于4:2:0的采样格式,u,v像素值变为原来的1/4,损失了大量信息。当再由yuv图像转为rgb时,信息量已经不是完整的了。 2.YUV与RGB色彩空间并不重合,在色彩空间变换时存在溢出。 3.在浮点运算时产生误差。