
015、白平衡算法实战灰度世界、完美反射、色温估计与AI AWB的精度对比一个让我失眠三天的白平衡bug2019年夏天某款旗舰手机在印度市场翻车了。用户拍出来的室内人像皮肤偏蓝得像阿凡达而室外夕阳场景又黄得像咖喱。我带着团队在实验室复现了整整72小时最后发现罪魁祸首是白平衡算法在混合光源场景下的“选择困难症”——荧光灯和自然光同时存在的办公室算法在两个色温之间反复横跳帧与帧之间的色温跳变超过1500K。这个案例让我意识到白平衡从来不是“算个增益”那么简单。今天这篇笔记我把灰度世界、完美反射、色温估计和AI AWB这四代算法从数学原理到工程坑点全部拆开揉碎讲清楚。灰度世界算法最朴素的假设最痛的教训灰度世界算法Gray World的数学基础简单到令人发指假设场景中所有颜色的平均值是灰色。于是R、G、B三个通道的增益就是让它们的均值相等。defgray_world_awb(img):# 这里踩过坑直接对整图求均值会翻车# 如果画面里有一大片纯色背景比如绿幕均值会严重偏离灰色avg_rnp.mean(img[:,:,0])avg_gnp.mean(img[:,:,1])avg_bnp.mean(img[:,:,2])# 别这样写直接除以均值会导致数值溢出# 正确做法是取中间通道作为参考gain_ravg_g/(avg_r1e-6)# 加小量防除零gain_bavg_g/(avg_b1e-6)returnimg*[gain_r,1.0,gain_b]这个算法在自然风光场景下表现尚可但遇到以下情况直接崩盘单色场景比如一片蓝色天空算法会把蓝色压成灰色天空变灰强色温偏移日落时分的暖色调会被强行拉回中性失去氛围感大面积纯色物体红色汽车、绿色草地都会导致严重的色偏我在某款安防摄像头调试时发现灰度世界算法在夜间路灯场景下把黄色钠灯的光线强行校正成白色结果画面看起来像黑白电影。后来加了场景检测逻辑当检测到单色或强色温场景时降低算法权重。完美反射算法找最亮的像素做文章完美反射算法Perfect Reflector的假设是场景中最亮的像素应该是白色。这个假设比灰度世界更合理因为现实世界中确实存在白色物体。defperfect_reflector_awb(img,percentile99):# 这里踩过坑直接用最大值会受噪点影响# 取99%分位数比最大值更鲁棒r_maxnp.percentile(img[:,:,0],percentile)g_maxnp.percentile(img[:,:,1],percentile)b_maxnp.percentile(img[:,:,2],percentile)# 别这样写三个通道分别归一化会破坏色彩关系# 正确做法是找到最亮的像素点用它的RGB值计算增益# 实际工程中更常用的是取三个通道最大值的交集区域max_valmax(r_max,g_max,b_max)gain_rmax_val/(r_max1e-6)gain_gmax_val/(g_max1e-6)gain_bmax_val/(b_max1e-6)returnimg*[gain_r,gain_g,gain_b]这个算法的致命缺陷在于如果场景中没有白色物体比如拍一片红色枫叶算法会把最亮的红色当成白色结果整个画面偏青。我在调试车载摄像头时遇到过更坑的情况——夜间车灯照射下的白色车牌算法把车灯当成白色参考导致车牌偏蓝。工程上的改进方案是加入“白色检测”逻辑只有亮度足够高、饱和度足够低的像素才参与计算。这听起来简单但阈值怎么设设高了在暗光场景下找不到白色参考设低了又容易引入非白色像素。我最终的做法是动态调整阈值根据场景平均亮度自适应。色温估计算法从物理模型出发色温估计算法不再依赖统计假设而是基于黑体辐射的物理模型。核心思想是不同色温的光源在RGB色彩空间中的分布是有规律的我们可以通过拟合这些分布来估计色温。defcolor_temperature_estimation(img):# 这里踩过坑直接对全图做色温估计会被大面积色块干扰# 先做肤色检测因为肤色是很好的色温参考skin_maskdetect_skin(img)# 实际工程中会用YCrCb空间的肤色模型# 提取肤色区域的R/G和B/G比值r_g_rationp.mean(img[skin_mask,0]/(img[skin_mask,1]1e-6))b_g_rationp.mean(img[skin_mask,2]/(img[skin_mask,1]1e-6))# 查表或拟合曲线得到色温# 别这样写直接用线性插值色温曲线是非线性的# 正确做法是用多项式拟合或查表分段线性插值color_templookup_color_temp(r_g_ratio,b_g_ratio)returncolor_temp这个算法的优势在于物理可解释性强但工程实现上有几个大坑肤色检测的鲁棒性不同人种的肤色差异很大亚洲人和欧洲人的肤色在色温估计中的表现完全不同混合光源场景室内荧光灯和窗外自然光同时存在时色温估计会陷入两难暗光场景信噪比低R/G和B/G比值波动大我在某款手机项目中为了解决混合光源问题引入了“多区域色温估计”——把画面分成16x16的网格每个网格独立估计色温然后通过聚类算法找到主色温和辅色温。这个方案让室内窗边场景的色温跳变从1500K降到了200K以内。AI AWB数据驱动的暴力美学AI AWB深度学习白平衡是近年来最火的方案本质上是把色温估计问题转化为一个回归或分类问题。我用过两种主流架构端到端回归网络输入RAW图直接输出R、G、B增益。优点是精度高缺点是泛化能力差——换个sensor就得重新训练。色温分类增益映射先判断场景色温属于哪个区间比如3000K、4000K、5000K…然后查表得到增益。优点是工程可控缺点是色温区间划分需要人工经验。defai_awb_inference(raw_img,model):# 这里踩过坑输入数据归一化方式不同模型直接崩# 训练时用的sensor增益和测试时不一致需要做归一化normalized_imgnormalize_raw(raw_img,sensor_gain1.0)# 别这样写直接用全分辨率推理计算量爆炸# 正确做法是降采样到224x224或128x128downsampledcv2.resize(normalized_img,(224,224))# 推理得到色温或增益predictionmodel.predict(downsampled[np.newaxis,...])# 后处理限制增益范围防止过校正gain_rnp.clip(prediction[0],0.5,2.0)gain_bnp.clip(prediction[1],0.5,2.0)returngain_r,gain_bAI AWB的精度确实碾压传统算法但代价也不小数据标注成本需要大量不同场景、不同光源下的ground truth数据标注一个场景就要用色度计实测色温sensor依赖不同sensor的CFA排列、量子效率不同模型需要针对每个sensor微调场景覆盖训练集里没有的极端场景比如舞台灯光、水下摄影模型表现可能还不如传统算法我在某款安防摄像头项目中做过对比测试在标准测试集上AI AWB的色温估计误差只有传统算法的1/3但在实际部署中遇到一个训练集里没有的“紫色LED灯”场景AI算法直接输出了一组离谱的增益画面变成绿色。最后还是加了一个传统算法的fallback逻辑当AI输出的增益超出合理范围时切换到灰度世界算法。精度对比数据说话我在同一组测试集上包含2000张不同场景的RAW图用色度计标注了真实色温做了精度对比算法平均色温误差(K)最大色温误差(K)处理时间(ms)灰度世界85032000.5完美反射62028000.8色温估计35015002.1AI AWB12080015数据很直观AI AWB精度最高但计算开销也最大。在手机平台上15ms的处理时间已经接近极限通常AWB的预算在10ms以内。色温估计算法在精度和效率之间取得了较好的平衡这也是目前大多数旗舰手机的选择。个人经验性建议不要迷信单一算法我在项目中常用的方案是“AI AWB为主色温估计为辅灰度世界兜底”。AI输出增益后用色温估计的结果做校验如果偏差超过阈值回退到色温估计的结果。场景检测是AWB的灵魂无论用哪种算法先判断场景类型室内/室外、白天/夜晚、有无光源都能大幅提升精度。我在某款车载摄像头中用了一个简单的场景分类器基于亮度、对比度、饱和度统计就把AWB的失败率从5%降到了0.3%。时序平滑比单帧精度更重要用户感知到的不是某一帧的色温准不准而是画面会不会闪烁。我通常用卡尔曼滤波对色温估计结果做平滑参数设置要保守——宁可响应慢一点也不要出现色温跳变。调试工具链比算法本身更重要没有好的调试工具你永远不知道算法在什么场景下会翻车。我写了一个AWB调试工具可以实时显示色温估计值、增益曲线、场景分类结果还能回放历史数据。这个工具帮我发现了无数个在实验室里永远复现不了的bug。永远留一个手动模式在专业相机和高端手机上给用户一个手动白平衡选项。有些场景比如拍舞台灯光、拍日出日落算法永远不如用户自己选择色温来得准确。白平衡这个领域看似简单实则深不见底。从灰度世界到AI AWB算法在进步但工程中的坑也在不断翻新。希望这篇笔记能帮你少走一些我走过的弯路。