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处理的第一步。到时候咱们再细聊。