色彩工程深挖 —— BT.709 / BT.2020 / HDR10 / Dolby Vision
同一段视频在不同手机上颜色不一样、Netflix 4K HDR 看着特别亮、抖音红色"特别红"——这些都跟"色彩工程"有关。这一篇把色彩空间、色域、HDR、Tone Mapping 的核心概念讲明白。
本文速览
| 章节 | 阅读重点 |
|---|---|
| 0. 一个简单问题 | 定位常见故障和规避方式 |
| 1. 色彩空间是什么 | 把握本节核心概念和使用场景 |
| 2. 主流色彩空间对比 | 按场景做技术取舍 |
| 3. Gamma 与 Transfer Function | 把握本节核心概念和使用场景 |
| 4. SDR vs HDR | 把握本节核心概念和使用场景 |
| 5. Tone Mapping —— HDR 到 SDR 的桥 | 把握本节核心概念和使用场景 |
| 6. 10 bit vs 8 bit | 把握本节核心概念和使用场景 |
| 7. 完整色彩处理链 | 把握本节核心概念和使用场景 |
| 8. Android 上的色彩处理 | 把握本节核心概念和使用场景 |
| 9. 几个常见坑 | 定位常见故障和规避方式 |
| 后续章节 | 余下 2 节继续按“概念 → 示例 → 坑点 → 总结”展开 |
0. 一个简单问题
色彩工程要解决的就是:让"拍出来的颜色"在所有设备上"看起来一样"——听起来简单,做起来要管 4 件事的一致性,下一节展开。
1. 色彩空间是什么
色彩空间 = 一组规则,用(R, G, B)或(Y, U, V)三个数描述真实颜色。但光有三个数不够——必须告诉对方你用的是哪一组规则,否则数字就是空话。
| 序号 | 元素 | 含义 |
|---|---|---|
| ① | 三原色(Primaries) | 红绿蓝具体对应哪种波长(哪种红 / 绿 / 蓝) |
| ② | 白点(White Point) | 什么算"中性白"(D65 / D60 等) |
| ③ | Gamma(Transfer Function) | 数字怎么对应真实亮度(sRGB / PQ / HLG) |
| ④ | 矩阵(Color Matrix) | RGB ↔ YUV 怎么互转(BT.601 / BT.709 / BT.2020) |
这 4 件事任何一件不同,颜色就变了。
2. 主流色彩空间对比
| 色彩空间 | 用途 | 色域 | Gamma | 特点 |
|---|---|---|---|---|
| sRGB | Web / 普通显示器 ⭐ | 标准 | 2.2 | 1996 标准,Web 默认 |
| BT.709 | HD 视频(1080p)⭐ | 同 sRGB | BT.1886 | HDTV 标准 |
| DCI-P3 | 数字电影 | +25% | 2.6 | 影院 |
| Display P3 | iPhone / Mac | +25% | 2.2 | DCI-P3 + sRGB Gamma |
| BT.2020 | UHD / HDR ⭐⭐ | +80% | PQ / HLG | 4K 标准 |
| Rec.2100 | HDR 综合 | 同 BT.2020 | PQ + HLG | HDR 标准 |
2.1 三原色不同会怎样
| 色彩空间 | "红"对应的波长 |
|---|---|
| sRGB / BT.709 | 约614 nm |
| DCI-P3 | 中间(620 nm 左右) |
| BT.2020 | 约630 nm(更红,接近纯红激光) |
同样数字(255, 0, 0),在不同色彩空间下表示的"红"是不同程度的红——前者带橙调,后者饱和度更高。
2.2 用矩阵转换
播放器拿到 BT.709 视频但屏幕是 BT.2020 → 必须做色彩转换,否则颜色"偏冷"。BT.709 → BT.2020 的转换矩阵:
R_2020 = 0.6274·R_709 + 0.3293·G_709 + 0.0433·B_709 G_2020 = 0.0691·R_709 + 0.9195·G_709 + 0.0114·B_709 B_2020 = 0.0164·R_709 + 0.0880·G_709 + 0.8956·B_709每个像素都要做一次 3×3 矩阵乘法——所以这种转换通常放 GPU 做。
3. Gamma 与 Transfer Function
3.1 Gamma 是什么
参考 1.1 篇 §5。一句话总结:
存储的数字 ≠ 真实亮度,要经过一条曲线(transfer function)映射后才是真实亮度。原因是 8 bit 整数有限(256 级),用非线性映射给暗部更多分辨率,符合人眼感知。
3.2 标准 Gamma 曲线
| 标准 | 曲线 | 用途 |
|---|---|---|
| sRGB | 接近 2.2(分段) | Web |
| BT.709 / BT.1886 | 2.4 | HD 视频 |
| Gamma 2.2 | 严格幂函数 | 老 PC 显示器 |
| PQ(ST.2084) | 复杂曲线 | HDR10 ⭐ |
| HLG(Hybrid Log-Gamma) | 折中 | 直播 HDR |
3.3 PQ vs HLG(HDR 的两条路)
| 维度 | PQ(Perceptual Quantizer) | HLG(Hybrid Log-Gamma) |
|---|---|---|
| 亮度映射 | 绝对(固定 0–10000 nits) | 相对(跟显示器最大亮度相对) |
| SDR 兼容 | ❌ 不兼容(在 SDR 屏上看是黑的) | ✅ 兼容(自动降级显示) |
| 提出方 | Dolby(基于人眼感知) | BBC + NHK 联合 |
| 典型标准 | HDR10 / Dolby Vision | BBC iPlayer HDR / NHK BS4K |
| 典型场景 | 后期 / 流媒体 | 直播 HDR |
SDR 显示器最大约 100 nits、HDR 显示器约 1000–4000 nits,亮度范围扩大 10–40 倍。
3.4 实战:识别视频的 transfer
ffprobe-show_streamsvideo.mp4|grep-E"color_transfer|color_primaries|color_space"# 输出例:color_primaries=bt709color_trc=bt709color_space=bt709播放器拿到这些后,要应用对应的转换链。
4. SDR vs HDR
4.1 HDR 三大格式
| 格式 | 主推方 | 元数据 | 兼容性 |
|---|---|---|---|
| HDR10 | 开放标准 | 静态(整片一组) | ⭐⭐⭐⭐⭐ |
| HDR10+ | 三星 | 动态(逐场景) | ⭐⭐⭐ |
| Dolby Vision | Dolby | 动态 + 12 bit | ⭐⭐⭐⭐ |
HDR10 是底线,HDR10+ 和 Dolby Vision 是增强。
4.2 HDR10 的元数据
播放器拿这些信息做 tone mapping(亮度映射):
| 要点 | 说明 |
|---|---|
| SMPTE ST 2086(Mastering Display) | 显示器最大亮度(1000 / 4000 nits)、最小亮度、色域端点 |
| CTA-861.3(Content Light Level) | MaxCLL(最大内容亮度)、MaxFALL(最大平均亮度) |
4.3 Dolby Vision 的优势
| 序号 | 要点 |
|---|---|
| 1 | 12 bit色深(4096 级)vs HDR10 的 10 bit(1024 级) |
| 2 | 逐场景动态元数据(每个场景单独优化映射) |
| 3 | 比 HDR10 高一档 |
用户买电视看到 “Dolby Vision” 标志价格更高——就是这俩区别。
5. Tone Mapping —— HDR 到 SDR 的桥
5.1 问题
| 序号 | 要点 |
|---|---|
| 1 | HDR 视频亮度范围 0–4000 nits |
| 2 | SDR 显示器只能 0–100 nits |
| 3 | 直接砍掉 100 以上→ 高光全白(过曝) |
| 4 | 线性压缩→ 整体偏暗 |
需要"智能"映射,也就是tone mapping——保持中间调正常,只压缩高光区域。
5.2 常见 Tone Mapping 算法
| 算法 | 特点 |
|---|---|
| Reinhard | 简单 / 通用 |
| Hable | 电影感 / 影视常用 ⭐ |
| ACES | 电影业标准 |
| BT.2390 | 广播电视推荐 |
| Mobius | 移动端低开销 |
5.3 FFmpeg Tone Mapping
# HDR10 → SDR (FFmpeg + zscale)ffmpeg-ihdr.mp4-vfzscale=t=linear:npl=100,format=gbrpf32le,zscale=p=bt709,tonemap=hable,zscale=t=bt709:m=bt709:r=tv,format=yuv420p sdr.mp4参考第 5.3 篇命令行滤镜。
5.4 GPU Tone Mapping
// 简化版 Reinhard vec3 reinhard(vec3 color) { return color / (color + vec3(1.0)); } // 简化版 Hable (电影感) vec3 hable(vec3 color) { float A = 0.15, B = 0.50, C = 0.10, D = 0.20, E = 0.02, F = 0.30; return ((color * (A*color + C*B) + D*E) / (color * (A*color + B) + D*F)) - E/F; }短视频 / 直播 App 常在 fragment shader 里做 tone mapping,避免 CPU 跑完整 zscale 链路。
6. 10 bit vs 8 bit
| 位深 | 级数 | 用途 |
|---|---|---|
| 8 bit | 256 级 | SDR ⭐ |
| 10 bit | 1024 级 | HDR 必备 ⭐ |
| 12 bit | 4096 级 | Dolby Vision |
为什么 HDR 必须 10 bit
SDR 100 nits 范围, 8 bit 256 级 → 每级 0.4 nits HDR 4000 nits 范围, 8 bit 256 级 → 每级 16 nits → 暗部出现 banding (色带) HDR 4000 nits 范围, 10 bit 1024 级 → 每级 4 nits → 大致避免 banding实战识别
ffprobe-show_streamsvideo.mp4|greppix_fmt# 8 bit: yuv420p / yuv422p / yuv444p# 10 bit: yuv420p10le / yuv422p10le / yuv444p10le7. 完整色彩处理链
每一步都可能错,一致性是色彩工程的核心难题。下面 §9 列了 5 个最常见的踩坑。
8. Android 上的色彩处理
8.1 SurfaceView / TextureView
// 默认 BT.601 / BT.709// 配 HDR 显示需要:SurfaceControlsurface=...;SurfaceControl.Transactiontx=newSurfaceControl.Transaction();tx.setColorTransform(surface,1f,0f,0f,...);// 色彩矩阵tx.apply();8.2 MediaCodec HDR 输出
MediaFormatformat=...;format.setInteger(KEY_COLOR_STANDARD,MediaFormat.COLOR_STANDARD_BT2020);format.setInteger(KEY_COLOR_RANGE,MediaFormat.COLOR_RANGE_LIMITED);format.setInteger(KEY_COLOR_TRANSFER,MediaFormat.COLOR_TRANSFER_ST2084);COLOR_TRANSFER_ST2084= PQ。HLG 用COLOR_TRANSFER_HLG。
8.3 屏幕能力查询
valdisplay=(context.getSystemService(WINDOW_SERVICE)asWindowManager).defaultDisplayvalhdrCaps=display.hdrCapabilitiesif(hdrCaps.supportedHdrTypes.contains(HdrCapabilities.HDR_TYPE_HDR10)){// 屏幕支持 HDR10}8.4 Limited Range vs Full Range
| 类型 | Y 范围 | U/V 范围 | 默认场景 |
|---|---|---|---|
| Limited(TV)Range | [16, 235] | [16, 240] | 视频默认⭐ |
| Full(PC)Range | [0, 255] | [0, 255] | PC / 截图默认 |
搞错的后果:黑变灰、白被砍。⚠️ MediaFormat 必须设对:
format.setInteger(KEY_COLOR_RANGE,MediaFormat.COLOR_RANGE_LIMITED);// 视频9. 几个常见坑
9.1 BT.601 / BT.709 矩阵搞混
| 视频分辨率 | 应该用的矩阵 |
|---|---|
| SD 视频(480p) | BT.601 |
| HD 视频(1080p) | BT.709 |
| UHD 视频(4K) | BT.2020 |
矩阵错了 → 颜色偏(尤其红色)。
9.2 Limited Range 当 Full Range 解
视频是 Limited(16–235),解码器以为是 Full(0–255)→黑色变成"暗灰",白色变成"灰白"。最常见的现象:截图比视频颜色明显偏淡。
9.3 HDR 视频显示在 SDR 屏 → 暗
直接显示 PQ 编码视频在 SDR 屏:高光全白、暗部全黑。正确做法:tone mapping(参考 §5)。
9.4 sRGB Gamma 跟 2.2 不完全等价
sRGB 在低值用线性段(避免 0 处的奇点),2.2 在 0 处是 0——两者差几个百分点。实战通常忽略(差异肉眼难辨),但要做色彩管理时不能混用。
9.5 截图色彩偏
截图通常是 Full Range RGBA,原视频是 Limited Range YUV——转换不对 → 截图比视频显示偏亮 / 偏白。✅ 截图前做正确的 YUV→RGB Limited→Full 转换。
10. 实战速查
10.1 识别视频的色彩参数
ffprobe-show_streamsvideo.mp4|grep-E"pix_fmt|color"# 输出:pix_fmt=yuv420p10le ←10bitcolor_range=tv ← Limited Rangecolor_primaries=bt2020 ← 色域color_trc=smpte2084 ← PQ(HDR10!)color_space=bt2020nc ← 矩阵10.2 转换色彩空间
| 用途 | 命令 |
|---|---|
| BT.2020 → BT.709 (HDR → SDR) | ffmpeg -i hdr.mp4 -vf zscale=p=bt709:t=bt709:m=bt709,format=yuv420p sdr.mp4 |
| Limited → Full | ffmpeg -i in.mp4 -vf zscale=r=full out.mp4 |
10.3 检查 Android 设备 HDR 能力
adb shell dumpsys SurfaceFlinger --display-color输出大概:
Display modes: HDR types: HDR10, HLG, Dolby Vision Wide color gamut: yes11. 总结
色彩工程的"四件事一致性":
| 序号 | 要点 | 说明 |
|---|---|---|
| 1 | 三原色(色域) | 红绿蓝具体是哪种红绿蓝 |
| 2 | 白点 | 什么是中性白 |
| 3 | Gamma(transfer function) | 数字怎么对应真实亮度 |
| 4 | 矩阵 | RGB ↔ YUV 怎么互转 |
任何一项不一致 → 颜色不对。
主流标准对照:
| 等级 | 色域 | Gamma | 位深 | 典型 |
|---|---|---|---|---|
| SDR | BT.709 | 2.4 | 8 bit | 普通视频 ⭐ |
| HDR10 | BT.2020 | PQ | 10 bit | 流媒体 ⭐ |
| Dolby Vision | BT.2020 | PQ | 12 bit | 高端电视 |
| HLG HDR | BT.2020 | HLG | 10 bit | 直播 HDR |
工程实战要会的 4 个动作:
| 动作 | 工具 |
|---|---|
| 检查色彩参数 | ffprobe -show_streams |
| 转换色彩空间 | ffmpeg -vf zscale=... |
| Tone mapping | ffmpeg -vf zscale,tonemap=hable |
| HDR 显示 | MediaFormat.KEY_COLOR_TRANSFER |
金句:色彩工程是"被忽视的水下冰山"。普通工程师以为颜色就是 RGB 三个数,专业工程师知道这三个数没有色彩空间标识就什么都不是。HDR 时代色彩工程从"锦上添花"变成"必须懂"。
更多请关注微信公众号“音视频修炼之旅”