3. ISP核心模块解析(中):RGB域处理

上一节我们把RAW域的几个关键模块聊完了。今天咱们进入RGB域。说白了,就是从传感器拿到的原始数据,经过解马赛克变成RGB三通道之后,怎么让颜色看起来「对」。

RGB域处理,我个人认为是ISP里最「玄学」的部分。为什么?因为RAW域处理大多有物理依据——坏点就是坏点,黑电平就是黑电平。但RGB域处理,尤其是白平衡和颜色校正,很大程度上取决于「你觉得什么颜色好看」。我在项目里见过不少团队,算法跑分很高,但拍出来的照片就是「不对味」。嗯,这里面的门道,咱们一个一个说。

核心知识点速览: 白平衡(AWB)解决色温偏差 → 颜色校正矩阵(CCM)修正传感器光谱响应 → Gamma校正适配显示设备非线性。三个模块串联,缺一不可。

3.1 白平衡(AWB)——让白色回归白色

白平衡的目标很简单:不管在什么光源下,白色物体拍出来都应该是白的。但实现起来,远没那么简单。

你想想看,人眼有色彩恒常性——日光灯下的一张白纸,到了白炽灯下,我们看着还是白的。但传感器没这个本事。它老老实实地记录光谱,结果就是:日光灯下偏蓝,白炽灯下偏黄。

白平衡要做的,就是估算当前光源的色温,然后对RGB三个通道分别乘一个增益,把「偏色」拉回来。

3.1.1 经典算法:灰度世界与白点检测

业界最常用的两种思路:

  • 灰度世界法:假设场景中所有颜色的平均值是灰色。那么R、G、B三个通道的平均值应该相等。如果不相等,就调整增益让它们相等。这个方法简单,但遇到大面积单色场景(比如一片蓝天)就崩了。
  • 白点检测法:在画面中找出「应该是白色」的区域,然后用这些区域的RGB值来估算色温。这个方法更准,但难点在于——你怎么知道哪些区域是白色的?

我在项目中遇到过一个问题:用白点检测法,在室内暖光灯下拍白墙,效果很好。但同一个算法拿到户外拍雪景,白点检测器把雪地误判成「偏蓝」,结果算法拼命加红色,雪变成粉色的了。后来怎么解决的?加了一个场景分类器,先判断是室内还是室外,再切换不同的白点检测策略。

实战技巧: 不要只用一种算法。我建议用「灰度世界 + 白点检测」融合的方式。低色温场景(< 4000K)以白点检测为主,高色温场景(> 6000K)适当引入灰度世界约束。这样能避免极端情况下的偏色。

3.1.2 色温估计与增益计算

估算出色温之后,怎么算增益?这里有个标准流程:

  1. 查表得到当前色温下的标准R/G、B/G比值。
  2. 用实际R/G、B/G与标准值做除法,得到增益。
  3. 限制增益范围(通常0.5~2.0),防止噪声放大。

举个例子:

// 假设标准D65光源下 R/G = 0.45, B/G = 0.65
// 实际测得 R/G = 0.38, B/G = 0.72

R_gain = 0.45 / 0.38 = 1.184
B_gain = 0.65 / 0.72 = 0.903
G_gain = 1.0  // 绿色通道通常作为参考

你看,实际R/G偏小,说明红色不够,所以R_gain > 1。实际B/G偏大,说明蓝色太多,所以B_gain < 1。这就是白平衡的本质——补不足,损有余。

注意: 增益不要设得太高。我曾经在低光场景下把R_gain拉到2.0,结果红色通道的噪声被放大了4倍(增益平方关系),整张照片全是红噪点。后来我们给增益加了一个上限1.6,并配合降噪模块联动。

3.2 颜色校正矩阵(CCM)——把「偏色」掰回来

白平衡做完,颜色看起来「差不多」了。但还不够。为什么?因为传感器的光谱响应和人眼不一样。

人眼有三种锥细胞,每种对红绿蓝的敏感曲线是固定的。但CMOS传感器的滤色片(CFA)做不到和人眼完全一致。结果就是:同一个物体,传感器看到的颜色和人眼看到的颜色有偏差。CCM就是用来修正这个偏差的。

3.2.1 CCM的数学本质

说白了,CCM就是一个3x3的矩阵:

[ R_out ]   [ a11  a12  a13 ]   [ R_in ]
[ G_out ] = [ a21  a22  a23 ] * [ G_in ]
[ B_out ]   [ a31  a32  a33 ]   [ B_in ]

每个输出通道,都包含了输入通道的线性组合。比如R_out = a11*R_in + a12*G_in + a13*B_in。这意味着什么?意味着红色通道里可以混入一点绿色和蓝色,用来修正传感器的光谱偏差。

我在项目中见过一个典型问题:某款传感器对红色敏感度偏高,拍出来的红色总是「过艳」。用CCM把a11调低到0.85,同时把a12(绿色混入红色)设为0.15,效果立竿见影。但要注意,矩阵系数不能太大,否则会引入颜色噪声。

