1. 为什么需要从色域坐标推导转换矩阵?
第一次接触RGB与YUV转换时,你可能直接套用过现成的转换系数。比如BT.601标准中常用的Y=0.299R+0.587G+0.114B这个公式。但当你需要适配HDR视频或特殊显示设备时,突然发现不同标准下的系数完全不同——这时候就需要理解这些数字背后的生成逻辑。
色域坐标(如x=0.64,y=0.33的红点坐标)定义了颜色空间的"边界"。就像地图上的经纬度决定了国家版图,色坐标决定了这个标准能显示多少种颜色。当摄像机使用BT.2020广色域拍摄,而显示器只支持BT.709时,就需要通过转换矩阵进行色彩映射。理解从色坐标到矩阵系数的推导过程,你就能:
- 自定义非标准色域的转换参数
- 排查跨设备色彩不一致的问题
- 优化HDR视频转码的色域压缩
2. 色域坐标到RGB转XYZ矩阵
2.1 色度坐标与三原色定义
以BT.709标准为例,其色域坐标定义为:
- 红色:x=0.64, y=0.33
- 绿色:x=0.30, y=0.60
- 蓝色:x=0.15, y=0.06
- 白点(D65):x=0.3127, y=0.3290
这些xy坐标需要先转换到XYZ色彩空间。XYZ是CIE定义的与设备无关的色彩空间,其中Y分量直接对应人眼感知的亮度。转换公式为:
def xy_to_XYZ(x, y): X = x / y Y = 1.0 # 亮度归一化 Z = (1 - x - y) / y return [X, Y, Z]2.2 构建RGB转XYZ矩阵
通过白点平衡计算,可以得到3x3的转换矩阵。这里用Python示例计算BT.709的矩阵:
# BT.709色坐标 red = [0.64, 0.33] green = [0.30, 0.60] blue = [0.15, 0.06] white = [0.3127, 0.3290] # 转换为XYZ坐标 RGB_XYZ = np.array([ xy_to_XYZ(*red), xy_to_XYZ(*green), xy_to_XYZ(*blue) ]).T # 转置为3x3矩阵 # 白点平衡计算 white_XYZ = xy_to_XYZ(*white) S = np.linalg.inv(RGB_XYZ) @ white_XYZ M_RGB2XYZ = RGB_XYZ * S # 最终矩阵得到的矩阵第二行[0.2126, 0.7152, 0.0722]就是RGB转灰度的系数来源——因为它对应人眼最敏感的亮度感知。
3. 推导YUV转换系数
3.1 Full Range系数计算
YUV的Y分量直接使用RGB转XYZ矩阵的第二行(亮度系数),而UV分量则通过色差计算得到:
# 从RGB2XYZ矩阵提取亮度系数 kr, kg, kb = M_RGB2XYZ[1] # 构建RGB转YUV矩阵 rgb2yuv = np.zeros((3,3)) rgb2yuv[0] = [kr, kg, kb] # Y行 rgb2yuv[1] = [kr/(2*(1-kb)), kg/(2*(1-kb)), 0.5] # U行 rgb2yuv[2] = [0.5, kg/(2*(1-kr)), kb/(2*(1-kr))] # V行 # 求逆得到YUV转RGB矩阵 yuv2rgb = np.linalg.inv(rgb2yuv)这里UV分量的分母(2*(1-kb))是为了将色差归一化到[-0.5,0.5]范围。你可以尝试修改这个分母值,观察对色彩饱和度的影响。
3.2 Limit Range的调整
电视信号常用的YCbCr(Limit Range)需要额外处理:
def full_to_limit(yuv2rgb, bit_depth=8): max_val = (1 << bit_depth) - 1 ys = 219 * (1 << (bit_depth - 8)) # Y范围系数 uvs = 224 * (1 << (bit_depth - 8)) # UV范围系数 scale_mat = np.diag([max_val/ys, max_val/uvs, max_val/uvs]) return yuv2rgb @ scale_mat # 矩阵乘法这个缩放操作解释了为什么8位YCbCr中,Y的有效范围是16-235而不是0-255——这是为了给信号过冲留出余量。
4. 不同标准的系数对比
通过上述方法,我们计算出三大标准的Full Range系数:
| 标准 | Y系数 (kr,kg,kb) | YUV→RGB矩阵示例 |
|---|---|---|
| BT.601 | 0.299, 0.587, 0.114 | [[1.0, 0.0, 1.402], ...] |
| BT.709 | 0.2126,0.7152,0.0722 | [[1.0, 0.0, 1.5748], ...] |
| BT.2020 | 0.2627,0.6780,0.0593 | [[1.0, 0.0, 1.4746], ...] |
可以看到随着色域扩大(BT.2020>BT.709>BT.601),蓝色系数kb逐渐减小,因为广色域需要更多绿色来保持亮度平衡。
5. 实战:自定义色域转换
假设你要为某款专业显示器定制转换矩阵,其色坐标为:
- 红:x=0.68,y=0.32
- 绿:x=0.25,y=0.65
- 蓝:x=0.12,y=0.05
- 白点:D50(x=0.3457,y=0.3585)
只需修改第2章的代码中的坐标值,就能生成对应的转换矩阵。在实际应用中,还需要考虑:
- Gamma校正:现代标准使用PQ或HLG传递函数
- 色度子采样:YUV420等格式需要特殊处理UV分量
- 位深扩展:10/12位数据需要调整缩放系数
我曾在一个医疗影像项目中,因为直接使用BT.709矩阵导致X光片色彩失真。通过重新计算设备实际的色域矩阵,最终实现了准确的灰阶再现。这印证了理解底层原理的重要性——现成系数就像别人家的钥匙,未必能打开你的门。