3. 灰度世界法:最朴素的白平衡估计

灰度世界法(Gray World Assumption)是我入行时学的第一个自动白平衡算法。说实话,它简单到让人怀疑——就这?但你别小看它,很多高端 ISP 的预处理环节里,灰度世界法依然在默默干活。

它的核心假设就一句话:一张色彩丰富的自然图像,所有像素的 R、G、B 均值应该趋于相等。说白了,就是认为世界平均是灰色的。如果拍出来的画面偏色,那一定是光源在捣鬼。

3.1 算法原理

我们先把问题拆开看。假设传感器捕获的 RAW 数据是 I_raw,经过黑电平校正和线性化之后,每个像素的 R、G、B 值分别记为 R(x,y)、G(x,y)、B(x,y)。

灰度世界法认为:

  • 计算全图 R 通道均值:R_avg = mean(R)
  • 计算全图 G 通道均值:G_avg = mean(G)
  • 计算全图 B 通道均值:B_avg = mean(B)

然后我们假设 G 通道是参考通道(因为人眼对绿色最敏感,ISP 里也常用 G 做基准),那么 R 和 B 的增益就是:

gain_R = G_avg / R_avg
gain_B = G_avg / B_avg

最后把增益乘回去:

R_corrected = R * gain_R
B_corrected = B * gain_B
G_corrected = G  (保持不变)

嗯,就是这么直白。你想想看,如果画面偏红,那 R_avg 就会偏大,gain_R 就会小于 1,红色被压下去,画面就正了。

核心要点:灰度世界法本质是在做通道均值对齐。它不关心场景里到底有什么物体,只关心统计结果。

3.2 代码实现

我习惯用 Python + NumPy 来快速验证算法。下面这个函数接受一个 RAW 格式的 numpy 数组(假设已经是线性数据),返回校正后的图像和增益值。

import numpy as np

def gray_world_awb(raw_img):
    """
    灰度世界法自动白平衡
    :param raw_img: 形状为 (H, W, 3) 的 numpy 数组,通道顺序为 R, G, B
    :return: corrected_img, (gain_R, gain_G, gain_B)
    """
    # 分离通道
    R = raw_img[:, :, 0].astype(np.float64)
    G = raw_img[:, :, 1].astype(np.float64)
    B = raw_img[:, :, 2].astype(np.float64)

    # 计算各通道均值
    R_avg = np.mean(R)
    G_avg = np.mean(G)
    B_avg = np.mean(B)

    # 防止除零
    if R_avg == 0 or G_avg == 0 or B_avg == 0:
        print("警告:存在通道均值为零,跳过校正")
        return raw_img, (1.0, 1.0, 1.0)

    # 计算增益(以 G 通道为参考)
    gain_R = G_avg / R_avg
    gain_B = G_avg / B_avg
    gain_G = 1.0

    # 应用增益
    R_corrected = R * gain_R
    G_corrected = G * gain_G
    B_corrected = B * gain_B

    # 合并通道
    corrected_img = np.stack([R_corrected, G_corrected, B_corrected], axis=2)

    # 裁剪到有效范围(假设输入是 12-bit RAW,范围 0-4095)
    corrected_img = np.clip(corrected_img, 0, 4095)

    return corrected_img.astype(raw_img.dtype), (gain_R, gain_G, gain_B)

我的小习惯:实际项目中我会先做一次坏点校正和黑电平减除,再跑灰度世界法。否则暗电流和坏点会严重拉偏均值,增益算出来全是错的。

3.3 灰度世界法的 SVG 流程图

下面这张图把整个流程串起来了。你可以看到,从 RAW 输入到增益输出,中间只有三个步骤——均值计算、增益推导、像素校正。

灰度世界法流程图 RAW 输入 (H,W,3) 分离 R/G/B 通道 计算 R_avg, G_avg, B_avg gain_R = G_avg / R_avg gain_B = G_avg / B_avg 校正后 RAW 说明:G 通道作为参考通道,增益固定为 1.0;R 和 B 通道根据均值比例调整

3.4 优缺点分析

灰度世界法不是万能的。我在项目中踩过不少坑,下面把它的优缺点摊开说。

维度 优点 缺点
计算量 极低。只需要一次全图遍历求均值,O(N) 复杂度 无
实现难度 简单到离谱。20 行代码搞定 无
适用场景 色彩丰富的自然场景、室内混合光源 大面积单色场景(蓝天、绿草地、红墙)会严重误判
鲁棒性 对噪声有一定容忍度(均值本身抗噪) 对暗电流和坏点敏感,需要预处理
色温范围 D50~D65 标准光源下表现不错 极端色温(低色温烛光、高色温阴天)偏差大

我曾经踩过的坑:有一次在户外拍蓝天白云,灰度世界法把整个画面拉成了黄褐色。为什么?因为天空占了画面 70%,B 通道均值远高于 R 和 G,算法以为光源偏蓝,拼命加红加绿,结果蓝天变黄天。从那以后,我学会了在跑灰度世界法之前先做天空检测或场景分类。

3.5 改进思路

既然灰度世界法有硬伤,那怎么救?我分享几个实战中常用的改进方案:

  • 剔除极端像素:计算均值前,去掉最亮和最暗的 5% 像素。这样可以避免高光溢出和暗电流的干扰。
  • 分块灰度世界:把图像分成 4x4 或 8x8 的块,每块独立计算增益,最后用插值平滑。适合光源不均匀的场景。
  • 结合色温先验:如果已知光源色温范围(比如室内灯光 2700K~4000K),可以把增益限制在合理区间内。
  • 加权灰度世界:给饱和度低的像素更高权重,因为灰色像素更符合“世界是灰色”的假设。

我的建议:灰度世界法最适合做粗校正。在 ISP 流水线里,我通常把它放在最前面,先把大方向的偏色调回来,后面再用更精细的算法(比如色温估计 + 查找表)做微调。这样既快又稳。

3.6 小结

灰度世界法就像一把瑞士军刀——不锋利,但什么场合都能掏出来用一下。它的价值不在于精度,而在于速度和稳定性。你想想看,在嵌入式平台上,每毫秒都很珍贵,一个 O(N) 的算法能搞定 80% 的场景,剩下的 20% 再上更复杂的方案,这才是工程思维。