RGB与YUV像素格式详解
摄像头预览发绿、H.264 编码前不知道用 NV12 还是 I420、RGB 转 YUV 后整体偏色------多数不是「公式背错」,而是 像素格式名没对齐:采样方式、平面排布、色域矩阵、量化范围、行跨距里少对齐一项就会踩坑。
显示器与图像处理常用 RGB ;摄像头采集、视频编解码、硬件加速缓冲里主流是 YCbCr(口语常叫 YUV)。一条链路可以记成:
text
场景选择 RGB 或 YCbCr → 色度采样(4:2:0 等) → 平面/半平面/打包存储(I420/NV12...) → 字节写入内存/文件
下文按这条链展开:先 RGB 与 YCbCr 各自干什么 ,再讲 4:2:0 采样与 I420/NV12 排布 ,然后是 RGB↔YCbCr 转换、色域与范围、stride ,最后给 FFmpeg 像素名与 ffplay 验证。
目录
一、RGB 与 YUV 为什么共存
- [1. 为什么视频链路偏爱 YCbCr](#1. 为什么视频链路偏爱 YCbCr)
- [2. RGB:显示侧的像素打包](#2. RGB:显示侧的像素打包)
- [3. YCbCr:亮度与色度分离](#3. YCbCr:亮度与色度分离)
二、YUV 采样与存储
- [4. 色度采样:4:4:4 到 4:2:0](#4. 色度采样:4:4:4 到 4:2:0)
- [5. 存储方式:Planar、Semi-Planar、Packed](#5. 存储方式:Planar、Semi-Planar、Packed)
- [6. 帧大小怎么算](#6. 帧大小怎么算)
- [7. 4:2:0 采样与 I420、NV12 内存布局](#7. 4:2:0 采样与 I420、NV12 内存布局)
- [8. 名字别混:YUV420、I420、NV12](#8. 名字别混:YUV420、I420、NV12)
三、RGB↔YCbCr 转换
- [9. RGB 与 YCbCr 的相互转换](#9. RGB 与 YCbCr 的相互转换)
- [10. 色域矩阵:BT.601、BT.709、BT.2020](#10. 色域矩阵:BT.601、BT.709、BT.2020)
- [11. Limited Range 与 Full Range](#11. Limited Range 与 Full Range)
四、工程实践
- [12. Stride(行跨距)](#12. Stride(行跨距))
- [13. FFmpeg 像素格式与 ffplay 验证](#13. FFmpeg 像素格式与 ffplay 验证)
- [14. 场景与格式速查](#14. 场景与格式速查)
- [15. 工程里常见现象](#15. 工程里常见现象)
- [16. 延伸阅读](#16. 延伸阅读)
1. 为什么视频链路偏爱 YCbCr
| 维度 | RGB | YCbCr(常说 YUV) |
|---|---|---|
| 信息组织 | R、G、B 捆绑,亮度与颜色难分离 | Y 表亮度,Cb/Cr(或 U/V)表色度 |
| 数据量 | 每像素常 3~4 字节,难再减 | 可对色度 降采样 ,4:2:0 约 1.5 字节/像素 |
| 典型场景 | 屏幕、OpenGL 纹理、未压缩 BMP | 摄像头、MediaCodec、x264/x265、多数 raw .yuv |
人眼对 亮度变化 更敏感、对 色度细节 较弱,因此视频在保持观感的前提下 少传 U/V ,带宽和存储都更划算。RGB 仍用于 显示最后一跳(GPU 合成、窗口上屏),中间处理与编码几乎总是 YCbCr 家族。
摄像头 / 解码输出
NV12 / I420
H.264 / H.265
解码
转 RGB
显示 / OpenCV 绘图
更上层的 编码、封装、播放流程 见 从播放流程到技术演进-音视频编解码与封装格式详解 ;Android 侧硬解缓冲 见 Android_MediaCodec架构与实现解析。
2. RGB:显示侧的像素打包
RGB 用红、绿、蓝三通道比例表示颜色,亮度和色度绑在一起,适合做显示,不适合再做大幅度色度压缩。
2.1 常见 RGB 存储
| 格式 | 位宽 | 布局(概念) | 说明 |
|---|---|---|---|
| RGB565 | 16 bit | R5 G6 B5 | 嵌入式屏、游戏纹理常见 |
| RGB555 | 16 bit | 1 保留 + R5 G5 B5 | 较少见 |
| RGB24 | 24 bit | R8 G8 B8 | 无 Alpha;width×height×3 字节 |
| ARGB8888 / RGBA8888 | 32 bit | A8 R8 G8 B8 | 带透明通道 |
| ARGB4444 | 16 bit | A4 R4 G4 B4 | 省内存 UI |
2.2 字节序:RGBA 与 BGRA
小端 CPU 上,源码里的 0xAARRGGBB 写入内存后,低地址往往先出现 B、G、R、A:
text
uint32_t pixel = 0xFF112233; // 常写成 ARGB
内存低地址 → 33 22 11 FF // 实际字节顺序多为 BGRA
| 生态 | 常见顺序 |
|---|---|
| Windows GDI、部分 OpenCV 默认 | BGR / BGRA |
| OpenGL、PNG、Web Canvas | RGB / RGBA |
对接 API 时以 文档与实测 为准,不要只看结构体字段名。
3. YCbCr:亮度与色度分离
数字视频标准里写的是 YCbCr (ITU-R BT.601 / BT.709 等),YUV 来自模拟电视术语,工程口语混用,下文在涉及矩阵与范围时写 YCbCr。
| 分量 | 含义 |
|---|---|
| Y | 亮度(Luma),可看作「灰度强弱」 |
| Cb / Cr | 色度(Chroma);也常记 U、V 指代两个色差方向 |
亮度与色度分开后,可以:
- 只对 Y 做锐化、降噪;
- 对 Cb/Cr 做 4:2:0 等降采样,在几乎不损观感的前提下 减半以上色度数据。
4. 色度采样:4:4:4 到 4:2:0
「4:2:0 」描述的是 色度在水平、垂直方向相对亮度的采样密度,不是文件里字节怎么排(那是下一节的事)。
| 采样 | 含义(直观) | 约字节/像素 |
|---|---|---|
| 4:4:4 | 每个像素都有完整 Y、U、V | 3 |
| 4:2:2 | 水平方向色度减半 | 2 |
| 4:1:1 | 水平每 4 像素共用一组 UV | 1.5(较少见) |
| 4:2:0 | 水平、垂直各减半 → 2×2 块共用一组 UV | 1.5 |
4:2:0 是 H.264/H.265、摄像头、软编 里最常遇到的采样;4:2:2 见于部分采集卡、YUY2 等 packed 格式。
4.1 4×4 块上的 4:2:0(示意)
text
像素网格(每格一个亮度采样点):
┌───┬───┬───┬───┐
│ │ │ │ │ 每个格子都有 Y(共 16 个 Y)
├───┼───┼───┼───┤
│ │ │ │ │
├───┼───┼───┼───┤
│ │ │ │ │
├───┼───┼───┼───┤
│ │ │ │ │
└───┴───┴───┴───┘
色度 U/V:每 2×2 块只保留 1 组 (U,V) → 共 4 组 UV
块0 覆盖左上 2×2,块1 右上,块2 左下,块3 右下
UV 平面 2×2 = 4 组
每 2×2 共享 1 组 U,V
Y 平面 4×4 = 16 采样
每像素 1 个 Y
5. 存储方式:Planar、Semi-Planar、Packed
同一套 4:2:0 采样,字节在内存里还有三种常见排法:
| 类型 | 排布方式 | 典型名字 |
|---|---|---|
| Planar(平面) | 先整块 Y,再整块 U,再整块 V | I420 (YU12)、YV12 |
| Semi-Planar(半平面) | 先整块 Y,再 UV 交错 | NV12 (UVUV...)、NV21(VUVU...) |
| Packed(打包) | 像素级交错,如 Y0 U0 Y1 V0... | YUY2(4:2:2 packed) |
| 平台/组件 | 常见格式 |
|---|---|
| Android Camera / MediaCodec | NV12 、NV21 |
| FFmpeg 软解默认 planar | YUV420P(I420) |
| 部分 Windows 采集 | YUY2 |
完整像素格式名 = 采样 + 存储 。例如 I420 = 4:2:0 采样 + Planar ;NV12 = 4:2:0 + Semi-Planar。
6. 帧大小怎么算
设宽 W、高 H(像素):
| 格式 | 大小(字节) |
|---|---|
| RGB24 | W × H × 3 |
| RGBA8888 | W × H × 4 |
| YUV 4:2:0(任意 planar/sp,采样相同) | W × H × 3 / 2 或 W × H × 1.5 |
例:1280×720
- RGB24:
1280 × 720 × 3≈ 2.64 MB - YUV420:
1280 × 720 × 1.5≈ 1.38 MB
未编码的 raw 帧按上式估算;H.264 码流 还要再乘编码压缩比,与 raw 未压缩像素布局不是同一层概念。
7. 4:2:0 采样与 I420、NV12 内存布局
在 4×4 示例上,设 Y 为 Y00...Y33,UV 为 U0 V0 ... U3 V3。
7.1 I420(Planar,FFmpeg 称 yuv420p)
text
[ Y00 Y01 Y02 Y03 Y10 ... Y33 ] ← 16 字节 Y
[ U0 U1 U2 U3 ] ← 4 字节 U
[ V0 V1 V2 V3 ] ← 4 字节 V
7.2 NV12(Semi-Planar)
text
[ Y00 ... Y33 ]
[ U0 V0 U1 V1 U2 V2 U3 V3 ]
7.3 YV12
与 I420 相同采样,但 V 平面在 U 前面 (Y → V → U)。对接解码器、硬编输入时要 逐格式区分,不能只看「都是 420」。
8. 名字别混:YUV420、I420、NV12
| 说法 | 实际指什么 |
|---|---|
| YUV420 / 4:2:0 | 只说明 色度采样比例 |
| I420 / YU12 | 4:2:0 + Planar(Y、U、V 分平面) |
| NV12 / NV21 | 4:2:0 + Semi-Planar(UV 或 VU 交错) |
| YUY2 | 多为 4:2:2 Packed,不是 4:2:0 |
排查日志时,应问:采样是 420 还是 422?存储是 I420 还是 NV12? 两项都对了,再谈转换矩阵。
域选型(实践) :采集 → 解码 → 滤镜/编码尽量全程留在 YUV/YCbCr (NV12、I420),少做 RGB↔YUV 往返;只在 上屏、OpenCV 绘图、UI 合成 等必须走显示管线时再转 RGB。每多一次转换,就多一次矩阵、range、stride 对齐风险。
9. RGB 与 YCbCr 的相互转换
显示前常要把解码出的 YUV420 转成 RGB24 ;自绘 OSD 或算法在 RGB 域做完,再喂给编码器时要做反向转换。矩阵与偏移必须和 整条链路约定一致(见 §10、§11)。
下面给出 8 bit、BT.601、Limited Range(电视范围) 的整数实现,与许多教材、入门博文一致;HD 内容 更常用 BT.709 系数(结构相同,系数不同,需查 ITU-R 表)。
9.1 RGB → YCbCr(Limited,BT.601)
cpp
void RGBtoYCbCr601Limited(uint8_t r, uint8_t g, uint8_t b,
uint8_t* y, uint8_t* cb, uint8_t* cr)
{
*y = static_cast<uint8_t>((( 66 * r + 129 * g + 25 * b + 128) >> 8) + 16);
*cb = static_cast<uint8_t>(((-38 * r - 74 * g + 112 * b + 128) >> 8) + 128);
*cr = static_cast<uint8_t>(((112 * r - 94 * g - 18 * b + 128) >> 8) + 128);
}
9.2 YCbCr → RGB(Limited,BT.601)
cpp
void YCbCr601LimitedToRGB(uint8_t y, uint8_t cb, uint8_t cr,
uint8_t* r, uint8_t* g, uint8_t* b)
{
int C = y - 16;
int D = cb - 128;
int E = cr - 128;
int R = (298 * C + 409 * E + 128) >> 8;
int G = (298 * C - 100 * D - 208 * E + 128) >> 8;
int B = (298 * C + 516 * D + 128) >> 8;
auto clip = [](int v) -> uint8_t {
if (v < 0) return 0;
if (v > 255) return 255;
return static_cast<uint8_t>(v);
};
*r = clip(R);
*g = clip(G);
*b = clip(B);
}
JPEG 图像常用 Full Range(0~255) 的 YCbCr,偏移与系数不同;把 JPEG 的 YCbCr 按 Limited 公式转 RGB,会 发灰或对比度异常。
10. 色域矩阵:BT.601、BT.709、BT.2020
RGB → YCbCr 的系数矩阵随 标准 变化:
| 标准 | 典型用途 | 场景举例 |
|---|---|---|
| BT.601 | SD | 老标清、部分摄像头默认 |
| BT.709 | HD | 720p/1080p 视频 |
| BT.2020 | UHD / HDR | 4K/HDR;色域更大,与 HDR_Vivid技术介绍 等专题相关 |
编码器 VUI 里可声明矩阵与范围;播放器按声明还原。若 用 601 矩阵编码、按 709 矩阵显示 (或相反),画面会 整体偏绿或偏红,且难以用简单亮度调节修回来。
11. Limited Range 与 Full Range
| 范围 | Y 常见区间 | Cb/Cr 常见区间 | 常见场景 |
|---|---|---|---|
| Limited(TV) | 16~235 | 16~240 | 视频编解码、摄像头默认 |
| Full(PC) | 0~255 | 0~255 | JPEG、部分截图/游戏纹理 |
RGB 帧缓冲多为 Full ;视频 YCbCr 多为 Limited 。转换时 矩阵和范围要成对 ;x264/x265 等可用参数指定 input-range / range(以具体版本文档为准)。
12. Stride(行跨距)
width 是有效像素宽;stride (FFmpeg 里常叫 linesize)是 相邻两行起始地址的字节差 ,常 ≥ 有效行宽。
12.1 为何存在 stride
摄像头、GPU、DMA 常要求 每行 16/32/64 字节对齐,于是:
text
stride_y >= width
stride_uv >= width / 2 (4:2:0 时)
只按 width 连续读 width×height 字节,会 把每行尾部 padding 当成下一行开头 ,出现 画面倾斜、绿条、错位。
12.2 按行拷贝(I420,伪代码)
cpp
// 拷贝 Y 平面
for (int row = 0; row < height; ++row) {
memcpy(dst_y + row * dst_stride_y,
src_y + row * src_stride_y,
width);
}
// U/V 平面高度为 height/2,宽度 width/2,同理按 stride_uv 逐行拷
从 MediaCodec Image / FFmpeg AVFrame 取数据时,永远用 linesize[i] 做行进,不要假设 linesize == width。
13. FFmpeg 像素格式与 ffplay 验证
13.1 常用 AVPixelFormat
| FFmpeg 名称 | 含义 |
|---|---|
AV_PIX_FMT_RGB24 |
packed RGB |
AV_PIX_FMT_BGRA |
packed BGRA |
AV_PIX_FMT_YUV420P |
I420 planar |
AV_PIX_FMT_YUVJ420P |
I420 + Full Range(旧接口,新项目注意弃用路径) |
AV_PIX_FMT_NV12 |
Y + 交错 UV |
AV_PIX_FMT_NV21 |
Y + 交错 VU |
软编滤镜、x264 输入多为 yuv420p ;Android 硬解表面多为 NV12。
13.2 用 ffplay 看 raw 文件
生成或截取一帧 raw 后,可直接预览(尺寸与格式必须一致):
bash
# I420,1280x720
ffplay -f rawvideo -video_size 1280x720 -pixel_format yuv420p frame.yuv
# RGB24
ffplay -f rawvideo -video_size 1280x720 -pixel_format rgb24 frame.rgb
画面正常说明 采样、存储、宽高 声明正确;异常条纹优先查 格式名是否写错、stride 是否忽略。
14. 场景与格式速查
| 场景 | 常见格式 | 备注 |
|---|---|---|
| 手机摄像头 / MediaCodec | NV12 、NV21 | 以机型与 API 文档为准 |
| x264/x265、FFmpeg 软编滤镜 | I420 (yuv420p) |
Planar 4:2:0 |
| FFmpeg 硬解 / 部分 GPU 路径 | NV12 | 与 I420 采样相同,存储不同 |
| Windows 部分采集卡 | YUY2 | 多为 4:2:2 Packed |
| OpenGL / 窗口上屏 | RGBA 、BGRA | 注意字节序 |
| JPEG / 截图算法 | Full Range YCbCr | 勿按视频 Limited 公式硬转 |
| raw 文件自测 | yuv420p / rgb24 + ffplay |
宽高、格式名必须一致 |
硬编输入若要求 NV12,而手里是 I420,应做 格式转换(或让解码/采集直接输出目标格式),不要只改枚举名。
15. 工程里常见现象
| 现象 | 优先排查 |
|---|---|
| 整体偏绿/偏红 | BT.601 vs BT.709 矩阵;Limited vs Full 混用 |
| 右侧斜纹、周期性色块 | stride 按 width 算,未用 linesize |
| NV12 当 I420 解 | U/V 平面顺序或交错方式错误 |
| RGB 正常、送编码器后色怪 | 送入的是 RGB 却声明为 YUV,或矩阵不对 |
| 只有部分机型异常 | 该机 Camera 输出 NV21 而非 NV12 |
16. 延伸阅读
- 从播放流程到技术演进-音视频编解码与封装格式详解 --- 编解码、封装与播放链路。
- Android_MediaCodec架构与实现解析 --- 硬解缓冲与 Surface 格式。
- ITU-R BT.601 / BT.709 / BT.2020 与 FFmpeg pixel formats 官方说明。
矩阵系数、FFmpeg 枚举名与 range 标志以所用 FFmpeg / 硬件 SDK 版本 文档为准;升级库后应回归 ffplay + 单帧 raw 做一次颜色与布局核对。
一句话 :搞清 RGB/YUV,本质是搞清 谁在用、怎么采、怎么存、怎么转、行跨距有没有对齐。