3. 经典色温估计方法:灰度世界、白点假设与完美反射

好,我们进入正题。色温估计是 AWB 的核心,说白了就是让相机知道「当前场景下,什么颜色才算白」。

我刚开始做 ISP 时,觉得这问题很简单——找个白色物体不就行了?后来发现,真实场景哪有那么多纯白物体等你拍。所以前辈们总结了几套经典假设,今天咱们一个一个拆开看。

3.1 灰度世界假设(Gray World)

这个假设很直观:一张色彩丰富的自然图像,所有像素的 RGB 平均值应该趋近于灰色。换句话说,R、G、B 三个通道的均值应该相等。

为什么能这么假设?因为自然界中各种颜色混合在一起,统计上会抵消成中性灰。你想想看,蓝天、绿树、红墙……平均下来确实接近灰色。

3.1.1 算法流程

  1. 计算图像 R、G、B 三个通道的均值:R_avg、G_avg、B_avg
  2. 计算灰色参考值:Gray = (R_avg + G_avg + B_avg) / 3
  3. 计算各通道增益:R_gain = Gray / R_avg,G_gain = Gray / G_avg,B_gain = Gray / B_avg
  4. 用增益校正每个像素
// 灰度世界算法核心实现
void grayWorldAWB(uint8_t* img, int width, int height) {
    double sumR = 0, sumG = 0, sumB = 0;
    int total = width * height;

    // 第一步:统计各通道总和
    for (int i = 0; i < total; i++) {
        sumR += img[i * 3 + 0];
        sumG += img[i * 3 + 1];
        sumB += img[i * 3 + 2];
    }

    // 第二步:计算均值
    double avgR = sumR / total;
    double avgG = sumG / total;
    double avgB = sumB / total;

    // 第三步:计算灰色参考和增益
    double gray = (avgR + avgG + avgB) / 3.0;
    double gainR = gray / avgR;
    double gainG = gray / avgG;
    double gainB = gray / avgB;

    // 第四步:应用增益
    for (int i = 0; i < total; i++) {
        img[i * 3 + 0] = clamp(img[i * 3 + 0] * gainR);
        img[i * 3 + 1] = clamp(img[i * 3 + 1] * gainG);
        img[i * 3 + 2] = clamp(img[i * 3 + 2] * gainB);
    }
}
我的经验:灰度世界在色彩丰富的户外场景表现很好。但遇到大面积单色场景(比如蓝天、绿草地),假设就失效了。我曾经在一个全是蓝色包装的仓库里调试,结果图像被硬生生拉成了黄色——因为蓝色通道均值太高,增益压得太狠。

3.1.2 适用场景与局限

场景类型 效果 原因
色彩丰富的户外 优秀 颜色分布均匀,假设成立
单一主色调场景 较差 均值偏离灰色,校正过度
低光照环境 一般 噪声干扰均值计算
室内混合光源 中等 局部色温不一致,全局假设失效

3.2 白点假设(White Patch)

这个方法和灰度世界正好相反。灰度世界看「平均」,白点假设看「最亮」。

它的核心思想是:图像中最亮的像素点,应该对应场景中的白色区域。找到这些白点,用它们来估计光源色温。

我个人习惯把 White Patch 叫做「找最亮的仔」——你找到图像里最亮的那几个像素,假设它们就是白色,然后算增益。

3.2.1 算法实现

  1. 遍历图像,找到 R、G、B 各通道的最大值:R_max、G_max、B_max
  2. 计算各通道增益:R_gain = 255 / R_max,以此类推
  3. 用增益校正全图
注意:直接取全局最大值容易被单个噪点干扰。我建议先做高斯模糊降噪,或者取前 1% 亮度的像素做平均,而不是直接用最大值。

3.2.2 改进版本:多白点假设

基础版 White Patch 太脆弱了。我在项目中常用的是「多白点假设」——不是只找一个最亮点,而是找一批候选白点。

