做图像处理这些年,我发现一个问题:很多人一听到"ISP-AWB",第一反应是"白平衡不就是调色温嘛,有什么好讲的"。说这话的人,多半没在嵌入式平台上把白平衡真正调稳过。给一张偏蓝的照片加上红色通道增益,看起来确实简单,但要让它在白天、夜晚、室内、室外、暖光灯、混合光源下都能稳定输出不偏色,牵扯到的东西远比想象中多。这篇文章就从一个实战过的ISP工程师视角,把AWB从算法到工程实话实说地拆一遍,适合做摄像头模组、FPGA图像采集、IPC/车载方案,或者单纯想搞懂ISP pipeline的人看看。
我先交代一下背景:我最早接触AWB是在一颗老掉牙的CMOS sensor上,跑的是自研的ISP pipeline,后面又在FPGA上实现过整个3A链路。踩过最多的坑,不是算法跑不通,而是算法跑通了,画面上墙就是黄、就是绿、就是莫名奇妙肤色发灰。后来才慢慢明白,AWB不是一个孤立的模块,它跟AE、gamma、CCM、去马赛克都是串在一根绳上的。你要把它做好,需要的不只是一套灰度世界公式,而是对整个ISP流程的理解。
接下来我尽量把这篇写成一份能直接拿去用的实战笔记。我不会堆公式,也不会贴一堆论文推断,只讲我实际验证过、能在硬件上跑起来的东西。
1. 先搞清楚AWB在ISP pipeline里到底在哪一步、管什么
1.1 为什么一张白纸在不同光线下会变成"黄纸"和"蓝纸"
想理解AWB,必须先理解一个物理事实:人眼和CMOS sensor对光源的响应是不同的。人类大脑会自动削弱环境光的影响,你站在白炽灯下看一张白纸,它仍然是白的;但sensor不这么干,它老老实实地把光源的光谱反射记录下来,白炽灯的偏橙光谱打在纸面上,拍出来就是一张黄纸。
这个现象在色彩学里叫"色貌恒常性"。人眼天生自带一套白平衡,而sensor没有,所以必须在ISP里通过算法补偿。AWB干的活用一句话说清楚就是:估计当前光源的色温,推算出场景中本来应该是中性灰/白色的点,然后给三个通道分配不同的增益,把色偏拉回来。
在ISP pipeline中,AWB的位置通常是在去马赛克(demosaic)之后、色彩校正矩阵(CCM)之前。为什么放在这?因为demosaic之后才有完整的RGB三通道信息,可以计算颜色比值;放在CCM之前,是因为AWB补偿的是光源引起的通道增益差,CCM补偿的是光学与sensor的串扰,两者线性叠加,先做AWB再做CCM,顺序不能反。
有些实现会放在RAW域直接用R、Gr、Gb、B四个通道做白平衡,好处是省掉demosaic的运算量,坏处是统计信息不如RGB域准确。FPGA上我见过两种都有的情况,多数低成本方案会选择在RAW域做,因为AWB的统计宏块可以直接复用Bayer pattern的原始数据,省一组缓存。
1.2 AWB、AE、AF怎么交互,为什么说它是"3A"之一
AWB很少独立工作,图像层面的三个自动——自动曝光(AE)、自动白平衡(AWB)、自动对焦(AF)——合称3A。它们是互锁的:曝光时长变了,信号增益变了,统计到的最亮值、平均亮度、饱和像素分布都会变,而这些恰恰是AWB判断场景的重要输入。
举个例子:AE目标亮度调高了,整个画面变亮,原本接近灰的暗部噪点会被放大,AWB统计时容易误判大面积暗部为灰色;反过来,AE欠曝时高光被削掉,本来用来定位白点的像素找不到了,色温估计就会飘。所以做AWB调参时,手边必须有一套AE曲线和AWB联动。很多团队画蛇添足地想先单独把AWB调到完美,结果一接上AE就全乱,就是这个原因。
AWB和AE还有个更直接的耦合点:帧曝光时间和增益变化最快的时候,AWB统计值会出现短时间的异常波动,必须在统计端做帧间滤波。我后面会在常见问题里详细聊这个"闪烁"现象。
2. 核心算法与工程方案选型,不要只会抄灰度世界
2.1 灰度世界法为什么是"基本盘",又为什么在纯色场景翻车
AWB最经典的算法是灰度世界法(Gray World):假设一幅场景中所有颜色的平均反射率是灰色,即R、G、B三通道的均值应该相等。用公式表达就是:R_gain = G_avg / R_avg,B_gain = G_avg / B_avg。它的实现极其简单,在FPGA里就是几个累加器加除法器的事,所以在低端IPC方案里非常常见。
但它有个致命假设:场景里的色彩要足够丰富。如果你画面里是大片的绿草地、蓝墙、红布,灰度世界假设直接失效。举个我实际遇到过的案例:一个室内监控场景,画面中央是一整面办公绿植墙,灰度世界法算出来的色温偏得离谱,整幅画面发品红。为什么?绿色物体在R通道均值偏低,G通道均值偏高,算出来R_gain被拉大,结果把白墙也染红了。
针对这种问题,工程上有几种补丁:一是降低统计权重,只取图像中饱和度较低、亮度中等的那部分像素参与均值计算,相当于"我只相信灰一点的点";二是跟下面的白点法做融合,按场景置信度切换。真正的项目里基本不会只用纯灰度世界。
2.2 白点法:找画面里"本来就应该是白色"的点
白点法(Perfect Reflector / White Patch)的思路是:把画面中最亮的那些像素当作白色参考。基于一个假设:场景中总存在一个接近镜面反射或白板的高亮区域,它的真实颜色就该是白的,那边的大比例色彩偏差就是光源造成的。
实际工程里,白点法不会傻到直接找全图最亮点,那会找到灯泡或者太阳,所以必须加约束条件。我自己的FPGA实现里,白点筛选的条件是:亮度大于某个阈值但没到饱和(例如Y在200~245之间,8bit量化),饱和度(RG差值和BG差值)低于某个阈值,且像素占比超过一定比例。然后对符合条件区域的R/G和B/G做加权平均,得到一个色温估计。
白点法适合高光场景、户外晴天、有高反射面的情况,和灰度世界正好互补。很多商用ISP的AWB都是这两者的加权混合,再加一个色温查表来兜底。
2.3 色温曲线法:跟物理模型对表,比纯统计更抗干扰
还有一个工程上非常扎实的方向叫"色温曲线法":先在实验室用标准光源(D65、A光源、CWF、TL84等)拍灰卡,记录不同色温下R/G和B/G在统计空间里的分布,拟合成一条"色温-色比"曲线。运行的时候,把当前统计到的R/G、B/G值映射到这条曲线上,就能反推当前色温,然后直接查表获得增益。
这条路的好处是物理意义明确、场景鲁棒性强,而且可以针对你的sensor做定制标定——同一个白炽灯,A sensor和B sensor拍出来的色比根本不一样,纯理论模型通用性差。坏处是需要实验室配合,产线上要跑标定流程。我在FPGA项目里采用的是曲线法做最终兜底,白点法和灰度世界法做短期估计,三者投票后选置信度最高的结果。
2.4 在FPGA上实现AWB有哪些硬约束
说完算法思路,聊一下落地的约束。FPGA和PC仿真最大的区别是:FPGA里除法器和缓存都是稀缺资源,流水线的每一级延迟都要算。
- 统计端:一般做法是把图像切成16x16或32x32的宏块,每个宏块单独统计R/G/B之和与像素数,然后按宏块汇总到二值化直方图。宏块太大则空间分辨率低,纯色物体会盖过一切;宏块太小则统计噪声大,而且DDR带宽受不了。
- 定点化:AWB增益一般用12bit定点表示,G通道增益固定为1.0或者512/1024,R/B增益围绕1.0上下浮动。用整数乘法加移位,避免浮点除法。除法只用在统计归一化的最终阶段。
- 帧级处理 vs 像素级处理:AWB不需要逐像素实时处理,它只需要一帧统计值。所以FPGA架构通常是"采样统计单元"和"增益应用单元"分离的。增益应用单元是逐像素乘一个固定值,这个值每帧更新一次;统计单元则跑在全分辨率或降采样后的低频通路。
FPGA实现里还有一个细节:AWB统计通常放在demosaic之后、RGB转YUV之前。如果放在demosaic之前,Bayer pattern上的R/G/B位置不齐,统计时需要对插值出来的伪像素做处理,增加复杂度;放在demosaic之后,就要等demosaic完成整行才能统计,延迟会增加,需要权衡。
3. 实操落地:从初始化到跑通一版稳定AWB的完整过程
3.1 初始化阶段:给AWB一个"不会说胡话"的初值
AWB要能快速稳定,但也不能在开机第一帧就乱跳。我的做法是:初始化阶段不给AWB全自动权重,先锁定一个保守色温值。比如Sensor默认在室内场景时,色温初始化到4500K~5000K,增益初值R=1.0、B=1.0,等AE稳定后AWB才参与调节。否则开机瞬间画面忽蓝忽黄,非常劝退。
如果你做的是IPC或车载DVR,还要考虑不同sensor上电时序。有些sensor冷启动时Bayer pattern输出会先闪几帧,统计值完全不可信。这个阶段如果AWB开启了,会让增益限幅器疯狂上下跳动,后续恢复会很慢。所以工程上必须有个"帧数安全期",例如前10帧只统计不调整。
3.2 统计模块:如何科学地选点、分区、定权重
统计是整个AWB的地基,统计做错,后面所有算法都是空中楼阁。我常用的统计策略分三层:
- 像素级筛选:先排除过暗(Y<16)、过曝(Y>240)和饱和度过高的像素。过暗的像素噪点大,色比浮动剧烈;过曝像素三通道被削顶,色比失真;高饱和像素会让灰度世界假设失效。
- 区域级加权:图像中心区域给予更高权重,边缘区域尤其是四角,因为镜头暗角(lens shading)的影响,即使做了校正,色比也会偏移。直接不加权会导致画面中心颜色正确、四周发青的经典问题。
- 时间级滤波:统计得到的R/G、B/G值不要直接用于增益计算,先做一阶IIR滤波,例如new_value = (3 * old + 1 * new) >> 2的权重。帧间色温突变平滑掉,防止画面闪烁。
还有一个我踩过的坑:统计宏块如果只放在有效像素区域内,一旦实际画面尺寸小于ISP输入尺寸(比如视频裁剪后),统计区会扫过黑色无效区域,导致亮度均值被拉低,AWB误判为低色温。这个在调试时一定要先输出统计Region的可视化结果看一眼,不要直接信曲线。
3.3 增益计算:为什么G通道不动,R和B怎么配
AWB增益计算的核心原则是:保持G通道增益固定,调整R和B,因为Bayer阵列中G像素数量是R和B的两倍,人眼对绿色最敏感,固定G能最大程度保持亮度稳定。
计算方法:假设你估计出当前色温下的标准白点R_target/G_target = 1.15,B_target/G_target = 0.85,而统计到的当前值R_cur/G_cur = 0.9,B_cur/G_cur = 1.2,则R_gain = (R_target/G_target)/(R_cur/G_cur) = 1.28,B_gain = 0.71。最终像素值R_out = R_in * R_gain,G_out = G_in,B_out = B_in * B_gain。
增益限幅也很重要。一般R_gain、B_gain限制在0.5~2.0之间,防止在极端光源下增益过大导致噪点爆炸。实际在暗光场景下,B通道增益拉到1.8+时,蓝色通道噪声会有很明显的放大,画面会偏紫或者全是彩噪。所以AWB的增益上限要和ISP的降噪模块耦合做。
3.4 从实验室标定到现场联调:一个可复用的调参流程
我自己的调参流程分五步,每一步都有明确的输出物:
- 灰卡标定:用标准灯箱(D65、A光源、TL84、CWF、Horizon)分别拍灰卡,记录每个色温下统计到的R/G、B/G值,拟合出这条sensor对应的"色温-色比"曲线,作为后面映射表的底表。
- 真实场景采样:在早中晚、晴天阴天、室内白炽灯/荧光灯/混合光下分别录一段raw流,离线回放统计值,确认统计模块输出的R/G、B/G能落在曲线合理范围内。
- 增益映射表生成:根据步骤1的曲线,生成一张色温到R_gain/B_gain的LUT,插值间隔建议不大于100K,超过则跳变会肉眼可见。
- FPGA联调:上板后用在线调节工具逐步切换灯箱色温,观察画面中灰阶是否保持中性,同时输出统计值和LUT索引做日志比对。
- 边界场景回归:跑一遍"黑暗房间突然开灯""逆光窗户""显示屏前自拍"这些容易让AWB崩溃的场景,确认时间滤波和模式切换是否正常工作。
这个流程看起来繁琐,但能一次性解决80%的后端问题。很多团队跳过第2步直接上第3步,结果就是灯箱里Perfect,一到实景就翻车,因为真实光源永远比灯箱复杂。
4. 常见问题与排查技巧实录:那些教科书上不讲的坑
4.1 画面偏色半天找不出原因,可能是统计区被挡了
现场反馈最多的一类bug:明明AWB算法看起来没问题,代码也没改动,但画面偏绿。优先怀疑对象不是增益计算,而是统计区。我遇到过几次,都是因为代码里一个rounding截断导致宏块坐标偏移了半个块,把一条原本黑色边框的像素算进了白色区域。
排查方法很老土但有效:把统计Region画在预览图上,逐帧去看它到底框住了什么内容。如果你发现统计区边缘压到了暗角或者画面外的无效数据,那偏色是必然的。另一个容易被忽视的是竖屏/横屏切换时宽高互换,统计宏块坐标没跟着旋转,这个在IPC常见。
4.2 场景切换时AWB剧烈闪烁,时间滤波系数怎么选
"从室内走到阳光下画面狂闪好几秒"是另一个高频问题。根源是帧级统计发生了突变,增益直接跳到位,导致肉眼可见的色温跳变。
解决方法是给AWB增益加时间平滑。我常用的系数是:正常场景alpha=0.5~0.7(帧间增益变化柔和);检测到场景突变时,强制alpha跳到1.0,直接响应。检测突变的依据可以看统计值帧间差,比如|R/G差值| > 0.15认为场景切换。这个思路本质上是"平时尽量稳,突变不拖沓",比全程固定一个系数要好用。
4.3 混合光源下肤色发灰、发紫,怎么取舍
以前的一个安防项目,使用现场是大厅顶灯(荧光灯)加窗外阳光,拍人脸时肤色经常发灰。为什么?因为两个光源同时作用——人脸一半受日光(色温约6000K)照射,一半受荧光灯(色温约4000K)照射,AWB只能选一个全局增益。事后不管偏向哪边,另一侧都偏。
这种场景的工程处理,一是结合人脸检测区域做加权统计(人脸肤色本身就是先验),二是在增益计算时给R通道增益设置一个下限,避免肤色被拉灰。三是坚定接受物理限制——全局AWB无解于混合光源,该上局部AWB(分区域增益)时才上,别硬扛。成本敏感项目里,我会选择牺牲次要区域,优先保画面中央的人脸。
4.4 极端光源和算法逃生门:当AWB干什么都不对
最后一个经验是给系统留一个逃生门。当统计值连续多帧处于色温映射表的边界之外(比如超低色温暖光灯下R/G大得离谱),或置信度低于阈值时,AWB应当自动切换到预设的"最安全色温"并锁住增益,而不是继续发疯。这个机制在灯串、蜡烛、彩色霓虹灯这种"非标准光源"下尤其重要——那根本不是色温能描述的。
另外,AWB和AE联动出现"呼吸效应"时,优先查AE是否在感光度切换阈值上反复跳变。曝光参数抖动会导致统计像素亮度分布变化,AWB跟着抖,看起来像AWB的bug,实际上是AE的。
后记:关于SNN和AI做AWB的一点个人看法
最近行业内确实有讨论用神经网络或SNN(脉冲神经网络)做AWB的,比如直接用统计特征预测色温增益,甚至端到端学习RGB到目标色温的映射。我个人认为这是个有意思的方向,尤其是在大算力的移动端SoC上已经能看到部分落地。但在中低端IPC和FPGA平台,算力和DDR带宽都不支持跑一个完整CNN,SNN的工程化程度又较低,短期内主流的自研方案依然是"统计筛选+加权估计+色温LUT",最多加一个人脸/场景标签模块。
如果对新方向感兴趣,建议从轻量级MLP预测色温开始试,输入是已有的色比直方图特征,输出是增益,先离线训练再量化部署,比直接上SNN要稳得多。但要说替换传统AWB,我持保留态度,毕竟AWB本质是一个物理光源估计问题,统计先验远比黑盒预测可控。
最后分享一个我自己的实战习惯:每次改AWB参数前,先截一段raw流存档。改完再截一段,离线逐帧回放对比。等你被现场"看起来增加了饱和度但偏色更厉害"折磨过几轮之后,就会明白这个存档习惯有多救命。AWB这东西,急不来,慢工出细活。