4. 色彩还原与白平衡:色彩校正矩阵(CCM)、自动白平衡(AWB)算法、色彩饱和度与自然度的平衡
色彩还原这件事,说简单也简单,说复杂也复杂。简单到你可以把它理解成「让红色看起来是红色」,复杂到它背后牵扯着传感器光谱响应、光源色温估计、人眼视觉偏好等一系列问题。
我个人习惯把色彩还原拆成两个阶段来看:先解决「对不对」的问题,再解决「好不好看」的问题。白平衡解决的是前者,色彩校正矩阵(CCM)和饱和度调整解决的是后者。
4.1 自动白平衡(AWB)——让白色回归白色
你想想看,一张白纸在日光灯下偏蓝,在白炽灯下偏黄。人眼会自动适应这种变化,但传感器不会。所以AWB要做的,就是估计当前光源的色温,然后把图像拉回到「标准光源」下的样子。
4.1.1 灰度世界假设
这是最经典的AWB方法。它的核心假设是:一张色彩丰富的自然图像,所有像素的R、G、B均值应该趋于相等。如果R均值明显偏高,说明光源偏红,需要降低R增益。
// 灰度世界算法伪代码
float avgR = mean(image[:,:,0]);
float avgG = mean(image[:,:,1]);
float avgB = mean(image[:,:,2]);
float gainR = avgG / avgR;
float gainB = avgG / avgB;
// 应用增益
image[:,:,0] = clamp(image[:,:,0] * gainR, 0, 255);
image[:,:,2] = clamp(image[:,:,2] * gainB, 0, 255);
4.1.2 完美反射假设
另一种思路是:图像中最亮的像素应该接近白色。找到这些「参考白点」,用它们的RGB比值来估计光源。
实际工程中,我建议把两种方法结合起来:
- 灰度世界:适合色彩丰富的场景
- 完美反射:适合有高光区域的场景
- 加权融合:根据场景统计信息动态调整权重
4.1.3 色温曲线与增益映射
更工程化的做法是预先标定一组色温曲线。在不同色温下拍摄灰卡,记录R/G和B/G的比值,形成一条色温曲线。运行时,根据统计的R/G和B/G值,查表得到估计色温,再查表得到对应的R、B增益。
| 色温(K) | R/G 比值 | B/G 比值 | R 增益 | B 增益 |
|---|---|---|---|---|
| 2800(白炽灯) | 1.85 | 0.52 | 0.54 | 1.92 |
| 4000(荧光灯) | 1.35 | 0.78 | 0.74 | 1.28 |
| 5500(日光) | 1.00 | 1.00 | 1.00 | 1.00 |
| 6500(阴天) | 0.82 | 1.18 | 1.22 | 0.85 |
4.2 色彩校正矩阵(CCM)——从传感器RGB到标准RGB
白平衡做完之后,图像的颜色大致对了,但还不够准。为什么?因为传感器的RGB响应曲线和人眼并不一样。传感器对某些波长的光敏感度偏高或偏低,导致颜色偏移。
CCM的作用就是用一个3x3的矩阵,把传感器RGB空间转换到标准RGB空间(比如sRGB)。
// CCM 矩阵乘法
R_out = CCM[0][0]*R_in + CCM[0][1]*G_in + CCM[0][2]*B_in
G_out = CCM[1][0]*R_in + CCM[1][1]*G_in + CCM[1][2]*B_in
B_out = CCM[2][0]*R_in + CCM[2][1]*G_in + CCM[2][2]*B_in
4.2.1 CCM的标定
标定CCM需要用到标准色卡,比如24色卡。流程是这样的:
- 在标准光源下拍摄色卡
- 提取每个色块的传感器RGB值
- 与标准色卡的标准RGB值做对比
- 用最小二乘法求解最优的3x3矩阵
嗯,这里要注意:CCM是跟光源相关的。不同色温下,传感器的响应曲线会有变化,所以通常需要标定多组CCM,比如低色温、中色温、高色温各一组,运行时根据AWB估计的色温做插值。
4.2.2 CCM的副作用
CCM不是万能的。它有一个明显的副作用:放大噪声。矩阵的非对角元素越大,噪声放大的程度就越严重。特别是B通道,在低照度下噪声本来就大,经过CCM之后可能更明显。
4.3 色彩饱和度与自然度的平衡
颜色准了,但用户不一定喜欢。你想想看,真实世界的颜色有时候看起来「不够鲜艳」。用户喜欢更饱和的蓝天、更翠绿的草地、更红润的肤色。
所以ISP里通常会在CCM之后再加一个饱和度调整模块。这个模块的核心问题就是:饱和度拉到多高才算「好看」?
4.3.1 饱和度控制策略
我见过几种常见的做法:
- 全局饱和度:对所有像素统一乘一个饱和度系数。简单粗暴,但容易让肤色失真。
- 分区饱和度:对不同颜色区域施加不同的饱和度增益。比如对蓝色和绿色区域加得多一些,对肤色区域加得少一些。
- 自适应饱和度:根据亮度、饱和度、色相做非线性映射。低饱和度区域保持自然,高饱和度区域适当增强。
我个人比较推荐第三种。它能在保持肤色自然的同时,让风景照看起来更讨喜。
4.3.2 肤色保护
肤色保护是饱和度调整中最关键的一环。我曾经调试过一款产品,饱和度拉上去之后,人脸看起来像「蜡像」——颜色太浓,失去了皮肤的质感。
解决方案是在YCbCr空间里,对Cb和Cr分量做非线性映射。在肤色区域(Cb和Cr落在特定范围内),降低饱和度增益;在其他区域,正常增强。
// 肤色保护饱和度调整伪代码
for each pixel:
if (Cb in skinCbRange && Cr in skinCrRange):
satGain = 1.0 + (globalSatGain - 1.0) * skinProtectFactor
else:
satGain = globalSatGain
Cb_out = Cb * satGain
Cr_out = Cr * satGain
4.4 整体流程与知识结构
把上面这些串起来,色彩还原的完整流程是这样的:
从RAW输入到最终输出,每一步都有它的作用,每一步也都有它的代价。AWB解决了色偏问题,但可能在单色场景下翻车;CCM提高了色彩准确度,但放大了噪声;饱和度调整让画面更讨喜,但过度增强会破坏自然感。