0%

[原创] YUV 基础知识

前言

Android 从 5.0(API 21, 2014年发布)开始,Google 已经将原来的 Camera 类废弃了,改用全新设计的功能更加强大的 Camera2,并且开始主推新的格式 YUV420。因此我们有必要先学习下有关 YUV 的基础知识。

Android 5 之前的版本,Camera Preview 支持的格式是包括 NV21, YV12,NV16,默认图像格式是 NV21,官方强烈建议使用 NV21 或 YV12。而对于 Andriod 5 及之后版本,支持全新的 YUV420Flexible 格式,配套 YUV_420_888

YUV420Flexible 并是一种具体的格式,而是一类 YUV 格式,包括 I420 还有旧版 Camera 支持的 NV21 和 YV12。YUV420Flexible 格式的最大优点就是速度快。在实时预览时,该格式至少可以达到 30 FPS。

结论

本文较长,考虑到大忙人不少,可能没时间看完全文,因此先说下结论:

常见 YUV420 格式说明:

I420[^i420]:属于 YUV420P。也叫做 IYUV,YU12,存储格式:YYYYYYYY UU VV

YV12[^yv12]:属于 YUV420P。存储格式:YYYYYYYY VV UU

NV12[^nv12]:属于 YUV420SP。存储格式:YYYYYYYY UVUV

NV21[^nv21]:属于 YUV420SP。存储格式:YYYYYYYY VUVU

需要源码的朋友,可以从我的 Gitee上下载。

好了,送走了大忙人之后,我们可以一起继续学习 YUV 相关知识了。

YUV 简介

YUV 是一种亮度信号 Y 和色度信号 U、V 是分离的色彩空间,它主要用于优化彩色视频信号的传输,使其向后相容老式黑白电视。与 RGB 视频信号传输相比,它最大的优点在于只需占用极少的频宽( RGB 要求三个独立的视频信号同时传输)。其中 Y′ 表示明亮度(Luminance 或 Luma),也就是灰阶值;而 U(Cb)V(Cr)表示的是色度(或浓度)(Chrominance 或 Chroma),作用是描述影像色彩及饱和度,用于指定像素的颜色。具体来讲, U(Cb) 代表的是“蓝色投影”, V(Cr) 代表的是“红色投影”。

说明:“明亮度(luminance)”用 Y 表示。Y′ 表示 luma。其中 prime symbol()(我也不知道中文管这个符号叫什么,以前一直叫“小撇”)符号代表“伽玛校正”。其实也不用分的这么细,只要记住 Y 代表“明亮度”就行了。什么“撇”不“撇”的(伽玛校正)就不用管了,因为太高深了!

请看具体示例:

YUV-Sample

上图中,第一张图最终显示的图像效果。第二张图是只有 Y′ 分量的结果。第三张图是只有 U 分量的结果。第四张图是只有 V 分量的结果。

使用 YUV 的优点有两个:

  1. 彩色 YUV 图像转黑白 YUV 图像转换非常简单,这一特性用在于电视信号上;
  2. YUV 是数据总尺寸小于 RGB 格式;

YUV 与 RGB 区别

YUV 的存储中与 RGB 格式最大不同在于,RGB 格式每个点的数据是连继保存在一起的。即 R,G,B 是前后不间隔的保存在2~4byte 空间中。而 YUV 的数据中为了节约空间,U,V分量空间会减小。每一个点的Y分量独立保存,但连续几个点的 U,V 分量是保存在一起的,通常人的肉眼察觉不出。

YUV 格式

YUV 格式分为两种类型:

  • 紧缩格式(Packed 格式):Packed 类型是将 Y、U、V 分量存在在同一个 Macro Pixels 数组中,和 RGB 的存放方式类似。每个像素点的 Y、U、V 是连续交错存储的。
  • 平面格式(Planar 格式):将 Y、U、V 分量分别存放到三个独立的数组中,先连续存储所有像素点的 Y,紧接着存储所有像素点的 U,最后是所有像素点的 V。

Packed 格式中,YUV 是混合在一起的,对于 YUV4:4:4 格式而言,用紧缩格式很合适的,因此就有了 UYVY、YUYV 等。
Planar 格式中,每 Y 分量,U 分量和 V 分量都是以独立的平面组织的。常见平面格式(planar format)有 I420、YV12 等。

