2、YUV格式详解:YUV420、YUV422、YUV444的采样与存储

好,咱们接着聊YUV。上一章我讲了YUV和RGB的根本区别,说白了就是亮度和色度分离。这一章咱们深入进去,看看YUV在实际存储时,到底是怎么“偷工减料”的。

你可能会问:为什么YUV要搞出这么多格式?直接存完整的RGB不行吗?嗯,这里有个现实问题——带宽和存储空间。我在做手机摄像头驱动时,传感器一秒钟要输出几千万个像素点,如果每个像素都存完整的Y、U、V三个分量,数据量直接爆炸。所以,必须想办法压缩。

怎么压缩?人眼对亮度敏感,对颜色不敏感。那就把色度信息砍掉一部分,视觉上还看不出区别。这就是YUV采样的核心思想。

2.1 采样格式:4:4:4、4:2:2、4:2:0

这三个数字代表什么?我习惯这么理解:

  • 第一个数字4:表示每行有4个像素的亮度采样(Y)。
  • 第二个数字4:表示第一行有4个像素的色度采样(U或V)。
  • 第三个数字4:表示第二行有4个像素的色度采样(U或V)。

说白了,就是看色度信息在水平和垂直方向上被“稀释”了多少。

YUV 4:4:4 —— 无损采样

每个像素都有完整的Y、U、V三个分量。一个像素占3个字节(如果每个分量8bit)。

这种格式画质最好,但数据量也最大。我在做专业相机驱动时用过,普通手机摄像头基本不用,太费带宽了。

YUV 4:2:2 —— 水平减半

每两个像素共享一组U和V。也就是说,每行中,U和V的采样率是Y的一半。

举个例子:4个像素,Y有4个,U只有2个,V只有2个。一个像素平均占2个字节。

这种格式在广播级视频里很常见。我记得以前调一个HDMI输入驱动,用的就是4:2:2,画质损失肉眼几乎看不出来。

YUV 4:2:0 —— 最常用的格式

这个要重点讲。4:2:0的意思是:每4个像素(2x2的块)共享一组U和V。

具体来说:

  • 第一行:Y有4个,U有2个,V有2个(但V其实不存,只存U)
  • 第二行:Y有4个,U有0个,V有2个(只存V)

说白了,就是U和V在水平和垂直方向上都减半了。一个像素平均只占1.5个字节。

重要结论:4:2:0不是“没有V”,而是U和V交替出现,每4个像素才有一组完整的UV。这是目前最主流的格式,从JPEG图片到H.264视频,再到手机摄像头,全都在用。

2.2 存储布局:平面模式 vs 交错模式

采样格式决定了“存多少数据”,但怎么存?这里有两种流派。

平面模式(Planar)

把所有Y数据连续存一块,然后所有U数据存一块,最后所有V数据存一块。就像三块独立的画布叠在一起。

举个例子,一张4x4像素的YUV420图像:

Y平面:Y00 Y01 Y02 Y03 Y10 Y11 Y12 Y13 Y20 Y21 Y22 Y23 Y30 Y31 Y32 Y33
U平面:U00 U01 U10 U11
V平面:V00 V01 V10 V11

这种布局对编解码器特别友好,因为可以批量读取Y数据做亮度处理。我在做ISP(图像信号处理器)驱动时,平面模式是首选。

交错模式(Interleaved / Packed)

把Y、U、V数据交错排列。比如NV12格式:先存所有Y,然后交错存UV。

Y平面:Y00 Y01 Y02 Y03 Y10 Y11 Y12 Y13 ...
UV交错:U00 V00 U01 V01 U10 V10 U11 V11 ...

还有一种叫YUYV,直接Y和UV交替:

Y00 U00 Y01 V00 Y02 U01 Y03 V01 ...

我的经验:做HAL层驱动时,平面模式更容易做DMA传输,因为内存连续。但有些硬件加速器只支持交错模式。你拿到一个摄像头传感器,第一件事就是看它的输出格式是Planar还是Packed,这个搞错了,图像直接花屏。

2.3 常见YUV格式速查表

格式名称 采样方式 存储布局 每像素比特 典型应用
I420 4:2:0 平面 Y + U + V 12 视频编码、软件处理
NV12 4:2:0 平面 Y + 交错 UV 12 Android摄像头、硬件编码
NV21 4:2:0 平面 Y + 交错 VU 12 老款Android手机
YUYV 4:2:2 交错 YUYV 16 USB摄像头、采集卡
UYVY 4:2:2 交错 UYVY 16 广播设备
YUV444 4:4:4 平面或交错 24 专业图像处理

避坑指南:我曾经在调试一个USB摄像头时,驱动里配的是NV12,但硬件输出的是NV21。结果图像颜色完全不对,人脸是紫色的。查了两天才发现是U和V的顺序反了。所以,拿到新硬件,一定要先确认U和V的排列顺序。

2.4 实战:如何计算YUV图像的大小

这个技能在HAL层开发中天天用。你要分配DMA缓冲区,必须知道要申请多少内存。

假设图像宽W像素,高H像素:

  • YUV 4:4:4:总大小 = W × H × 3 字节
  • YUV 4:2:2:总大小 = W × H × 2 字节
  • YUV 4:2:0:总大小 = W × H × 1.5 字节

以1920x1080的YUV420图像为例:

Y平面大小 = 1920 × 1080 = 2,073,600 字节
U平面大小 = (1920/2) × (1080/2) = 518,400 字节
V平面大小 = (1920/2) × (1080/2) = 518,400 字节
总大小 = 2,073,600 + 518,400 + 518,400 = 3,110,400 字节 ≈ 2.97 MB

嗯,这里要注意:有些硬件要求行对齐(stride),比如每行必须是16字节对齐。那实际分配的内存会比理论值大一点。我一般会这样算:

stride = (width + 15) / 16 * 16;  // 16字节对齐
y_size = stride * height;
uv_size = (stride/2) * (height/2) * 2;  // UV各一半
total = y_size + uv_size;

2.5 小结

这一章我们聊了YUV的三种采样方式,以及它们怎么在内存里摆。说白了,4:2:0是王者,4:2:2是备胎,4:4:4是奢侈品。你在HAL层打交道最多的,就是NV12和NV21。

下一章,我会带你看看RAW格式。那个更原始,更“暴力”,但也是ISP处理的第一步。到时候咱们再细聊。