1. YUV色彩空间基础:从RGB到YUV的转换与高通平台数据排布

做ISP图像处理,YUV是绕不开的核心话题。我刚开始接触高通平台时,就被YUV的各种格式绕晕过——明明都是YUV,为什么有的能直接显示,有的却花屏?说白了,YUV不是一种格式,而是一族格式。理解它的底层逻辑,是后面做边缘增强、降噪、色彩校正的根基。

1.1 为什么ISP要用YUV,而不是RGB?

传感器出来的原始数据是Bayer格式,经过ISP前端的黑电平校正、去马赛克后,会先转到RGB域做白平衡和颜色校正。但到了YUV域,才是真正的“重头戏”。

原因很简单:人眼对亮度敏感,对色度不敏感。YUV把亮度和色度分开,我们就可以对Y通道做精细的边缘增强,对UV通道做压缩或降采样,而人眼几乎察觉不到画质损失。我在项目中遇到过一位刚入行的同事,非要在RGB域做锐化,结果彩色边缘出现大量振铃——这就是没理解YUV分离的好处。

核心结论:YUV域处理的核心优势在于——亮度与色度分离,允许对两者采用不同的处理策略,在保持视觉质量的同时大幅降低计算量和带宽。

3. YUV与RGB的转换原理

YUV和RGB的转换,本质上是一个线性变换。不同标准(BT.601、BT.709、BT.2020)的系数略有差异,但高通平台默认使用BT.601(标清)或BT.709(高清)。

我个人的习惯是:先把公式记死,再理解物理意义。下面给出BT.601标准的转换矩阵:

RGB → YUV(正向转换)

Y  =  0.299 * R + 0.587 * G + 0.114 * B
U  = -0.147 * R - 0.289 * G + 0.436 * B + 128
V  =  0.615 * R - 0.515 * G - 0.100 * B + 128

YUV → RGB(反向转换)

R  =  Y + 1.140 * (V - 128)
G  =  Y - 0.394 * (U - 128) - 0.581 * (V - 128)
B  =  Y + 2.032 * (U - 128)
实战小技巧:高通平台的硬件加速器通常使用定点数实现这些系数。我在调试时发现,直接用浮点计算会引入精度误差,导致颜色偏绿。建议使用Q8格式(左移8位)的定点系数,精度足够且速度快。

你可能会问:为什么U和V要加128?因为YUV数据通常用8位无符号数存储,Y的范围是[16, 235],UV的范围是[16, 240]。加128是为了把零值映射到中间值,避免负值无法存储。

1.3 YUV采样格式:4:4:4、4:2:2、4:2:0

这是YUV域最容易被搞混的地方。我见过不少工程师,代码写对了,但数据排布理解错了,结果图像全是马赛克。

采样格式的核心思想是:对色度通道进行降采样。人眼对色度的空间分辨率远低于亮度,所以我们可以“偷懒”——少存一些UV数据。

采样格式 每4个像素的Y样本数 每4个像素的U/V样本数 数据压缩比 典型应用
4:4:4 4 4(每个像素都有完整UV) 无压缩 专业视频编辑、屏幕截图
4:2:2 4 2(每2个像素共享一组UV) 33% 广播级视频、HDMI传输
4:2:0 4 1(每4个像素共享一组UV) 50% 消费级视频、JPEG、H.264
避坑指南:我曾经在调试高通平台的NV12格式时,把UV平面的stride算错了。4:2:0的UV平面宽度是Y平面的一半,但高通某些平台要求UV平面的stride对齐到16字节。如果直接按宽度/2计算,内存访问会越界,导致图像底部出现绿色条纹。这个bug我调了整整两天。

1.4 高通平台YUV数据排布

高通平台最常用的YUV格式是NV12和NV21。两者都是4:2:0采样,区别在于UV的排列顺序。

NV12(YUV 4:2:0 平面+半平面)

数据排布如下:

  • Y平面:连续存储所有像素的Y值,大小为 width × height
  • UV半平面:交错存储U和V值,大小为 width × height / 2

具体到内存布局:

Y[0] Y[1] Y[2] Y[3] ... Y[W*H-1]
U[0] V[0] U[1] V[1] ... U[W/2*H/2-1] V[W/2*H/2-1]

NV21(与NV12相反)

UV半平面中,V在前,U在后:

Y[0] Y[1] Y[2] Y[3] ... Y[W*H-1]
V[0] U[0] V[1] U[1] ... V[W/2*H/2-1] U[W/2*H/2-1]
高通平台默认格式:大部分高通ISP硬件输出的是NV12格式。但Android Camera HAL层通常要求NV21(因为JPEG编码器需要)。所以中间会有一个格式转换步骤——这个转换如果做得不好,会引入额外的延迟和画质损失。

1.5 知识体系总览

下面这张图总结了本章的核心知识点,我建议你把它存下来,后面每章都会用到这些基础概念。

YUV色彩空间知识体系 YUV色彩空间 RGB ↔ YUV 转换 YUV采样格式 高通平台数据排布 BT.601标准 BT.709标准 4:4:4 4:2:2 4:2:0 NV12 NV21 核心:亮度与色度分离 → 不同采样策略 → 带宽与画质的平衡 高通平台默认NV12,Android HAL层要求NV21,需注意格式转换

1.6 实战中的注意事项

最后,分享几个我在高通平台上踩过的坑:

  1. Stride对齐:Y平面的stride不一定等于width。高通平台要求stride对齐到32或64字节。读取数据时一定要用stride,而不是width。
  2. UV平面偏移:NV12的UV平面起始地址 = Y平面地址 + stride × height。但有些平台会在Y平面末尾填充额外的行(用于硬件加速),导致UV平面偏移。我建议用驱动返回的offset,不要自己算。
  3. 颜色空间转换:高通ISP输出的YUV是BT.601范围(Y:16-235, UV:16-240),但显示驱动可能期望全范围(0-255)。不做范围映射的话,图像会发灰或过曝。
调试小工具:我习惯用Python的numpy直接dump YUV文件到内存,然后用matplotlib显示Y平面和UV平面。如果Y平面正常但UV平面出现棋盘格,基本可以断定是采样格式或排布顺序搞错了。

嗯,YUV基础就讲到这里。这些概念看起来简单,但实际项目中80%的YUV相关问题都出在这些“基础”上。后面讲边缘增强时,你会频繁用到4:2:0的采样结构——因为边缘增强只在Y通道做,UV通道要保持原样,否则会出现彩色伪影。