2. 色彩空间与转换:RGB、CMYK、YCbCr、XYZ
色彩空间,说白了就是描述颜色的数学坐标系。同一个颜色,在不同坐标系下的数值完全不同。我在做图像传感器ISP项目时,就吃过这个亏——传感器输出的RAW数据是Bayer格式,但后端显示器和编码器各要一套不同的色彩空间。如果转换做不好,画面偏色、饱和度丢失都是家常便饭。
这一章,我们重点讲四个最常用的色彩空间:RGB、CMYK、YCbCr和XYZ。然后我会把它们的数学转换矩阵掰开揉碎讲清楚。硬件里做色彩空间转换,说白了就是矩阵乘法加截位,但精度和位宽怎么选,这里头门道不少。
2.1 四大色彩空间概述
先给个总览表格,方便你快速对比:
| 色彩空间 | 典型用途 | 分量含义 | 硬件实现特点 |
|---|---|---|---|
| RGB | 显示器、摄像头、图像传感器 | 红、绿、蓝三原色 | 直接,但各分量相关性高 |
| CMYK | 印刷、打印机 | 青、品红、黄、黑 | 四分量,硬件中较少直接处理 |
| YCbCr | 视频压缩、JPEG、H.264/H.265 | 亮度Y + 色度Cb/Cr | 亮度与色度分离,利于压缩 |
| XYZ | 色彩管理、设备无关标准 | 三刺激值(人眼响应) | 线性空间,常作为中间桥梁 |
核心观点:硬件里最常用的是RGB↔YCbCr和RGB↔XYZ。CMYK我很少在FPGA里直接做,除非你接的是工业打印机控制器。
2.2 RGB色彩空间
RGB是最直观的。每个像素用三个8位或10位数值表示红、绿、蓝的强度。0表示最暗,255(或1023)表示最亮。
但有个坑:RGB不是设备无关的。同样的RGB值,在不同显示器上颜色不一样。为什么?因为每个显示器的色域和伽马曲线不同。我在一个项目里遇到过,摄像头采集的RGB在PC上看正常,接到嵌入式屏上就偏蓝。后来发现是两边的RGB色域标准不一样——一个用sRGB,一个用Adobe RGB。
硬件里处理RGB,通常就是直接做加减乘除。比如白平衡调整,就是给R、G、B各乘一个系数。但要注意位宽扩展,8位乘8位至少需要16位中间结果。
2.3 CMYK色彩空间
CMYK是减色模型。你想想看,白纸反射所有光,印上青色油墨就吸收了红光。所以C、M、Y分别吸收红、绿、蓝。K(黑色)是为了节省彩色油墨并加深暗部。
从RGB到CMYK的转换公式:
// 先归一化到[0,1]
R' = R / 255, G' = G / 255, B' = B / 255
// 计算CMY
C = 1 - R'
M = 1 - G'
Y = 1 - B'
// 提取黑色分量
K = min(C, M, Y)
// 调整CMY
C = (C - K) / (1 - K)
M = (M - K) / (1 - K)
Y = (Y - K) / (1 - K)
注意:CMYK转换涉及除法,硬件里除法器很贵。我一般建议用查找表(LUT)或者近似算法。除非你非做不可,否则尽量绕开CMYK。
2.4 YCbCr色彩空间
YCbCr是视频领域的王牌。它把亮度(Y)和色度(Cb、Cr)分开。人眼对亮度变化敏感,对色度变化不敏感。所以视频编码时可以降低色度的采样率——这就是4:2:2、4:2:0的由来。
标准BT.601(标清)的RGB→YCbCr矩阵:
Y = 0.299 * R + 0.587 * G + 0.114 * B
Cb = -0.169 * R - 0.331 * G + 0.500 * B + 128
Cr = 0.500 * R - 0.419 * G - 0.081 * B + 128
硬件实现时,浮点数乘法太慢。我习惯把系数放大到整数:
// 定点化,放大2^10 = 1024倍
Y = ( 306 * R + 601 * G + 117 * B) >> 10
Cb = (-173 * R - 339 * G + 512 * B) >> 10 + 128
Cr = ( 512 * R - 429 * G - 83 * B) >> 10 + 128
我的经验:定点化时,系数精度至少保留10位小数位(Q10格式),否则色度偏差肉眼可见。另外,Cb/Cr加128这个偏置,在硬件里就是加一个常数,不费资源。
2.5 XYZ色彩空间
XYZ是CIE在1931年定义的设备无关色彩空间。它基于人眼对红、绿、蓝三种锥状细胞的响应曲线。说白了,XYZ就是颜色的"坐标"。
从RGB到XYZ的转换取决于RGB的色域标准。以sRGB为例:
|X| |0.4124564 0.3575761 0.1804375| |R_linear|
|Y| = |0.2126729 0.7151522 0.0721750| |G_linear|
|Z| |0.0193339 0.1191920 0.9503041| |B_linear|
注意,这里的R_linear、G_linear、B_linear是经过伽马校正前的线性值。sRGB有伽马曲线,硬件里需要先做反伽马校正,再做矩阵乘法。
关键点:XYZ是线性空间。所有色彩空间转换,如果经过XYZ做中间桥梁,可以避免直接转换的精度损失。我在做多色域校准项目时,就是先把所有输入统一转到XYZ,再转到目标空间。
2.6 色彩空间转换的数学原理
所有线性色彩空间转换,本质上都是3×3矩阵乘法。为什么?因为人眼的颜色视觉是线性的(在忽略伽马的情况下)。
通用形式:
|C1_out| |a11 a12 a13| |C1_in|
|C2_out| = |a21 a22 a23| |C2_in|
|C3_out| |a31 a32 a33| |C3_in|
硬件实现这个矩阵乘法,需要:
- 9个乘法器(每个系数乘一个输入分量)
- 6个加法器(每行三个乘积相加)
- 流水线寄存器(至少2级流水,避免组合逻辑过长)
我曾经优化过一个色彩转换模块,把9个乘法器缩减到6个。怎么做?利用系数对称性。比如YCbCr矩阵里,很多系数互为相反数,可以共用乘法器再取反。
2.7 硬件实现要点
这里列几个我在项目中踩过的坑:
- 位宽选择:输入8位,中间结果至少12位,输出8位。如果中间截位太狠,色带效应就出来了。
- 饱和处理:矩阵乘法可能产生负值或超过255的值。硬件里要做钳位(clamp)到[0, 255]。
- 流水线深度:我建议至少3级流水——乘法、加法、钳位各一级。跑200MHz以上时,可能需要4-5级。
- 系数存储:用寄存器存系数,不要用BRAM。因为系数很少(9个),寄存器更省资源。
避坑指南:我曾经在YCbCr转RGB时,忘记处理Cb/Cr的128偏置。结果画面整体偏绿,查了两天才发现。记住:YCbCr的Cb/Cr是有偏置的,转RGB前要先减128。
2.8 本章知识结构图
下面这张SVG图,把四个色彩空间的关系和转换路径画清楚了:
2.9 总结
色彩空间转换,说穿了就是矩阵乘法。但真正做好,要考虑定点精度、流水线结构、位宽管理和边界处理。我个人的建议是:
- 视频项目优先用YCbCr,兼容性好,压缩方便
- 色彩校准项目用XYZ做中间桥梁,精度最高
- 显示驱动直接用RGB,省去转换开销
- CMYK能不用就不用,除非你接打印机