3.2.2 矩阵标定与调优

CCM的系数怎么来?不是拍脑袋定的。标准做法是用24色卡:

步骤 操作 说明
1 拍摄色卡 在D65光源下拍摄标准24色卡
2 提取色块值 取每个色块中心区域的RGB平均值
3 对比标准值 与色卡的标准Lab值做对比
4 最小二乘法拟合 求解3x3矩阵,使色差最小
5 主观调优 根据肤色、蓝天等典型场景微调

这里我要强调一点:客观指标(比如ΔE色差)和主观感受经常打架。我记得有一次,客观指标优化到了ΔE < 2,但测试人员反馈肤色「发青」。后来发现是矩阵对肤色区域的拟合过于追求整体精度,牺牲了局部色调。最后我们给肤色区域加了权重,才平衡过来。

我的习惯: CCM调优时,准备三组矩阵——日光、灯光、混合光。不同光源下传感器的光谱响应会有细微变化,一组矩阵打天下是不现实的。另外,矩阵系数总和最好接近1,否则会改变整体亮度。

3.3 Gamma校正——让亮度「听话」

Gamma校正,很多人觉得就是套一个幂函数。没错,数学上确实简单。但为什么需要它?这里面有两个原因:

  • 人眼感知特性:人眼对暗部变化更敏感,对亮部变化不敏感。线性编码会浪费大量码字在亮部,暗部却不够用。
  • 显示设备特性:CRT显示器、LCD屏幕,都有非线性的亮度响应。如果不做Gamma校正,显示出来的图像会偏暗或偏亮。

3.3.1 标准Gamma曲线

最常用的Gamma曲线是:

V_out = V_in ^ (1/γ)

其中 γ 通常取 2.2

输入是线性光强,输出是编码后的值。γ=2.2时,暗部被拉伸,亮部被压缩。这样,8bit的码字(0~255)就能更好地匹配人眼的感知。

但实际工程中,很少直接用纯幂函数。为什么?因为纯幂函数在暗部斜率太大,会放大暗部噪声。我见过一个方案,暗部直接用线性段替代,到一定阈值再切换为幂函数。这样既保证了暗部信噪比,又保留了Gamma的压缩特性。

3.3.2 分段Gamma与自适应Gamma

现在手机ISP里,更常用的是分段Gamma:

区间 输入范围 曲线类型 作用
暗部 0 ~ 0.1 线性(斜率2.0~3.0) 抑制噪声放大
中间调 0.1 ~ 0.5 幂函数(γ=2.2) 保持对比度
亮部 0.5 ~ 1.0 幂函数(γ=1.8~2.0) 防止高光过曝

自适应Gamma就更高级了。它会根据场景的直方图分布,动态调整曲线形状。比如夜景模式,暗部区域大,就把暗部斜率调高,提亮阴影。逆光场景,亮部区域大,就把亮部压缩得更狠一些。

避坑指南: 我曾经在自适应Gamma上栽过跟头。动态调整曲线时,没有做帧间平滑,结果视频里亮度一闪一闪的,非常明显。后来加了时域滤波,每次曲线变化不超过5%,才解决了闪烁问题。做视频ISP的朋友,这点一定要记住。

3.4 三个模块的协同关系

白平衡、CCM、Gamma校正,不是各自为战的。它们之间有严格的顺序依赖:

  1. 先做白平衡:消除光源色温的影响,让颜色回到「中性」。
  2. 再做CCM:修正传感器的光谱偏差,让颜色更准确。
  3. 最后做Gamma:适配显示设备,同时做动态范围压缩。

为什么顺序不能乱?因为CCM假设输入已经是白平衡后的数据。如果先做Gamma再做CCM,非线性变换会破坏颜色的线性关系,CCM就失效了。同样,如果先做CCM再做白平衡,CCM会把色温偏差也「校正」进去,导致颜色混乱。

下面这张图,是我在项目中常用的RGB域处理流程:

RGB域处理流程 RAW域输入 白平衡 (AWB) 颜色校正 (CCM) Gamma RGB输出 关键说明: • 顺序不可颠倒:AWB → CCM → Gamma • AWB消除色温偏差,CCM修正光谱响应,Gamma适配显示 • 每个模块的输出质量直接影响下一个模块的效果

你看,这个流程是单向的、线性的。每个模块的输出质量,直接影响下一个模块的效果。白平衡偏了,CCM再怎么调也救不回来。Gamma曲线设错了,前面再准的颜色也显示不对。

所以,做ISP设计,不能只盯着单个模块的指标。要站在整个链路上看问题。我个人的习惯是:先保证白平衡的稳定性,再优化CCM的准确性,最后用Gamma做微调。每一步都留有余地,给后面的模块留出调整空间。

本章小结: RGB域处理的三个核心模块——白平衡、颜色校正、Gamma校正——构成了手机相机色彩管线的中坚力量。白平衡解决「色温」问题,CCM解决「色偏」问题,Gamma解决「亮度」问题。三者协同,才能输出一张色彩自然、观感舒适的照片。