前言
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
[1]:属于 YUV420P。也叫做 IYUV,YU12,存储格式:YYYYYYYY UU VV
YV12
[2]:属于 YUV420P。存储格式:YYYYYYYY VV UU
NV12
[3]:属于 YUV420SP。存储格式:YYYYYYYY UVUV
NV21
[4]:属于 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
代表“明亮度”就行了。什么“撇”不“撇”的(伽玛校正)就不用管了,因为太高深了!
请看具体示例:
上图中,第一张图最终显示的图像效果。第二张图是只有 Y′
分量的结果。第三张图是只有 U
分量的结果。第四张图是只有 V
分量的结果。
使用 YUV 的优点有两个:
- 彩色 YUV 图像转黑白 YUV 图像转换非常简单,这一特性用在于电视信号上;
- 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 的水平取样,垂直完全采样。
最常用的 YUV420P 和 YUV420SP 都是按 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 的排列顺序变化。例如:I420(YUV420Planar
的一种)为 YYYYYYYY UUVV
,NV21(YUV420SemiPlanar
的一种)则为 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 值。见下图:
通过上图可知,Y1,Y2,Y7,Y8 这个 2x2 的矩阵(4个像素)共享一个 U1,V1,也就是说每一个 U 和 V 对应 4 个像素。
将上图数据转换成数组后的存储形式如下:
根据上图可知(以下描述中的 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(x264,XviD,DivX),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上下载。
参考文献
- https://www.linuxtv.org/downloads/v4l-dvb-apis-old/re34.html
- https://www.fourcc.org/yuv.php
- https://zh.wikipedia.org/wiki/YUV
- https://www.polarxiong.com/archives/Android-YUV_420_888编码Image转换为I420和NV21格式byte数组.html
- https://www.polarxiong.com/archives/Android-Image类浅析-结合YUV_420_888.html
- https://blog.csdn.net/andrexpert/article/details/69267043
- https://blog.csdn.net/u010126792/article/details/86593199
- https://docs.microsoft.com/en-us/previous-versions/aa904813(v=vs.80)
- https://blog.csdn.net/byhook/article/details/84037338
- https://www.latelee.org/my-study/yuv-learning-yuv420p-to-rgb24.html
I420 yuv pixel format,V4L2_PIX_FMT_YVU420 (‘YV12’), V4L2_PIX_FMT_YUV420 (‘YU12’) ↩︎
YV12 yuv pixel format,V4L2_PIX_FMT_YVU420 (‘YV12’), V4L2_PIX_FMT_YUV420 (‘YU12’) ↩︎
NV12 yuv pixel format,V4L2_PIX_FMT_NV12 (‘NV12’), V4L2_PIX_FMT_NV21 (‘NV21’) ↩︎
NV21 yuv pixel format,[V4L2_PIX_FMT_NV12 (‘NV12’), V4L2_PIX_FMT_NV21 (‘NV21’)]( ↩︎