3. 白平衡问题定义:偏色现象、校正目标与两大经典假设
好,咱们正式开始进入白平衡算法的核心地带。在做自动白平衡之前,你得先搞清楚一个问题:我们到底在解决什么问题?
说白了,就是照片拍出来颜色不对。明明是一张白纸,在暖色灯光下拍出来发黄,在阴天拍出来发蓝。这种偏色,就是我们要搞定的目标。
3.1 偏色现象:为什么相机看到的颜色和人眼不一样?
人眼有个很牛的能力叫「色彩恒常性」。不管光源怎么变,我们看一张白纸,它始终是白的。但相机传感器没这个本事,它老老实实地记录进入镜头的光线。光源变了,记录下来的 RGB 值就变了。
我举个例子。你在办公室的日光灯下拍一张白纸,RGB 值大概是 (240, 245, 250)。拿到家里暖色台灯下再拍,可能就变成了 (255, 220, 180)。你看,蓝色通道掉下去了,红色通道爆表了,整张图就偏黄。
偏色的本质: 光源的光谱能量分布不同,导致物体反射到传感器上的 RGB 比例发生偏移。这不是物体本身的颜色变了,是光源在「捣鬼」。
常见的偏色场景我整理了一下:
| 光源类型 | 色温范围 (K) | 偏色倾向 | 典型场景 |
|---|---|---|---|
| 白炽灯 | 2500 - 3000 | 偏黄/橙 | 家庭卧室、餐厅 |
| 日光灯 | 4000 - 5000 | 偏绿/白 | 办公室、教室 |
| 正午日光 | 5000 - 6500 | 标准白光 | 户外晴天 |
| 阴天/阴影 | 7000 - 9000 | 偏蓝 | 阴天户外、树荫下 |
嗯,这里要注意。色温越高,颜色越偏蓝;色温越低,颜色越偏红。这个和直觉是反的,别搞混了。
3.2 白平衡校正目标:让白色回归白色
校正目标其实就一句话:让图像中原本是白色的物体,在任意光源下都呈现为白色。
一旦白色被校正了,其他所有颜色都会跟着被拉回正确的色域。这就是白平衡的底层逻辑——以白点为锚点,校正整个色偏。
数学上怎么描述?假设我们有一个偏色图像,它的 R、G、B 三个通道需要分别乘上一个增益系数,让白色区域的 R、G、B 值相等。
// 校正后的像素值 = 原始像素值 × 增益系数
R' = R × gain_R
G' = G × gain_G
B' = B × gain_B
// 校正目标:对于白色区域,R' = G' = B'
说白了,就是找到三个系数,把偏色的通道拉回来。那问题来了:增益系数怎么算?
这就引出了两个最经典的假设——灰度世界假设和完美反射假设。几乎所有自动白平衡算法,都是在这两个假设的基础上发展出来的。
3.3 灰度世界假设 (Gray World Assumption)
这个假设很直观:一张色彩丰富的自然图像,所有像素的 R、G、B 平均值应该趋于同一个灰度值。
为什么?因为自然界中各种颜色混合在一起,统计上会趋向于灰色。如果图像偏黄,说明 R 通道的平均值偏高,B 通道的平均值偏低。那我们就把 R 的增益调低,B 的增益调高,让三个通道的平均值相等。
我的经验: 灰度世界假设在色彩丰富的场景下效果非常好。但我曾经在一个全是蓝色天空的航拍图上栽过跟头——整张图只有蓝色,平均值严重偏蓝,算法硬是把蓝色拉成了灰色,结果天空变成了灰蒙蒙一片。所以,这个假设的前提是「色彩丰富」,场景单一的时候千万别用。
计算步骤很简单:
- 计算整张图像 R、G、B 三个通道的平均值:avg_R, avg_G, avg_B
- 计算三个平均值的总平均:gray = (avg_R + avg_G + avg_B) / 3
- 计算每个通道的增益:gain_R = gray / avg_R, gain_G = gray / avg_G, gain_B = gray / avg_B
- 用增益系数校正每个像素
// 灰度世界假设的 Python 实现
import numpy as np
def gray_world(img):
# img: HxWx3, dtype=np.uint8
avg = np.mean(img, axis=(0, 1)) # 每个通道的平均值
gray = np.mean(avg) # 总平均灰度
gain = gray / avg # 增益系数
corrected = (img * gain).clip(0, 255).astype(np.uint8)
return corrected
3.4 完美反射假设 (Perfect Reflector Assumption)
另一个经典假设:图像中最亮的像素点,应该是纯白色的。 也就是说,图像中一定存在某个区域,它对所有波长的光都完全反射,不吸收任何颜色。
这个假设的出发点是:场景中总会有高光、镜面反射或者白色物体。这些区域的 RGB 值应该相等。如果它们不相等,说明存在色偏,我们就用这些「最亮点」来估计光源颜色。
避坑指南: 我曾经在一个夜景灯光图像上用过完美反射假设,结果翻车了。因为图像中最亮的点是红色的霓虹灯,算法把它当成了「完美反射点」,结果整张图被拉成了青绿色。所以,使用前一定要确认最亮点确实是白色物体或高光,而不是彩色光源本身。
实现思路:
- 找到图像中最亮的像素点(通常是 R+G+B 最大的前 0.1% 像素)
- 计算这些像素点的 R、G、B 平均值
- 用这些平均值作为光源颜色的估计
- 计算增益系数,使这些最亮点变为纯白色
// 完美反射假设的 Python 实现
def perfect_reflector(img, percentile=99.9):
# 计算每个像素的亮度
brightness = np.sum(img, axis=2)
# 找到亮度最高的阈值
threshold = np.percentile(brightness, percentile)
# 提取高亮像素的掩码
mask = brightness >= threshold
# 计算高亮像素的 RGB 平均值
avg = np.mean(img[mask], axis=0)
# 增益系数:让高亮像素变为纯白 (255, 255, 255)
gain = 255.0 / avg
corrected = (img * gain).clip(0, 255).astype(np.uint8)
return corrected
3.5 两大假设的对比与选择
这两个假设各有各的脾气。我平时做项目,会根据场景来选:
| 对比维度 | 灰度世界假设 | 完美反射假设 |
|---|---|---|
| 适用场景 | 色彩丰富、纹理复杂的自然图像 | 存在高光、镜面反射或白色物体的场景 |
| 失效场景 | 大色块单一场景(如蓝天、绿草地) | 最亮点是彩色光源(如霓虹灯、彩色灯光) |
| 计算复杂度 | 低,只需一次全局统计 | 中等,需要排序或阈值查找 |
| 鲁棒性 | 对噪声敏感度低 | 对最亮点的选择敏感 |
你想想看,如果一张图既有丰富的色彩,又有明显的白色高光,那这两个假设可以结合起来用。我个人的习惯是:先用灰度世界假设做一次粗校正,再用完美反射假设做一次细调。效果往往比单独用任何一个都好。
3.6 本章知识体系
下面这张图把本章的核心逻辑串起来了,你可以对照着梳理一下思路:
嗯,到这里,白平衡问题的定义和两个最基础的假设就讲完了。灰度世界假设和完美反射假设虽然简单,但它们是所有高级白平衡算法的基石。后面的章节里,我们会看到它们是如何被改进、被组合、被应用到实际产品中的。