为节省带宽起见,大多数 YUV 格式平均使用的每像素位数都少于24位。主要的抽样(subsample)格式有YCbCr 4:2:0、YCbCr 4:2:2、YCbCr 4:1:1和YCbCr 4:4:4。YUV 的表示法称为 A:B:C 表示法,每个像素由一组 YUV 组成:

  • 4:4:4 表示完全取样。每一个Y对应一组U/V分量。每像素(即一组YUV)占3个字节(8+8+8=24bits)
  • 4:2:2 表示 2:1 的水平取样(即水平方向隔一行采样一次U/V分量),垂直完全采样。每2个Y共用一组UV分量。每像素(即一组YUV)占2个字节(8+4+4=16bit)
  • 4:2:0 表示 2:1 的水平取样(即水平方向隔一行采样一次U/V分量),垂直 2:1 采样(即垂直方向隔一行采样一次U/V分量)。每4个共用一组UV分量。每像素(即一组YUV)占1.5个字节(8+2+2=12bits)或2个字节(8+4+4=16bits)
  • 4:1:1 表示 4:1 的水平取样,垂直完全采样。

最常用的 YUV420PYUV420SP 都是按 YUV 4:2:0 的方式采样的。而且 DVD-Video 也是以 YUV 4:2:0 的方式采样的,具体格式为 I420,YUV4:2:0 并不是说只有 U(即Cb), V(即Cr)一定为0,而是指 U:V 互相援引,时见时隐,也就是说对于每一个行,只有一个 U 或者只有一个V 分量。如果一行是 4:2:0 的话,下一行就是 4:0:2,再下一行是 4:2:0,以此类推。至于其它常见的 YUV 格式有 YUY2、YUYV、YVYU、UYVY、AYUV、Y41P、Y411、Y211、IF09、IYUV、YV12、YVU9、YUV411、YUV420等。

YUV420

首先说明下,大家从网查关于 YUV 的资料时会看到很多格式,每个人的写法和叫法也不太一样。这对于那些刚开始接触 YUV 的人来讲很是迷糊,我刚开始就这样。不过看了超多文档后(真的是看了超多文档!),慢慢的明白了那些网上关于 YUV 具体格式的叫法,发现网上的资料有的说的对,有的说的也不对。主要是不准确(国内的很多资料一看就是被大量转载的,准确度不高,而且很多重要的知识点没有说清楚。国外资料相对好一些,但是也不是很准确。无论是国内资料还是国外资料,主要问题都体现在在针对特定机型时,例子和讲解的内容是正确的,但是换个机型就不好用了)。我这边先总结一下术语

YUV420 并不是一种具体的格式,而是代表一组格式。根据颜色数据的存储顺序不同,又分为了多种不同的格式。其中最常用的 YUV420 分为两大类:YUV420P(非具体格式,也代表一组格式) 和 YUV420SP(非具体格式,也代表一组格式):

  • YUV420Planar(即 YUV420P):包含 I420,YV12。这些才是具体的 YUV420 格式。I420 与 YV12 一样,唯一区别是 U/V 顺序不同,详见后文。
  • YUV420SemiPlanar(即 YUV420SP):包含 NV12,NV21。这些才是具体的 YUV420 格式。NV12 和 NV21 基本相同,仅 U/V 顺序不同,详见后文。
  • YUV420PackedPlanar
  • YUV420PackedSemiPlanar

注意:只有确定了具体格式,才能知道其准确的数据存储方式。
说明:大家上网查阅资料时会发现,网上很多文章将 YUV420P 等同于 I420,将 YUV420SP 等同于 NV12 了。看了上面的介绍,读者应该明白了,这种说法其实是不准确的。YUV420P 和 YUV420SP 代表的是一组格式,并不是某一种具体格式。而 I420,NV21 等才是具体的格式。读者在查阅网上资料时,需要注意该问题。

上述这些格式实际存储的信息是完全一致的。举例来说,对于 4x4 的图片,在 YUV420 下,任何格式都有16个 Y 值,4个 U 值和4个 V 值,不同格式只是 Y、U 和 V 的排列顺序变化。例如:I420YUV420Planar 的一种)为 YYYYYYYY UUVVNV21YUV420SemiPlanar 的一种)则为 YYYYYYYY VUVU。也就是说,YUV420 是一类格式的集合,并不能完全确定颜色数据的存储顺序。只有确定了具体格式,才能知道其准确的数据数据存储方式。

I420,YV12:Y、U 和 V 三个分量的数据分别保存在三个 Plane 中。4 个 Y 分量共享一个 U/V 分量。
NV12,NV21:是一种 two-plane 模式。即 Y 和 U/V 分为两个 Plane, Y 占用一个 Plane,U/V 共用一个 Plane 且 U/V 数据交错存储。同样是 4 个 Y 分量共享一个 U/V 分量。

前面已经简要的介绍了 YUV。这里主要简述下 YUV420。YUV 根据 U 和 V 采样数目的不同,分为 YUV444、YUV422 和 YUV420 等,而 YUV420 表示的就是每个像素点有一个独立的亮度表示,即 Y 分量;而色度,即 U 和 V 分量则由每4个像素点共享一个(即每 2x2 个 Y 分量共享一个 U,V 分量)。也就是说,Y 分量的数量与图像的总像素数是一致的,而 U 和 V 分量数是 Y 分量数的 1/4。举例来说,对于 6x4 的图片,在 YUV420 下,有24个 Y 值,6个 U 值和6个 V 值。见下图:

