4、自动白平衡(AWB)基础:色温与白平衡的关系,AWB的经典算法(灰度世界、完美反射、色温估计),AWB的局限性。

各位同学,今天我们聊聊自动白平衡。说白了,AWB 就是让相机在不同光线下,都能把白色拍成白色。你想想看,一张白纸在日光灯下发青,在白炽灯下发黄,人眼会自动校正,但传感器不会。AWB 就是替人眼干这个活。

我个人习惯把 AWB 看作 ISP 里的“调色盘起点”。起点歪了,后面所有颜色处理都会跑偏。我在项目中遇到过不少案例,降噪和锐化做得再好,白平衡一错,整张图就废了。

4.1 色温与白平衡的关系

色温不是温度,是光线的颜色属性。单位是开尔文(K)。数值越低,光越暖(偏红黄);数值越高,光越冷(偏蓝)。

光源类型 典型色温(K) 视觉感受
烛光 1500 - 2000 暖红
白炽灯 2500 - 3000 暖黄
日光灯 4000 - 5000 中性偏冷
正午日光 5000 - 6500 标准白
阴天 6500 - 8000 冷蓝

白平衡的核心逻辑很简单:找到场景中的“参考白”,然后反向补偿。如果参考白偏蓝,就加黄;偏黄,就加蓝。补偿量由 R、G、B 三个通道的增益决定。

关键公式:

Rout = Rin × Rgain

Gout = Gin × Ggain

Bout = Bin × Bgain

其中,Rgain、Ggain、Bgain 就是我们要算的增益系数。

4.2 AWB 的经典算法

业界常用的算法就三种。我按出场频率排个序:灰度世界、完美反射、色温估计。每种都有它的脾气。

4.2.1 灰度世界算法

这个算法假设:一张色彩丰富的图片,所有颜色的平均值应该是灰色。也就是 R、G、B 三个通道的均值相等。

实现步骤很简单:

  1. 计算整张图的 R、G、B 均值:Ravg、Gavg、Bavg。
  2. 计算全局灰度值:Gray = (Ravg + Gavg + Bavg) / 3。
  3. 计算各通道增益:Rgain = Gray / Ravg,Ggain = Gray / Gavg,Bgain = Gray / Bavg。
  4. 用增益校正每个像素。
// 灰度世界算法伪代码
float R_avg = mean(image, R_channel);
float G_avg = mean(image, G_channel);
float B_avg = mean(image, B_channel);

float Gray = (R_avg + G_avg + B_avg) / 3.0f;

float R_gain = Gray / R_avg;
float G_gain = Gray / G_avg;
float B_gain = Gray / B_avg;

for each pixel (r, g, b) in image:
    r_out = r * R_gain;
    g_out = g * G_gain;
    b_out = b * B_gain;

避坑指南:我曾经在一个纯蓝色天空的场景里用灰度世界算法,结果天空被硬生生拉成了灰色。原因很简单——场景本身就不满足“色彩丰富”的假设。遇到大面积单色场景,灰度世界会翻车。

4.2.2 完美反射算法

这个算法假设:图像中最亮的像素点应该是白色。它认为场景里总有一个区域是纯白反射面。

实现步骤:

  1. 找到图像中最亮的像素(通常是 R+G+B 最大的那个)。
  2. 以这个像素的 R、G、B 值作为参考白。
  3. 计算增益:Rgain = 255 / Rmax,Ggain = 255 / Gmax,Bgain = 255 / Bmax。
  4. 用增益校正全图。

嗯,这里要注意。完美反射对高光区域非常敏感。如果场景里有个灯泡或者反光点,算法会把它当成白色,结果整张图偏冷。

警告:完美反射算法在低光环境下几乎不可用。因为低光场景里根本没有“足够亮”的像素,算法会强行拉高增益,导致噪声爆炸。我在做夜景模式时吃过这个亏。

4.2.3 色温估计算法

这个算法更聪明一些。它不依赖全局假设,而是直接估算当前场景的色温,然后查表得到增益。

实现思路:

  1. 在标定阶段,拍摄不同色温下的灰卡,记录 R/G 和 B/G 的比值。
  2. 建立色温-比值映射表。
  3. 运行时,计算当前图像的 R/G 和 B/G 比值。
  4. 在映射表中找到最接近的色温值。
  5. 用该色温对应的增益校正图像。
// 色温估计算法伪代码
// 标定表:色温 -> (R_gain, G_gain, B_gain)
struct CalibrationEntry {
    float color_temperature;
    float R_gain;
    float G_gain;
    float B_gain;
};

CalibrationEntry table[] = {
    {3000, 1.8, 1.0, 1.4},
    {4000, 1.4, 1.0, 1.2},
    {5000, 1.2, 1.0, 1.1},
    {6500, 1.0, 1.0, 1.0},
    {8000, 0.9, 1.0, 0.8}
};

// 运行时估算色温
float R_over_G = mean(R_channel) / mean(G_channel);
float B_over_G = mean(B_channel) / mean(G_channel);

// 查表找到最接近的色温
float estimated_CT = lookup(table, R_over_G, B_over_G);

我的经验:色温估计算法是目前手机 ISP 的主流方案。它比灰度世界和完美反射都稳定,但依赖前期标定质量。标定做不好,算法就是空中楼阁。

4.3 AWB 的局限性

没有完美的算法。AWB 的局限性我总结为三点:

  • 场景假设失效:灰度世界要求色彩丰富,完美反射要求存在白点。现实场景往往不满足这些假设。比如日落时的暖色调场景,算法会强行“校正”成白色,反而破坏了氛围。
  • 混合光源难处理:室内窗边,一边是日光,一边是灯光。两种色温混在一起,AWB 不知道该听谁的。我见过不少手机在这种场景下拍出“阴阳脸”。
  • 低光噪声放大:暗光下信号弱,AWB 的增益计算会被噪声干扰。增益一高,噪声跟着放大,颜色就飘了。

下面这张图是我自己整理的 AWB 算法对比,方便你快速理解差异。

AWB 经典算法对比 灰度世界算法 假设:色彩均值 = 灰色 优点:实现简单 缺点:单色场景失效 完美反射算法 假设:最亮点 = 白色 优点:高光场景准 缺点:低光噪声大 色温估计算法 假设:标定表准确 优点:稳定性最好 缺点:依赖前期标定 共同局限性:场景假设失效 / 混合光源 / 低光噪声 实际工程建议 白天用灰度世界 + 完美反射融合 低光用色温估计 + 降噪预处理

工程建议:实际产品里很少只用一种算法。我通常的做法是:白天场景用灰度世界和完美反射做加权融合,低光场景切到色温估计。这样能兼顾稳定性和准确性。