最初,我只是想把一张 HDR RGB 图像尽快转成这次 AVIF 交付所用的 YUV420。输出是 10-bit、BT.2020 NCL、PQ,希望尽量保住颜色,也控制转换的计算开销。
当时的 baseline 很简单:每个 2×2 区域取左列两个像素的色度平均值,作为这个区域的共享 UV。大部分照片没有明显问题,直到我开始看灯牌。
文字边缘多了一圈不该有的颜色;有些亮边像被额外锐化过;原本看着平整的发光区域,出现了细碎的网格和颜色波动。后来,类似的问题还出现在被蓝色、青色灯光照射的复杂物体上。
我的问题也逐渐从“UV 应该怎么平均”,变成了:怎样让 10-bit PQ 的 RGB→YUV420 转换少制造一些原图没有的结构,又不把计算过程变得太重?
从一块灯牌开始
先看一个合成例子。下面的灯牌包含倾斜文字、小字和细线,颜色在 P3-D65 内,转换使用 BT.2020 容器。斜边会经过不同的采样相位,比对齐像素网格的横竖线更容易暴露问题。

注意 G 的内侧弧线和左边斜笔画,观察边缘颜色与亮暗条带。
点击按钮比较同一位置,也可以用键盘方向键切换。HDR 环境显示 PQ AVIF,其余环境显示固定曝光的 SDR 预览。
这里的“左列平均”有明确的采样位置:水平方向在左列,垂直方向在两行中间。重建也按这个位置做 bilinear 插值。四点平均的样本位于块中心,是另一个对照,不能只换平均公式而沿用旧的上采样相位。
为了单独观察转换,本文先模拟降采样和重建,再把结果存进 444 AVIF 供网页显示。这样,浏览器不会替实验再做一次未知的 420 上采样。图像都经过同一套显示处理,按钮之间没有单独调曝光或增强误差。
第一次定位:原本稳定的通道被带动了
我最早观察的是青绿色背景上的白字。G′、B′ 在边界两侧比较接近,主要变化在 R′。经过 420 往返,原本稳定的 G′、B′ 也开始波动,而且误差方向往往与 R′ 相反。
这个现象可以直接从矩阵看出来。本文用工程里常说的 YUV 指代数字 Y′CbCr;其中 Y′ 是非线性信号的 luma,不是以 nit 表示的物理亮度。
对 BT.2020 NCL,归一化后的关系是:
Y′ = 0.2627 R′ + 0.6780 G′ + 0.0593 B′
R′ = Y′ + 1.4746 Cr
G′ ≈ Y′ − 0.1646 Cb − 0.5714 Cr
B′ = Y′ + 1.8814 Cb
因此,即使逐像素 Y′ 完全保留,Cb、Cr 的误差仍会以不同方向进入三个通道。增加 Cr 会抬高 R′、压低 G′;增加 Cb 会抬高 B′、压低 G′。
在一个便于推导的局部模型里,假设原始 G′、B′ 不变,r 表示原始 R′ 相对于重建色度所代表的 R′ 的偏差,重建误差就是:
eR′ = −0.7373 r
eG′ = +0.2627 r
eB′ = +0.2627 r
这不是说真实场景总符合这个模型,而是它把“一个通道的变化跑进另外两个通道”解释得很清楚。
顺着这个观察,我做了 stableY:先模拟 420 重建,再调整每个像素的 Y′,优先减小原本稳定通道的误差。稳定性由局部方差估计,方差小的通道权重大。
e′ 是重建值减原值;这里的权重来自通道稳定性,误差在 PQ 码值域中计算。
它确实能减轻一些脏边。但继续看更多场景,问题并没有结束。
再往前一步:单通道只是入口
后来出问题的区域,不一定存在两个恒定通道。它们更常见的共同点是:颜色比较饱和,一个或多个分量很亮,内部又有细节、噪声或复杂边界。
灯牌、发光物体,以及被纯色光照射的纹理,都容易满足这些条件。“纯色”在这里指色相比较集中,不是每个像素完全相同。完全恒定的色块,经过保持常量的滤波,本身不会长出网格。
下面两组例子分别把内部波动和复杂结构单独拿出来看。
只有源图的 R′ 带有多尺度随机纹理,G′、B′ 恒定。波动强度随位置变化,用于压力测试,不是传感器噪声标定。
这一组的 R′ 波动有意放大到约 ±0.066 PQ,约合 ±67 个 10-bit 码阶。它包含细颗粒、局部斑驳和不均匀的强度包络,不是整齐的棋盘格。源颜色仍限制在 P3-D65 内。
这里也要区分“蓝色通道很亮”和“整个像素亮度很高”:B 分量接近 1000 nit,并不等于像素的 XYZ Y 接近 1000 nit。这组源图的线性亮度约为 85–122 nit。它适合检查通道误差怎样传递,灯牌里的白色部分才同时具有很高的整体亮度。
三个通道都随结构变化。观察斜向细纹和浅色反光交界处,哪些亮暗变化是原图没有的。
对这种结构,不能简单地把波动都当成噪声先抹掉。预平滑适用于确认没有真实细节的区域;边界、反光和细纹里的变化,本来就是图像的一部分。
stableY 的另一个限制也在这里显现。固定色度后,调整 Y′ 会给三个 PQ 通道加上同一个量:
一个标量不能同时消掉三个方向不同的通道误差。减小某个通道的误差,可能增加另一个通道的误差。
所以,“让稳定通道少波动”是一个有用的启发式,却还不足以定义 HDR 重建的好坏。
Y′ 没变,显示亮度为什么变了?
这是我后来认为最关键的一步。
设 BT.2020 权重为 k,色度改变产生的 RGB 码值误差为 e′。先忽略量化和裁切,保留 Y′ 意味着:
这个等式约束的是 PQ 码值的加权和,还没有经过显示端的 PQ EOTF。
显示亮度则要先把三个通道分别解码到线性光。这里讨论理想 PQ 解码后的亮度,不额外加入显示端的色调映射。用 E 表示 PQ EOTF,亮度为:
L = kR E(R′) + kG E(G′) + kB E(B′)
对小误差做一阶展开:
E′ 是 PQ EOTF 在当前通道码值处的导数。三个通道工作点不同,原来的抵消关系就可能失效。
这就把前面的现象连起来了:色度采样改变了 RGB 的组合,PQ 再把它变成新的亮度与颜色变化。沿边界分布时,它像亮边或暗边;随采样相位重复时,可能像网格;落在细纹上,又可能被看成锐化或细节。
但不能只凭外观认定原因。AV1 量化、滤波和 chroma siting 错配也可能制造类似结构。本文使用匹配相位的重建和无损 YUV 平面编码,把讨论集中在颜色转换、10-bit 量化与色度降采样上。
两条后续路线:SharpYUV 与 ITU
到了这里,优化有两个方向:一是让亮度和低分辨率颜色一起参与迭代,二是保留当前色度,把 Y′ 这个自由度用在更明确的目标上。
SharpYUV:根据重建结果反复修正
Google/WebM 的 SharpYUV 属于第一条路线。它建立内部的亮度分量和低分辨率 RGB 残差,模拟插值后的图像,再更新这些量。比起一次平均,它能根据重建结果继续调整。SharpYUV 实现
它支持 PQ,但默认接口使用 sRGB;比较 HDR 时,必须显式选 SMPTE ST 2084,并匹配 BT.2020 矩阵、位深和范围。它还假设 bilinear 上采样,实际解码器换一种重建方式,效果也会改变。SharpYUV 接口
还有一个实现细节:本文引用版本的内部灰度代理采用固定的近 BT.709 权重,输出矩阵设成 BT.2020 并不会自动把内部目标改成 BT.2020 物理亮度。因此,“支持 PQ 输入”与“所有内部度量都为 BT.2020/PQ 定制”需要分开看。灰度计算
SharpYUV 扩大了调整范围,也带来多轮处理和中间数据访问。它提供的是联合迭代的思路;下面的 ITU 方法则把主要工作集中在逐像素 Y′ 上。
ITU luma-control:把显示亮度拉回原位
ITU-T H.Sup15 的第 7.3 节讨论了闭环 luma adjustment。ISO/IEC TR 23008-14 则给出了同类 PQ HDR/WCG 转换流程的技术指导。本文把这条路线简称为 luma-control。H.Sup15、TR 23008-14
它先让色度经历降采样、量化、反量化和上采样。固定这份重建色度后,每个像素的 RGB 可以写成:
R′recon = Y′adjust + qR
G′recon = Y′adjust + qG
B′recon = Y′adjust + qB
q 是当前色度带来的固定偏移。亮度匹配方法求一个 Y′adjust,使:
固定色度,搜索一个 Y′。进入目标函数的是 EOTF 后的亮度,而不是稳定通道的 PQ 方差。
在允许的码值范围里,这是单调非递减的一维搜索。本文按 H.Sup15 的亮度匹配思路,在 full-range 10-bit 的 0–1023 中二分,最后选亮度误差更小的相邻码值。搜索时先将候选 RGB 裁到 PQ 的有效范围,再计算 EOTF;三组实验的最终重建均没有 RGB 越界。limited range 实现需要换成相应的有效码值范围。
它依然只有一个自由度,不能恢复丢失的全部颜色。区别在于:stableY 优先照顾局部稳定通道,luma-control 优先把显示亮度放回正确的位置。对于额外亮暗边和假锐化,这个目标更直接。
一阶近似:用 PQ 局部斜率减少搜索
完整搜索便于验证,但多次 PQ 计算并不便宜。闭式近似通过局部线性化减少计算。以线性亮度匹配为目标,可以写成:
C′ᵢ 是源图对应通道的 PQ 码值,e′ᵢ 是修正前重建值减源值。这里匹配线性亮度;若改为最小化线性 RGB 的加权平方误差,权重会出现 E′ 的平方,不能混用。
公式仍像加权平均,但权重有了颜色科学上的含义:这个通道的一点码值误差,在当前工作点会改变多少线性亮度。
这也是我把它视为 stableY 升级方向的原因。二者共享 Y′ 修正的自由度,目标和权重却不同,不能说它们严格等价。
计算上还可以用 LUT 近似 E 和 E′、融合数据遍历,减少重复转换;在误差大或裁切明显的位置,再做局部搜索。这些优化不改变核心思路:按 PQ 工作点判断误差,而不是把各通道的码值偏差等同看待。
回到图像:改善了什么,又留下什么?