YUV420 Single Frame

通过上图可知,Y1,Y2,Y7,Y8 这个 2x2 的矩阵(4个像素)共享一个 U1,V1,也就是说每一个 U 和 V 对应 4 个像素。
将上图数据转换成数组后的存储形式如下:

Position In Byte Stream

根据上图可知(以下描述中的 x 代表图像的宽度,y 代表图像的高度):

  • Y 分量的起始索引值为 0。
  • U 分量的起始索引值为 x * y(以当前示例的话,U 分量起始索引值为 6 * 4 = 24)。
  • V 分量的起始索引值为 x * y + (x * y) ÷ 4(以当前示例的话,V 分量起始索引值为 6 * 4 + (6 * 4) ÷ 4 = 30)。
YUV420 具体格式 所属类型 说明
I420 YUV420P 所有 Y 在最面,之后是所有的 U 在前,最后是 所有的 V。例如:YYYYYYYY UUVV
YV12 YUV420P 和 I420 一样,不同之处是 U 与 V 数据反转。V 在前,U 在后。即 YV 表示 Y 后面是 V。12 表示 12 bit。例如:YYYYYYYY VVUU。YUV420P 与 YV12 可以使用相同的算法进行处理。许多重要的编码器都采用YV12空间存储视频:MPEG-4(x264XviDDivX),DVD-Video存储格式MPEG-2,MPEG-1以及MJPEG。
NV12 YUV420SP U/V 交错存储,U 在前,V 在后。例如:YYYYYYYY UVUV。(注意:网上很多文章都写错了,将 NV12 和 NV21 的存储形式写反了。)
NV21 YUV420SP Android Camera 的标准图像格式(即老版的 Camera,非 Camera2),与 NV12 类似,仅U/V顺序不同。U/V 也是交错存储,不过 V 在前, U在后。例如:YYYYYYYY VUVU。(注意:网上很多文章都写错了,将 NV12 和 NV21 的存储形式写反了。)

再次提醒大家,网上很多文章都将 NV12 和 NV21 的存储形式写反了,估计是一人写错,大家一转载导致错误被广大了。NV12 是 YYYYYYYY UVUV 这样存储的(不是 YYYYYYYY VUVU),NV21 是 YYYYYYYY VUVU 这样存储的(不是 YYYYYYYY UVUV)。而且 Android 官方文档也写明了:

NV21: YCrCb format used for images, which uses the NV21 encoding format.

看到了吧,NV21 是 YCrCb 格式,也就是 V 在前,U在后的顺序。感兴趣的还可以查阅下 LinuxTV 的官方文档对 NV12 及 NV21 的说明。

总结:

  • YUV420P: U/V 数据是连续存储的,该类型下的具体格式之间的差别仅在于 U/V 的存储顺序不同。

  • YUV420SP: U/V 数据是交替存储的,该类型下的具体格式之间的差别仅在于 U/V 的存储顺序不同。

  • YUV420 占用的内存空间大小是:width×height × 3 ÷ 2 单位:byte

    • Y = width × height
    • U = Y ÷ 4
    • V = Y ÷ 4

Android YUV420 分量

Android 对从 Camera2 返回的 YUV420 数据有自己的处理,从而让我们使用起来更方法。强烈建议大家阅读下我写的另一篇文章“Android 使用 Camera2 获取实时摄像头数据并实时编码成 H.264”一定会帮助到你。(我可是花了很长很长时间来学习并撰写文章的,都是干货!)

源代码

需要源码的朋友,可以从我的 Gitee上下载。

参考文献

*[I420]: 属于 YUV420P。也叫做 IYUV,YU12。存储格式:YYYYYYYY UU VV
*[YV12]: 属于 YUV420P。存储格式:YYYYYYYY VV UU
*[NV12]: 属于 YUV420SP。存储格式:YYYYYYYY UVUV
*[NV21]: 属于 YUV420SP。存储格式:YYYYYYYY VUVU

[^i420]: I420 yuv pixel formatV4L2_PIX_FMT_YVU420 (‘YV12’), V4L2_PIX_FMT_YUV420 (‘YU12’)
[^yv12]: YV12 yuv pixel formatV4L2_PIX_FMT_YVU420 (‘YV12’), V4L2_PIX_FMT_YUV420 (‘YU12’)
[^nv12]: NV12 yuv pixel formatV4L2_PIX_FMT_NV12 (‘NV12’), V4L2_PIX_FMT_NV21 (‘NV21’)
[^nv21]: NV21 yuv pixel format,[V4L2_PIX_FMT_NV12 (‘NV12’), V4L2_PIX_FMT_NV21 (‘NV21’)](

坚持原创及高品质技术分享,您的支持将鼓励我继续创作!