// 多白点假设算法
void multiWhitePatchAWB(uint8_t* img, int width, int height) {
    int total = width * height;
    double sumR = 0, sumG = 0, sumB = 0;
    int count = 0;

    // 计算亮度阈值(取前 5% 亮度)
    // 实际项目中会用直方图统计
    int threshold = 200; // 简化示例

    for (int i = 0; i < total; i++) {
        uint8_t r = img[i * 3 + 0];
        uint8_t g = img[i * 3 + 1];
        uint8_t b = img[i * 3 + 2];

        // 亮度足够高,且接近灰色(防止彩色高光干扰)
        int luminance = (r + g + b) / 3;
        int maxDiff = max(abs(r - g), max(abs(r - b), abs(g - b)));

        if (luminance > threshold && maxDiff < 30) {
            sumR += r;
            sumG += g;
            sumB += b;
            count++;
        }
    }

    if (count == 0) return; // 没找到白点,跳过

    double avgR = sumR / count;
    double avgG = sumG / count;
    double avgB = sumB / count;

    double gainR = 255.0 / avgR;
    double gainG = 255.0 / avgG;
    double gainB = 255.0 / avgB;

    // 应用增益...
}
关键点:白点假设在室内灯光场景表现很好,因为灯、白墙、白纸这些常见物体提供了可靠的白点参考。但遇到没有白色物体的场景(比如日落、森林),它就抓瞎了。

3.3 完美反射理论(Perfect Reflector)

这个名字听起来很唬人,其实它和白点假设是一家人。完美反射理论认为:场景中存在一个完美反射面,它能完全反射入射光的所有波长。这个反射面的颜色,就是光源的真实颜色。

说白了,完美反射就是 White Patch 的「理论升级版」。白点假设找的是「最亮像素」,完美反射找的是「最接近理想白体的像素」。

3.3.1 与 White Patch 的区别

对比维度 White Patch Perfect Reflector
理论基础 经验假设 物理光学模型
白点判定 亮度最高 反射率最高(接近 1)
抗噪能力 弱 中等(可结合反射率估计)
实现复杂度 低 中

3.3.2 实际应用中的折中

我在实际项目中很少严格按完美反射理论去算反射率——那需要知道场景的几何信息和光源分布,太复杂了。通常的做法是:

  • 用 White Patch 的思路找候选白点
  • 加一个「色度一致性」约束:候选点的 R/G、B/G 比值要接近
  • 再加一个「空间分布」约束:白点不能全集中在某个角落

这样既保留了完美反射的物理合理性,又保持了 White Patch 的计算效率。

避坑指南:我曾经在一个博物馆项目里用完美反射理论做 AWB,结果展品上的高光点全被当成了白点——那些是玻璃反射的灯光,根本不是场景的真实白色。后来我加了「高光检测」模块,把镜面反射和漫反射分开处理,效果才正常。

3.4 三种方法的对比与选择

这三种方法没有的好坏,关键看场景。我一般这样选:

  • 灰度世界:色彩丰富的户外场景,首选
  • 白点假设:室内有人造光源、有白色参照物的场景
  • 完美反射:需要高精度色温估计的专业场景(比如医疗影像、工业检测)

但说实话,现代 ISP 很少只用一种方法。我习惯的做法是:用灰度世界做基础校正,用白点假设做局部修正,再用完美反射做异常检测。三个方法互相兜底,效果才稳。

经典色温估计方法对比 输入图像 灰度世界假设 RGB均值 → 灰色 白点假设 最亮像素 → 白色 完美反射理论 理想反射面 → 光源色 优点 色彩丰富场景优秀 计算简单 缺点:单色场景失效 优点 室内灯光场景优秀 有白色参照物时稳定 缺点:无白点时失效 优点 物理模型更精确 适合专业场景 缺点:实现复杂 多方法融合 → 稳定 AWB 灰度世界做基础 + 白点假设做修正 + 完美反射做检测

3.5 本章小结

三种经典方法,各有各的脾气。灰度世界稳但怕单色,白点假设灵但怕没白,完美反射准但怕复杂。我建议你从灰度世界入手,先跑通基础流程,再逐步叠加其他方法。