色温估计基础:灰度世界、完美反射与普朗克轨迹
做AWB这么多年,我最大的感触就是——色温估计是白平衡的“眼睛”。眼睛准了,后面色彩还原才有意义。今天咱们就把这几种经典方法掰开揉碎了讲清楚。
3.1 灰度世界假设(Gray World)
这个假设很朴素:一张色彩丰富的自然图像,所有像素的R、G、B平均值应该趋于相等。说白了,就是场景整体是“灰色”的。
核心公式:
R_avg = G_avg = B_avg → 场景光源为白色
实际计算时,我们分别求三个通道的均值,然后算出增益:
gain_R = G_avg / R_avg
gain_B = G_avg / B_avg
我在瑞芯微RV1109上调试时,遇到过一个问题:拍一片蓝天时,灰度世界假设直接把蓝色压成了灰色。为什么?因为天空本身就不是“灰度世界”,它偏蓝。所以这个方法有个致命弱点——场景必须色彩丰富。
| 场景类型 | 灰度世界效果 | 原因 |
|---|---|---|
| 自然风景(绿树+蓝天+建筑) | ✅ 好 | 色彩分布均匀 |
| 单一色块(蓝天、草地) | ❌ 差 | 主色调偏移估计 |
| 室内混合光源 | ⚠️ 一般 | 局部色温不一致 |
我的经验:在瑞芯微平台上,我通常把灰度世界作为“兜底方案”。当其他算法失效时,用它做最后一道防线。但一定要加一个色彩丰富度检测——如果场景方差太小,就别用。
3.2 完美反射假设(Perfect Reflector)
这个假设换个角度想:场景中最亮的那个点,应该反射了光源的全部颜色。所以它应该是白色的。
具体做法是:
- 找到图像中最亮的像素(通常是R+G+B最大的那个)
- 假设这个像素是纯白色
- 用这个像素的RGB值反推光源颜色
我曾经踩过的坑:有一次在RK3588上调试,发现AWB在拍金属表面时总是偏色。后来才发现,金属高光点虽然很亮,但它不是“完美反射”——它反射的是环境光,不是光源本身。所以用完美反射假设时,一定要排除镜面高光。
两种假设的对比,我整理了一张表:
| 对比项 | 灰度世界 | 完美反射 |
|---|---|---|
| 依赖条件 | 色彩丰富度 | 存在高亮区域 |
| 计算复杂度 | 低(只需均值) | 中(需要排序查找) |
| 典型失败场景 | 单色场景 | 无高光、过曝场景 |
| 瑞芯微平台适配 | 直接可用 | 需加高光检测 |
3.3 色温曲线与普朗克轨迹
你想想看,一个黑体在不同温度下会发出不同颜色的光。从暗红到橙黄,再到白、蓝。这条颜色变化轨迹,就是普朗克轨迹。
在AWB中,我们通常把这条轨迹映射到R/G和B/G的二维坐标系里。为什么?因为这样就能用一条曲线来描述所有可能的“自然光源”。
实际应用:
// 瑞芯微平台上的色温曲线查找示例
typedef struct {
int color_temp; // 色温值 (K)
float rg_ratio; // R/G 比值
float bg_ratio; // B/G 比值
} awb_curve_point_t;
// 预置的普朗克轨迹点
awb_curve_point_t plancks_curve[] = {
{2500, 1.85, 1.20}, // 白炽灯
{4000, 1.40, 1.50}, // 荧光灯
{5500, 1.00, 1.00}, // 日光
{6500, 0.85, 1.30}, // 阴天
{8000, 0.70, 1.60}, // 蓝天阴影
};
我个人习惯把色温曲线分成三段:
- 低色温段(2000K-3500K):偏红黄,R/G > 1.5
- 中间段(3500K-5500K):接近白色,R/G ≈ 1.0
- 高色温段(5500K-8000K):偏蓝,B/G > 1.3
嗯,这里要注意:实际场景的光源并不都是黑体辐射。LED灯、荧光灯的光谱分布和黑体差别很大。所以普朗克轨迹只是一个参考基准,不是真理。
3.4 色温估计的局限性
说实话,没有一种方法是万能的。我总结了几个常见的“翻车现场”:
局限性一:混合光源场景
比如室内既有窗外的日光,又有头顶的LED灯。这时候整张图像没有一个统一的光源色温。我曾经在RK3568上调试会议室摄像头,画面一半是蓝色窗光,一半是暖色灯光,AWB怎么调都偏色。最后只能做分区AWB。
局限性二:极端色温
低于2500K的烛光场景,或者高于10000K的雪地阴影。普朗克轨迹在这些区域已经很不准确了。我建议在瑞芯微平台上,把色温估计范围限制在2500K-8000K之间,超出部分做截断处理。
局限性三:场景内容干扰
拍一片红色花海时,灰度世界会认为光源偏红。拍蓝色大海时,又会认为光源偏蓝。这就是所谓的“场景色偏”问题。我的做法是:加入一个“场景先验知识库”,比如检测到大量绿色时,不盲目降低G通道增益。
最后说一句实在的:色温估计不是越复杂越好。在瑞芯微的嵌入式平台上,算力有限,我通常的做法是:
- 先用灰度世界做粗估计
- 再用完美反射做细校准
- 最后用普朗克轨迹做约束
三层下来,90%的场景都能搞定。剩下的10%,靠场景检测和特殊处理。