前言
对一种颜色进行编码的方法统称为”颜色空间”或”色域”,RGB和YUV,都是颜色空间的种类。iOS中获取到的摄像头的原始数据,常见也就是RGB和YUV。RGB可能大家都比较熟,就是每个像素由三原色分量组成,在计算机存储中三个分量的值在0~255之间。YUV也是有三个分量的,只是存储的信息与RGB是完全不一样的。
YUV简介
YUV(亦称YCrCb)是被欧洲电视系统所采用的一种颜色编码方法(属于PAL)。YUV主要用于优化彩色视频信号的传输,使其向后兼容老式黑白电视。主要用于电视系统以及模拟视频领域,它将亮度信息(Y)与色彩信息(UV)分离,没有UV信息一样可以显示完整的图像,只不过是黑白的,这样的设计很好地解决了彩色电视机与黑白电视的兼容问题。与RGB视频信号传输相比,它最大的优点在于只需占用极少的带宽(RGB要求三个独立的视频信号同时传输)。
YUV格式有两大类:planar和packed。
- 对于planar的YUV格式,先连续存储所有像素点的Y,紧接着存储所有像素点的U,随后是所有像素点的V。
- 对于packed的YUV格式,每个像素点的Y,U,V是连续交*存储的。
YUV,分为三个分量,“Y”表示明亮度(Luminance或Luma),也就是灰度值;而“U”和“V” 表示的则是色度(Chrominance或Chroma),作用是描述影像色彩及饱和度,用于指定像素的颜色。YUV码流的存储格式其实与其采样的方式密切相关,主流的采样方式有三种,YUV4:4:4,YUV4:2:2,YUV4:2:0,如图1所示,以黑点表示采样该像素点的Y分量,以空心圆圈表示采用该像素点的UV分量。

图1、主流YUV采样图
按照图1所示,每种采样的区别在于YUV分量的比例,我们知道仅有Y分量就可以组成一幅完整的黑白画面,UV控制的是色彩和饱和度,降低UV的采样率并不会明显降低视觉质量,只是色度会减弱。
- YUV 4:4:4采样,每一个Y对应一组UV分量。
- YUV 4:2:2采样,每两个Y共用一组UV分量。
- YUV 4:2:0采样,每四个Y共用一组UV分量。
YUV420P结构分析
YUV420P结构比较简单,应用比较广泛。在YUV420P中,P代表planar,意味着像素点的YUV都是是分开存储的,还可以分为I420和YV12。I420格式和YV12格式的不同处在U平面和V平面的位置:
- I420: YYYYYYYY UU VV =>YUV420P
- YV12: YYYYYYYY VV UU =>YUV420P

