3. 灰度世界法:最朴素的白平衡估计
灰度世界法(Gray World Assumption)是我入行时学的第一个自动白平衡算法。说实话,它简单到让人怀疑——就这?但你别小看它,很多高端 ISP 的预处理环节里,灰度世界法依然在默默干活。
它的核心假设就一句话:一张色彩丰富的自然图像,所有像素的 R、G、B 均值应该趋于相等。说白了,就是认为世界平均是灰色的。如果拍出来的画面偏色,那一定是光源在捣鬼。
3.1 算法原理
我们先把问题拆开看。假设传感器捕获的 RAW 数据是 I_raw,经过黑电平校正和线性化之后,每个像素的 R、G、B 值分别记为 R(x,y)、G(x,y)、B(x,y)。
灰度世界法认为:
- 计算全图 R 通道均值:
R_avg = mean(R) - 计算全图 G 通道均值:
G_avg = mean(G) - 计算全图 B 通道均值:
B_avg = mean(B)
然后我们假设 G 通道是参考通道(因为人眼对绿色最敏感,ISP 里也常用 G 做基准),那么 R 和 B 的增益就是:
gain_R = G_avg / R_avg
gain_B = G_avg / B_avg
最后把增益乘回去:
R_corrected = R * gain_R
B_corrected = B * gain_B
G_corrected = G (保持不变)
嗯,就是这么直白。你想想看,如果画面偏红,那 R_avg 就会偏大,gain_R 就会小于 1,红色被压下去,画面就正了。
核心要点:灰度世界法本质是在做通道均值对齐。它不关心场景里到底有什么物体,只关心统计结果。
3.2 代码实现
我习惯用 Python + NumPy 来快速验证算法。下面这个函数接受一个 RAW 格式的 numpy 数组(假设已经是线性数据),返回校正后的图像和增益值。
import numpy as np
def gray_world_awb(raw_img):
"""
灰度世界法自动白平衡
:param raw_img: 形状为 (H, W, 3) 的 numpy 数组,通道顺序为 R, G, B
:return: corrected_img, (gain_R, gain_G, gain_B)
"""
# 分离通道
R = raw_img[:, :, 0].astype(np.float64)
G = raw_img[:, :, 1].astype(np.float64)
B = raw_img[:, :, 2].astype(np.float64)
# 计算各通道均值
R_avg = np.mean(R)
G_avg = np.mean(G)
B_avg = np.mean(B)
# 防止除零
if R_avg == 0 or G_avg == 0 or B_avg == 0:
print("警告:存在通道均值为零,跳过校正")
return raw_img, (1.0, 1.0, 1.0)
# 计算增益(以 G 通道为参考)
gain_R = G_avg / R_avg
gain_B = G_avg / B_avg
gain_G = 1.0
# 应用增益
R_corrected = R * gain_R
G_corrected = G * gain_G
B_corrected = B * gain_B
# 合并通道
corrected_img = np.stack([R_corrected, G_corrected, B_corrected], axis=2)
# 裁剪到有效范围(假设输入是 12-bit RAW,范围 0-4095)
corrected_img = np.clip(corrected_img, 0, 4095)
return corrected_img.astype(raw_img.dtype), (gain_R, gain_G, gain_B)
我的小习惯:实际项目中我会先做一次坏点校正和黑电平减除,再跑灰度世界法。否则暗电流和坏点会严重拉偏均值,增益算出来全是错的。
3.3 灰度世界法的 SVG 流程图
下面这张图把整个流程串起来了。你可以看到,从 RAW 输入到增益输出,中间只有三个步骤——均值计算、增益推导、像素校正。
3.4 优缺点分析
灰度世界法不是万能的。我在项目中踩过不少坑,下面把它的优缺点摊开说。
| 维度 | 优点 | 缺点 |
|---|---|---|
| 计算量 | 极低。只需要一次全图遍历求均值,O(N) 复杂度 | 无 |
| 实现难度 | 简单到离谱。20 行代码搞定 | 无 |
| 适用场景 | 色彩丰富的自然场景、室内混合光源 | 大面积单色场景(蓝天、绿草地、红墙)会严重误判 |
| 鲁棒性 | 对噪声有一定容忍度(均值本身抗噪) | 对暗电流和坏点敏感,需要预处理 |
| 色温范围 | D50~D65 标准光源下表现不错 | 极端色温(低色温烛光、高色温阴天)偏差大 |
我曾经踩过的坑:有一次在户外拍蓝天白云,灰度世界法把整个画面拉成了黄褐色。为什么?因为天空占了画面 70%,B 通道均值远高于 R 和 G,算法以为光源偏蓝,拼命加红加绿,结果蓝天变黄天。从那以后,我学会了在跑灰度世界法之前先做天空检测或场景分类。
3.5 改进思路
既然灰度世界法有硬伤,那怎么救?我分享几个实战中常用的改进方案:
- 剔除极端像素:计算均值前,去掉最亮和最暗的 5% 像素。这样可以避免高光溢出和暗电流的干扰。
- 分块灰度世界:把图像分成 4x4 或 8x8 的块,每块独立计算增益,最后用插值平滑。适合光源不均匀的场景。
- 结合色温先验:如果已知光源色温范围(比如室内灯光 2700K~4000K),可以把增益限制在合理区间内。
- 加权灰度世界:给饱和度低的像素更高权重,因为灰色像素更符合“世界是灰色”的假设。
我的建议:灰度世界法最适合做粗校正。在 ISP 流水线里,我通常把它放在最前面,先把大方向的偏色调回来,后面再用更精细的算法(比如色温估计 + 查找表)做微调。这样既快又稳。
3.6 小结
灰度世界法就像一把瑞士军刀——不锋利,但什么场合都能掏出来用一下。它的价值不在于精度,而在于速度和稳定性。你想想看,在嵌入式平台上,每毫秒都很珍贵,一个 O(N) 的算法能搞定 80% 的场景,剩下的 20% 再上更复杂的方案,这才是工程思维。