灯牌边缘的亮暗误差在 ITU 搜索后明显减小,但上排仍能看到颜色边缘变软。亮度保住了,不等于色度细节也回来了。
这次合成实验得到的全图相对亮度误差如下。计算方式是逐像素 (Lrecon − Lsource) / Lsource 的均方根,参考是量化前的源图。
| 场景 | 10-bit 444 | 左列平均 | stableY | ITU 搜索 | 一阶近似 |
|---|---|---|---|---|---|
| 蓝色随机波动 | 0.40% | 0.66% | 4.56% | 0.27% | 0.59% |
| 蓝色灯牌 | 0.38% | 4.68% | 3.65% | 0.28% | 2.19% |
| 蓝光复杂结构 | 0.38% | 3.77% | 3.60% | 0.34% | 0.99% |
444 也有 10-bit 量化误差。ITU 搜索显式优化亮度,因此这一项甚至可能优于直接量化的 444;这不表示 420 的整体颜色保真超过 444。表格也提醒我,一阶近似在强边缘上会偏离搜索解,不能把二者的画质直接混为一谈。
这里比较的是本文的数值复现;SharpYUV 部分用于解释另一种优化思路,不参加这张误差表。对于本文关注的额外亮暗结构,ITU 亮度匹配提供了明确的控制目标:完整搜索作为参考,一阶近似以一定的亮度误差换取较少的计算。
PC 仿真需要多少时间?
在同一台 PC 上,我对灯牌场景做了两档尺寸的计时。每种方法预热 2 次,再测量 7 次,取中位数。计时包括 RGB→YUV420、10-bit 量化、匹配相位的 bilinear 重建,以及对应的 Y′ 修正;不包括场景生成、绘图、文件读写或 AVIF 编码。
| 方法 | 384×240 | 1536×960 |
|---|---|---|
| 左列平均与重建 | 11.81 ms | 229.34 ms |
| 左列平均 + stableY | 37.53 ms | 610.94 ms |
| 左列平均 + 一阶近似 | 51.14 ms | 699.25 ms |
| 左列平均 + ITU 搜索 | 290.06 ms | 3954.19 ms |
环境为 Intel Core i5-12600K、Windows(build 26200)、Python 3.11.2、NumPy 2.4.6、SciPy 1.17.1,采用 float64 计算,数值库线程上限设为 1。大图由原灯牌平铺 4×4 得到,未做插值;每轮打乱方法顺序,减少固定执行顺序的影响。原始计时记录
在这份 Python 仿真中,一阶近似约为完整搜索的 5.7 倍速度,但仍比 stableY 慢。这与误差表合起来才构成有意义的取舍:搜索更准确,一阶近似省去多轮 EOTF 计算,stableY 更轻但目标不同。这里的时间反映本文实现的成本,不能直接当作优化后的原生库吞吐量,也不构成与 SharpYUV 的速度排名。
PQ 的局限,应该怎么理解?
PQ 让有限码值能覆盖很大的亮度范围,这是它的价值。但逐通道 PQ RGB 并不是完整的感知均匀颜色空间,也不保证随后做矩阵转换、空间平均和色度降采样时仍保持亮度与色相。
本文的问题恰好落在这个缝隙里:NCL 矩阵保住的是 Y′,显示端需要的是经过各通道 EOTF 后的光。高饱和颜色把三个通道放在不同的工作点,让这种差别更容易显现。SDR 也有类似的耦合,不能简单写成“PQ 才有这个问题”。
10-bit 量化又缩小了可选修正的空间。增加位深能减小量化误差,却补不回 420 丢失的空间色度。用 ICtCp 或保留 444 是另外的方向;在交付格式固定为 PQ / BT.2020 NCL / 420 时,闭环转换仍然有实际价值。
回头看,stableY 帮我发现了错误怎样在通道间转移;SharpYUV 提供了联合迭代的思路;ITU luma-control 则让我把目标落到了显示亮度上。对这类高亮饱和色,先弄清楚哪些亮暗结构是转换额外造出来的,往往比继续调整一个平均公式更有效。
配图、HDR 显示与实验设置
三组素材均由固定种子 4200919 生成,源颜色限制在 P3-D65 的线性 RGB 0–1000 nit 内,容器为 BT.2020。蓝色色块是在容器的 PQ R′ 上添加纹理,并检查转换回 P3 后仍在范围内。所有图都为合成压力测试。
主比较使用左列平均,色度位置为 (0, 0.5),上采样为匹配相位的 bilinear,边缘采用常量延拓。Y′、Cb、Cr 均按 full-range 10-bit 量化;中性色度码值为 512。stableY 沿用 7×7 局部方差权重和 ±0.015 的 Y′ 修正限幅。
网页原位切换的是同一 96×72 局部的 6 倍像素放大图。PQ 文件为 CICP 9/16/9 的 10-bit 444 AVIF,将模拟重建结果固定下来;AV1 对输入 YUV 平面采用无损编码,并逐平面验证回读一致。冻结结果仍会经过一次 444 码值量化,统计表计算的是冻结前的重建值。
SDR 回退统一采用 1/1200 曝光和保亮度的 sRGB 色域压缩,保留高亮通道的局部差异。这是用于观察空间结构的辅助映射,颜色不等同于 PQ 直接显示。网页只显示预生成图像,不执行颜色转换;通过显示能力查询选择 PQ AVIF 或 SDR PNG。
完整数值、码值范围和文件验证见 实验记录,PC 仿真的各轮耗时见 计时记录。配图中的误差图相对于源图,而非相对于有量化误差的 444。
参考资料
- ITU-R BT.2100:HDR 信号、PQ 与颜色表示。
- ITU-T H.Sup15:PQ HDR/WCG 的转换与编码指导,重点为第 7.3 节。
- ISO/IEC TR 23008-14:2018:PQ HDR/WCG 转换技术报告。
- ITU-R BT.2390:HDR 制作与交换中的处理问题。
- WebM / Google SharpYUV:接口与实现,本文引用固定版本 ea0c6205。
- AV1 Image File Format:AVIF 色彩与像素格式。