从像素格式到色彩空间,系统理解 RGB、YUV、NV12 与 YUV420P
前言:为什么像素格式和色彩空间总让人越学越乱
只要开始接触音视频、Camera、FFmpeg、OpenCV、编码器或者渲染链路,几乎很快就会碰到这几个词:
- RGB
- YUV
- NV12
- YUV420P
- 色彩空间
- 像素格式
很多人最开始都会把它们混成一团。
典型表现是:
- 把 RGB / YUV 当成“文件格式”
- 把 NV12 / YUV420P 当成“色彩空间”
- 知道它们都和图像有关,但不知道差别到底在哪
- 碰到 FFmpeg、OpenCV、编码器之间格式不匹配时,只能靠试
- 明明图能显示出来,但颜色不对、画面发绿、发紫、偏灰,又说不清是哪层出了问题
我觉得这里最容易乱的根本原因,不是你记性不好,而是很多资料一上来就把两个层次混着讲:
- 颜色怎么表达
- 数据怎么存放
前者更接近“色彩空间”或颜色表示方式,后者更接近“像素格式”或内存布局方式。
而工程里你真正会频繁碰到的问题,往往正是这两层交叉造成的。
所以这篇文章的目标,就是先把这两个层次拆开,再把它们重新串起来。
重点讲清下面这些问题:
- RGB 和 YUV 到底分别在表达什么
- 为什么视频系统更偏爱 YUV
- NV12 和 YUV420P 到底是什么关系
- 什么叫 planar,什么叫 packed
- 为什么 FFmpeg、OpenCV、Camera、编码器总在格式上“打架”
- 工程里该怎么判断当前数据到底是什么
而且这篇我会尽量按工程思维来写,多放结构图、内存图和代码,不只停在概念层。
一、先给结论:RGB / YUV 解决的是“颜色表达”,NV12 / YUV420P 解决的是“数据存放”
如果你现在只想先记住一句最重要的话,那就是:
RGB / YUV 更像是在讲颜色信息怎么表达,而 NV12 / YUV420P 更像是在讲这些数据在内存里怎么排。
这句话可以直接帮你把很多混乱先切开。
也就是说:
- RGB / YUV,更偏“颜色模型 / 颜色表示方式”
- NV12 / YUV420P,更偏“像素格式 / 采样方式 / 内存布局”
当然工程里它们不会完全彼此独立,因为一旦你选择了某种颜色表示方式,后面通常会对应某种具体的像素格式。
但先把这两层区分开,是理解后面全部内容的关键。
二、先看全局:一张图把几个概念的位置放清楚
先上总图。
flowchart TD
A[图像颜色信息] --> B[颜色表示方式]
B --> C1[RGB]
B --> C2[YUV / YCbCr]
C2 --> D[采样方式]
D --> D1[4:4:4]
D --> D2[4:2:2]
D --> D3[4:2:0]
D3 --> E[具体像素格式]
E --> E1[YUV420P]
E --> E2[NV12]
E --> E3[NV21]
C1 --> F[RGB像素格式]
F --> F1[RGB24]
F --> F2[BGR24]
F --> F3[RGBA]
这张图最想表达的是:
- 先有颜色信息要表达
- 然后有 RGB / YUV 这类表达方式
- 如果走 YUV 路线,还会继续涉及采样方式
- 采样方式再进一步落成具体像素格式
所以你以后再看到一个格式时,可以先问自己:
它是在讲颜色怎么表示,还是在讲数据怎么存?
这一步就能过滤掉很多混乱。
三、RGB 到底是什么,它为什么直观但不一定适合视频系统
3.1 RGB 的本质
RGB 是最直观的一种颜色表示方式。
它的核心思想非常简单:
用红、绿、蓝三个通道的强度组合,表达一个像素的颜色。
比如一个像素可以表示成:
1 | |
从人类直觉上看,这很好理解。
3.2 RGB 为什么适合显示,却不总适合视频编码
因为 RGB 的优点是:
- 直观
- 适合显示设备理解
- 很多图形接口和图像处理库天然支持
但它的问题也很明显:
- 数据量大
- 不方便利用视觉冗余
- 对视频压缩不够友好
视频系统往往更关心:
- 带宽
- 存储
- 压缩效率
- 传输成本
所以虽然显示端很喜欢 RGB,但编码和传输链路通常更偏爱 YUV。
四、YUV 到底是什么,它和 RGB 的根本差别在哪
4.1 YUV 的核心思想
YUV 的直觉理解可以先抓住一句话:
它把亮度信息和色度信息拆开了。
通常可以粗略理解成:
- Y:亮度信息
- U / V:色度信息
在更严格的数字视频语境里,很多时候更准确的说法其实是 YCbCr,但工程沟通里大家经常仍然习惯说 YUV。为了不把文章讲得太绕,这里我们先沿用工程上更常见的叫法。
4.2 为什么把亮度和色度拆开很重要
因为人眼对亮度变化更敏感,对色彩细节没那么敏感。
这意味着:
- 亮度信息要尽量保住
- 色度信息可以适当降低采样精度
一旦接受这个前提,系统就可以大幅减少数据量,而主观视觉效果又不至于差太多。
这也是 YUV 在视频系统里特别重要的根本原因。
4.3 RGB 和 YUV 的角色差异
你可以先这样粗暴记:
- RGB 更像显示友好格式
- YUV 更像压缩友好格式
这不是绝对规则,但对大多数视频工程场景来说非常有帮助。
五、一张图看懂 RGB 和 YUV 的角色差别
flowchart LR
A[原始图像内容] --> B1[RGB 路线]
A --> B2[YUV 路线]
B1 --> C1[更直观]
B1 --> C2[更适合显示/图形系统]
B1 --> C3[数据量相对大]
B2 --> D1[亮度与色度分离]
B2 --> D2[更适合压缩]
B2 --> D3[更适合视频编码链路]
这张图虽然简单,但可以帮你建立一个很稳的直觉:
RGB 更偏显示侧,YUV 更偏视频侧。
后面你看 Camera、编码器、FFmpeg、播放器时,很多格式选择就会更容易理解。
六、为什么视频系统更偏爱 YUV,而不是 RGB
这个问题很重要,因为它关系到后面所有具体格式的存在意义。
6.1 核心原因:YUV 更适合做色度降采样
YUV 最大的工程价值之一,就是它允许:
在不明显伤害主观视觉的情况下,减少色度数据量。
这就是后面你经常看到的:
- 4:4:4
- 4:2:2
- 4:2:0
这些采样方式的来源。
6.2 亮度保得更多,色度省一点
因为人眼更关心亮度细节,所以系统通常会尽量保留 Y,而对 U/V 做降采样。
这会带来两个直接好处:
- 数据量下降
- 压缩效率更高
6.3 工程上的连锁影响
一旦视频链路主要走 YUV 路线,后面很多模块都会围绕它来设计:
- Camera 输出
- 编码器输入
- FFmpeg frame 格式
- 硬件编解码器支持
- 显示前色彩转换
这也是为什么很多时候你在系统里看到的是 NV12、YUV420P,而不是 RGB24。
七、4:4:4、4:2:2、4:2:0 到底在表达什么
这一块是很多人最容易发晕的地方。
我们先不讲特别学院派的定义,先抓工程直觉。
7.1 它们本质上在讲什么
它们描述的是:
亮度和色度分别采样得有多密。
你可以把它理解成:
- Y 通常采得更完整
- U/V 视情况采得更稀疏
7.2 为什么会这样设计
因为色度对人眼没那么敏感,所以系统可以少存一点 U/V,换更低的数据量。
7.3 一张示意图先建立直觉
flowchart TD
A[4个像素块] --> B1[4:4:4]
A --> B2[4:2:2]
A --> B3[4:2:0]
B1 --> C1[Y完整 + U完整 + V完整]
B2 --> C2[Y完整 + U横向减半 + V横向减半]
B3 --> C3[Y完整 + U/V横纵都减采样]
这张图先不用追特别细的数学定义,你只要先记住:
从 4:4:4 到 4:2:0,色度信息是在逐步减少的。
而 4:2:0 正是视频系统里最常见的一种方案。
八、YUV420P 和 NV12 到底是什么关系
终于进入最容易在工程里出问题的地方了。
很多人知道它们都很常见,也知道它们都和 YUV420 有关,但说不清差别。
一句话先说结论:
YUV420P 和 NV12 都属于 4:2:0 路线,但它们主要差在内存布局。
也就是说:
- 它们都在表达类似的采样思想
- 但它们存数据的方式不一样
这就是“像素格式”层真正开始起作用的地方。
九、YUV420P 的内存布局到底长什么样
YUV420P 也常被理解成一种 planar 格式。
所谓 planar,就是:
不同分量分开放。
它的典型布局是:
- 先放一整块 Y 平面
- 再放一整块 U 平面
- 最后放一整块 V 平面
9.1 结构示意图
1 | |
9.2 更直观一点的二维理解
假设一张 4x4 图像:
1 | |
因为是 4:2:0,所以 U 和 V 的分辨率会比 Y 更低。
9.3 YUV420P 的特点
- 结构清楚
- 每个平面分开,处理时比较直观
- FFmpeg 里非常常见
- 很多软件处理链路更容易直接操作
十、NV12 的内存布局到底长什么样
NV12 也是 4:2:0,但它不是 Y、U、V 三个平面完全分开,而是:
- 先是一整块 Y 平面
- 然后是交错存放的 UV 平面
10.1 结构示意图
1 | |
10.2 二维理解
还是用 4x4 图像举例:
1 | |
注意这里不是单独的 U 平面和 V 平面,而是 UV 交错排在一起。
10.3 NV12 的特点
- 也是 4:2:0
- 数据量和 YUV420P 同量级
- 更适合很多硬件编解码和图像处理路径
- 在 Camera / 硬件视频链路里特别常见
十一、YUV420P 和 NV12 一张图看清楚差别
flowchart LR
A[YUV420 采样思想] --> B1[YUV420P]
A --> B2[NV12]
B1 --> C1[Y plane]
B1 --> C2[U plane]
B1 --> C3[V plane]
B2 --> D1[Y plane]
B2 --> D2[UV interleaved plane]
所以你以后再看到这两个格式时,最稳的理解应该是:
- 共同点:都属于 YUV420 路线
- 关键差别:内存布局不同
这就是工程里很多“格式兼容”问题的根源之一。
十二、planar 和 packed 到底怎么理解
这两个词在图像和视频处理中非常高频。
12.1 planar 是什么
planar 的意思是:
不同通道 / 分量分开放。
例如:
- Y 一整块
- U 一整块
- V 一整块
YUV420P 就是典型的 planar 风格。
12.2 packed 是什么
packed 的意思是:
同一个像素相关的数据尽量挨着放。
比如 RGB24 通常可以理解成:
1 | |
每个像素的三个分量放在一起。
12.3 NV12 算什么
NV12 介于两者之间,常被理解成一种半平面(semi-planar)结构:
- Y 单独一个 plane
- UV 交错作为另一个 plane
所以它既不是像 RGB24 那种完全 packed,也不是像 YUV420P 那种三平面完全拆开。
十三、为什么 FFmpeg、OpenCV、Camera、编码器经常在像素格式上“打架”
这部分非常工程。
因为你真正踩坑的地方,不在概念,而在模块对格式的要求经常不一致。
13.1 一个典型链路可能长这样
flowchart LR
A[Camera 输出 NV12] --> B[OpenCV 处理 BGR]
B --> C[FFmpeg 编码器要求 YUV420P]
C --> D[显示链路可能又要 RGB / RGBA]
你会发现,一条链路里不同模块喜欢的数据格式可能完全不同。
13.2 为什么会这样
因为每个模块的目标不一样:
- Camera / 硬件链路更看重采集效率和硬件兼容
- OpenCV 更偏图像处理与算法接口友好
- 编码器更偏压缩友好
- 显示链路更偏渲染友好
所以它们并不会天然统一成同一个格式。
13.3 这类问题的典型表现
- 颜色不对
- 画面偏绿 / 偏紫
- 编码器初始化失败
- 图能看但质量不对
- 性能突然很差,因为中间做了太多转换
所以工程里一定要有一个习惯:
每过一个模块,都明确当前数据格式是什么。
这句话看起来朴素,但非常有用。
十四、一段 FFmpeg 代码,怎么看 frame 的像素格式
先给一段非常实用的代码。
14.1 打印 AVFrame 的像素格式
1 | |
这段代码的意义不只是打印一个字符串,而是在提醒你:
frame 不只是“有图像”,它还有格式语义。
而这个格式语义,决定了你后面该怎么访问数据、怎么转换、能不能直接送编码器。
十五、一段 FFmpeg 代码,怎么做像素格式转换
很多时候你拿到的 frame 格式,不是编码器或后处理模块想要的格式,这时就得转。
FFmpeg 里最常见的是用 libswscale。
15.1 从一种格式转到另一种格式
1 | |
这段代码非常有代表性,因为它体现了工程里一个高频现实:
模块之间经常需要做格式桥接。
而格式桥接的成本,不只是代码复杂度,还包括:
- CPU 开销
- 内存带宽
- 延迟
- 额外 copy
所以格式转换并不是“无害小动作”,它会直接影响性能。
十六、一段 OpenCV 代码,怎么把 YUV 数据转成 BGR 处理
如果你做算法或图像处理,OpenCV 这层很常见。
16.1 NV12 转 BGR
1 | |
16.2 YUV420P 转 BGR
1 | |
这两段代码非常适合帮助建立一个现实认识:
同样看起来都是“YUV420”,OpenCV 转换入口也不一样,因为布局不一样。
这正好对应我们前面讲的:
- YUV420P 和 NV12 的采样思想接近
- 但内存布局不同
十七、一张时序图看懂一帧图像在系统里为什么总要来回转格式
这个现象在工程里特别常见。
sequenceDiagram
participant Cam as Camera
participant ISP as ISP/Preprocess
participant Algo as OpenCV/AI
participant Enc as Encoder
participant Disp as Display
Cam->>ISP: 输出 NV12 / Raw
ISP->>Algo: 转成 BGR/RGB 便于算法处理
Algo->>Enc: 处理后转回 YUV420/NV12
Enc->>Disp: 解码后可能再转 RGB/RGBA 显示
这张时序图说明了一个很重要的工程事实:
很多系统不是只转换一次格式,而是在不同目标之间不断桥接。
而你真正要做的,不只是“把它转成功”,更要思考:
- 有没有必要每次都转
- 哪一步可以少转一次
- 能不能走硬件路径
- 能不能减少 copy
这就是格式问题为什么会升级成性能问题。
十八、工程里最常见的几个坑
这一块你后面大概率会频繁遇到。
18.1 把颜色模型和像素格式混了
比如:
- 说 NV12 是一种色彩空间
- 说 YUV420P 是一种颜色模型
更稳的说法应该是:
- YUV 更偏颜色表示方式
- NV12 / YUV420P 更偏具体像素格式和内存布局
18.2 忽略 linesize / stride
很多人访问图像数据时直接按 width * bytes_per_pixel 走,结果图像错乱。
因为真实内存布局里,经常存在对齐和 stride。
所以工程里读 frame 数据时,一定不能忘:
data[]linesize[]
18.3 以为“YUV420 都一样”
不是。
哪怕都叫 4:2:0,布局不同就足以让整个处理逻辑完全不同。
18.4 模块一多,格式语义丢了
比如:
- Camera 给的是 NV12
- OpenCV 里变成 BGR
- 编码器要 YUV420P
- 最后显示要 RGB
如果中间没人维护清楚“当前格式是什么”,问题迟早会出现。
十九、面试里怎么讲这件事,才不会显得很散
如果面试官问你:
RGB、YUV、NV12、YUV420P 你怎么理解?
不建议一上来就背定义。
更好的讲法应该是:
19.1 先分层
RGB 和 YUV 更偏颜色信息怎么表达,NV12 和 YUV420P 则是基于 YUV 采样思想的具体像素格式和内存布局。
19.2 再讲核心差别
RGB 更直观、偏显示友好;YUV 把亮度和色度分开,更适合视频压缩和传输。NV12 和 YUV420P 都属于 4:2:0 路线,但 YUV420P 是 Y/U/V 三平面分开,NV12 是 Y 平面加 UV 交错平面。
19.3 最后讲工程意义
工程里这些格式差异会直接影响:
- Camera 输出
- OpenCV 处理
- FFmpeg 编码输入
- 显示渲染链路
- 性能和 copy 次数
如果你能按这个顺序讲,回答会明显更像工程师,而不是只会背几个术语。
总结:理解像素格式和色彩空间,本质上是在建立图像数据的“阅读能力”
回到这篇文章最开始的问题:
RGB、YUV、NV12、YUV420P 到底该怎么系统理解?
我觉得最稳的主线是这样:
- RGB / YUV 先看作颜色表达方式
- 4:4:4 / 4:2:2 / 4:2:0 再看作采样方式
- NV12 / YUV420P 再落到具体像素格式和内存布局
- 工程里再进一步关注:当前数据在哪一层、当前模块希望吃什么格式、格式转换代价有多大
如果把这条主线立住,后面很多原本容易混的东西都会突然顺起来:
- 为什么 Camera 喜欢 NV12
- 为什么编码器常见 YUV420 路线
- 为什么 OpenCV 喜欢 BGR
- 为什么显示前常常要转 RGB
- 为什么格式转换会成为性能瓶颈
所以我觉得,理解这部分内容的真正价值,不只是为了记住几个格式名字,而是为了建立一种能力:
看到一份图像数据时,知道它是怎么表达颜色的,又是怎么在内存里排布的。
这其实就是图像系统最基础的“阅读能力”。