4、AWB核心算法框架:灰度世界假设、完美反射假设、白点检测与统计方法

聊到自动白平衡的核心算法,我脑子里第一个蹦出来的就是这三个经典方法。它们就像ISP工程师工具箱里的三把扳手——各有各的脾气,也各有各的用武之地。我个人习惯把这三种方法串起来理解,而不是孤立地看。

说白了,白平衡要解决的核心问题就一个:让白色物体在任何光源下都呈现为白色。听起来简单吧?但实际做起来,坑多得很。我刚开始调AWB那会儿,就吃过不少亏。

4.1 灰度世界假设(Gray World)

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

为什么能这么假设?你想想看,自然界里各种颜色混合在一起,统计上确实会趋向中性灰。这个假设在大多数场景下都成立,尤其是户外风景、室内多物体场景。

算法实现其实就三步:

  1. 计算整幅图像R、G、B三个通道的均值
  2. 计算三个均值的平均,得到目标灰度值
  3. 分别计算每个通道的增益系数,然后应用

代码实现长这样:

// 灰度世界假设实现
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 targetGray = (avgR + avgG + avgB) / 3.0;
    double gainR = targetGray / avgR;
    double gainG = targetGray / avgG;
    double gainB = targetGray / 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);
    }
}
注意:灰度世界假设有个致命弱点——当场景中大面积存在单一颜色时(比如蓝色天空、绿色草地),算法会强行把这种颜色拉向灰色,导致偏色。我曾经在拍摄蓝色幕布的演播室场景中翻过车,整张图被拉成了灰蒙蒙一片。

4.2 完美反射假设(Perfect Reflector)

这个假设和灰度世界正好相反。它认为:图像中最亮的那个像素点,应该是纯白色的。因为最亮的物体通常就是白色物体,它应该反射所有波长的光。

嗯,这里要注意。完美反射假设不是找整幅图最亮的那个点,而是找R、G、B三个通道都接近饱和的那些像素。我习惯的做法是:

  1. 遍历所有像素,计算每个像素的亮度值
  2. 选出亮度最高的前1%~5%的像素
  3. 用这些像素的R、G、B均值来估算光源颜色
  4. 计算增益并校正
小技巧:我在项目中遇到过,直接用最亮的那一个像素容易受噪点干扰。建议取前1%的像素做平均,稳定性会好很多。另外,可以加一个亮度阈值,排除掉过曝区域。

两种假设的对比,我整理了一张表:

特性 灰度世界假设 完美反射假设
适用场景 色彩丰富的自然场景 存在白色/高光物体的场景
失效场景 大面积单色场景 无白色物体、过曝场景
计算复杂度 低(只需一次遍历) 中(需要排序或阈值筛选)
鲁棒性 一般 较好(有白色物体时)

4.3 白点检测与统计方法

前面两种方法都是全局假设,说白了就是「一刀切」。但实际场景哪有那么理想?所以工业界更常用的是白点检测+统计的方法。

核心思路是:先找出图像中「可能是白色」的像素点,然后用这些点的颜色信息来估算光源。这样既避免了灰度世界被大面积单色干扰,也避免了完美反射找不到白色物体的尴尬。

白点检测的判据有很多种,我常用的有:

  • 亮度判据:像素亮度要足够高(排除暗部噪声)
  • 色度判据:R/G和B/G的比值要在合理范围内
  • 饱和度判据:饱和度不能太高(排除彩色物体)

我曾经在调试一款手机摄像头时,发现白点检测的阈值设得太宽松,结果把浅蓝色的天空也当成了白点,导致白平衡偏蓝。后来我把色度判据的阈值收紧了一些,效果就好多了。

下面我用一张SVG流程图来展示白点检测与统计的完整流程:

白点检测与统计方法流程图 输入RAW/RGB图像 白点检测判据 亮度阈值 | 色度范围 | 饱和度限制 筛选候选白点(Top 5%~10%) 统计白点R/G、B/G均值 估算光源色温 & 计算增益 输出校正后图像

实际工程中,我一般会把灰度世界和完美反射结合起来用。比如:

  • 先用白点检测筛选出候选区域
  • 如果候选白点数量足够多(比如超过总像素的5%),就用完美反射的思路
  • 如果候选白点很少,说明场景中可能没有白色物体,这时候回退到灰度世界假设
核心要点:没有万能的算法,只有合适的组合。我见过很多工程师死磕某一种方法,结果调来调去都搞不定。其实把几种方法融合起来,再加一些场景判断逻辑,效果往往出奇的好。

最后说一句,白点检测的阈值参数千万别写死。我习惯把它们做成可调节的tuning参数,这样在不同传感器、不同镜头下都能灵活适配。你想想看,同样的算法在手机摄像头和监控摄像头上,表现可能天差地别——这就是调优的价值所在。


专注资料整理