图2、YUV420P I420结构图
在内存中是以Y1 Y2 ... Y32 U1 U2 ... U8 V1 V2 ... V8去存储的,所以必须要知道图片宽度才能正确解析出。图2中排法让我们更直观的看出分量间的关系,不同的色块对应着不同的组合关系,比如Y1、Y2、Y9、Y10对应的UV采集为U1、V1。
YUV420P与RGB24的转化
显示设备都是RGB的三原色显示标准,想要在屏幕中显示YUV图像需要将其转为RGB图像,转换是通过公式计算的方式完成的,在网上搜索会出现很多公式,有些就差小数点几位,但是都能正常地转化。至于为什么这么多种,可以参考一下《YUV与RGB互转各种公式 (YUV与RGB的转换公式有很多种,请注意区别!!!)》这篇文章,差小数点后几位的真的不知道哪里来的差别。。。
这里就用雷神程序里的计算公式吧:
YUV420P -> RGB24
1 2 3
| R = Y + ( 1.4075 * (V - 128) ); G = Y - ( 0.3455 * (U - 128) - 0.7169 * (V - 128) ); B = Y + ( 1.7790 * (U - 128) );
|
RGB24 -> YUV420P
1 2 3
| Y= 0.299 * R + 0.587 * G + 0.114 * B U= -0.147 * R - 0.289 * G + 0.463 * B V= 0.615 * R - 0.515 * G - 0.100 * B
|
YUV420P生成与显示
了解完YUV420P的存储方式以及转化显示原理,可以找一张图片过来试试,也可以自己手动生成一张图片,验证一下以上的理论。这里我直接拿雷神的生成YUV420P格式的灰阶测试图程序来验证。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70
|
int simplest_yuv420_graybar(int width, int height,int ymin,int ymax,int barnum,char *url_out){ int barwidth; float lum_inc; unsigned char lum_temp; int uv_width,uv_height; FILE *fp=NULL; unsigned char *data_y=NULL; unsigned char *data_u=NULL; unsigned char *data_v=NULL; int t=0,i=0,j=0; barwidth=width/barnum; lum_inc=((float)(ymax-ymin))/((float)(barnum-1)); uv_width=width/2; uv_height=height/2; data_y=(unsigned char *)malloc(width*height); data_u=(unsigned char *)malloc(uv_width*uv_height); data_v=(unsigned char *)malloc(uv_width*uv_height); if((fp=fopen(url_out,"wb+"))==NULL){ printf("Error: Cannot create file!"); return -1; } printf("Y, U, V value from picture's left to right:\n"); for(t=0;t<(width/barwidth);t++){ lum_temp=ymin+(char)(t*lum_inc); printf("%3d, 128, 128\n",lum_temp); } for(j=0;j<height;j++){ for(i=0;i<width;i++){ t=i/barwidth; lum_temp = ymin + (char)(t * lum_inc); data_y[j*width+i]=lum_temp; } } for(j=0;j<uv_height;j++){ for(i=0;i<uv_width;i++){ data_u[j*uv_width+i]=128; } } for(j=0;j<uv_height;j++){ for(i=0;i<uv_width;i++){ data_v[j*uv_width+i]=128; } } fwrite(data_y,width*height,1,fp); fwrite(data_u,uv_width*uv_height,1,fp); fwrite(data_v,uv_width*uv_height,1,fp); fclose(fp); free(data_y); free(data_u); free(data_v); return 0; }
|
调用如下,传入宽高、Y分量的取值范围以及灰度条的个数,这里传了10,最后一个参数是生成图片的存储路径。程序中我们看到,Y分量根据不同的灰度递增,U和V都为128,根据转换公式,在转为RGB时RGB分量都取值Y。也可以随意修改UV值看能得出一个什么样的图片,进一步地理解。关于YUV图像的更多处理请参考《视音频数据处理入门:RGB、YUV像素数据处理》。
1
| simplest_yuv420_graybar(640, 360, 0, 255, 10, "graybar_640x360.yuv");
|
显示也是参考雷神《最简单的视音频播放示例5:OpenGL播放RGB/YUV》,这里截出转化的代码,其余部分可以去雷神的博客看看,比较浅显易懂。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47
| inline unsigned char CONVERT_ADJUST(double tmp) { return (unsigned char)((tmp >= 0 && tmp <= 255)?tmp:(tmp < 0 ? 0 : 255)); }
void CONVERT_YUV420PtoRGB24(unsigned char* yuv_src,unsigned char* rgb_dst,int nWidth,int nHeight) { unsigned char *tmpbuf=(unsigned char *)malloc(nWidth*nHeight*3); unsigned char Y,U,V,R,G,B; unsigned char* y_planar,*u_planar,*v_planar; int rgb_width , u_width; rgb_width = nWidth * 3; u_width = (nWidth >> 1); int ypSize = nWidth * nHeight; int upSize = (ypSize>>2); int offSet = 0; y_planar = yuv_src; u_planar = yuv_src + ypSize; v_planar = u_planar + upSize; for(int i = 0; i < nHeight; i++) { for(int j = 0; j < nWidth; j ++) { Y = *(y_planar + nWidth * i + j); offSet = (i>>1) * (u_width) + (j>>1); V = *(u_planar + offSet); U = *(v_planar + offSet); R = CONVERT_ADJUST((Y + (1.4075 * (V - 128)))); G = CONVERT_ADJUST((Y - (0.3455 * (U - 128) - 0.7169 * (V - 128)))); B = CONVERT_ADJUST((Y + (1.7790 * (U - 128)))); offSet = rgb_width * i + j * 3; rgb_dst[offSet] = B; rgb_dst[offSet + 1] = G; rgb_dst[offSet + 2] = R; } } free(tmpbuf); }
|

图3、YUV灰阶测试图显示
后话
这里只是对YUV420P图像结构的基本了解,往后YUV图像采集和播放有一定的认识。终于也对大学时学的MATLAB函数有点了解,如果当初先了解这些,也许我会更有兴趣学习图像处理吧。
参考
《最简单的视音频播放示例5:OpenGL播放RGB/YUV》
《视音频数据处理入门:RGB、YUV像素数据处理》
《图文详解YUV420数据格式》
《从零开始学习音视频编程技术(十五) YUV420P转RGB32》
《YUV与RGB互转各种公式 (YUV与RGB的转换公式有很多种,请注意区别!!!)》