3. 自动曝光算法(AE)原理

自动曝光,说白了就是让摄像头自己找到那个「不亮不暗刚刚好」的曝光值。我刚开始做手机摄像头调试那会儿,总觉得AE不就是调调曝光时间嘛,后来被各种场景打脸——逆光、夜景、高动态……嗯,从那以后我再也不敢小看AE了。

这一章我们拆开来看:AE系统怎么搭的?测光模式怎么选?目标亮度定多少?收敛策略怎么走?

核心一句话:AE的本质是一个闭环控制系统,输入是当前帧的亮度统计,输出是下一帧的曝光参数。

3.1 AE系统架构

先看整体框架。我习惯把AE系统分成四个模块:统计模块、分析模块、决策模块、执行模块。

AE系统架构流程图 统计模块 直方图 / 分区亮度 分析模块 测光 / 目标比对 决策模块 收敛策略 / 步长 执行模块 曝光时间 / 增益 / 光圈 反馈(下一帧) 闭环控制:每帧采集 → 统计 → 分析 → 决策 → 执行 → 下一帧采集 收敛目标:当前亮度与目标亮度的差值趋近于0

流程其实不复杂:Sensor 采集到原始图像后,统计模块算出亮度分布;分析模块根据测光模式加权得到当前亮度;决策模块拿当前亮度和目标亮度做差,算出下一步要调多少;执行模块把曝光时间、增益、光圈写回 sensor。

我的习惯:统计模块尽量在硬件层面做,比如 ISP 的 3A 统计引擎。纯软件统计太慢,帧率一高就扛不住。

3.2 测光模式

测光模式决定了「当前亮度怎么算」。同一个场景,不同测光模式算出来的亮度可能差很远。我遇到过好几次,用户说「画面忽亮忽暗」,查到最后是测光模式选错了。

测光模式 权重分布 适用场景 我踩过的坑
平均测光 全画面等权重 风景、均匀光照 逆光时主体过暗
中央重点测光 中心区域权重高(60-80%) 人像、中心构图 主体偏离中心时失效
点测光 极小区域(1-5%) 舞台、逆光特写 测光点选错直接过曝

3.2.1 平均测光

最简单粗暴的方式。把画面分成 M×N 个块,每个块算平均亮度,然后全部加起来取平均。公式长这样:

L_avg = (1 / (M * N)) * Σ Σ L(i, j)

好处是稳定,坏处是容易被大面积背景带偏。比如拍逆光人像,背景天空很亮,平均下来主体就黑了。

3.2.2 中央重点测光

这个模式给画面中心区域更高的权重。我一般用高斯权重或者阶梯权重:

L_weighted = Σ Σ w(i, j) * L(i, j) / Σ Σ w(i, j)

// 中心区域权重设为1.0,边缘设为0.3
// 过渡区域线性插值

人像场景我首选这个模式。但要注意——如果主体不在中心,效果还不如平均测光。

3.2.3 点测光

点测光只取画面中一个极小区域(通常是对焦点附近)。我做过一个舞台项目,演员追光跟着走,点测光跟着对焦点移动,效果非常好。

曾经踩过的坑:点测光区域太小,如果测光点落在高光或阴影上,画面会剧烈跳动。我后来加了一个「测光点邻域平滑」,取周围 3×3 块的平均值,稳定性好多了。

3.3 目标亮度设定

目标亮度就是「我们希望画面达到多亮」。这个值不是拍脑袋定的,我一般从三个维度考虑:

  • 场景类型:室内、室外、夜景、逆光,目标亮度不同
  • 用户偏好:有人喜欢亮一点,有人喜欢暗一点
  • 传感器特性:不同 sensor 的线性区、信噪比拐点不一样

常用的目标亮度表达方式有两种:

方式 说明 典型值
8-bit 亮度值 0-255 范围内的目标值 120-140(中等亮度)
直方图百分比 目标平均亮度占满量程的百分比 50%-60%
我的经验:目标亮度不要固定死。我习惯做一个「场景自适应目标亮度」——检测到人脸时目标亮度提高 10%,检测到夜景时降低 15%。这样用户体验会好很多。

3.4 AE收敛策略

收敛策略解决的是「怎么从当前亮度走到目标亮度」。走快了会 overshoot,走慢了会拖影。我把它分成三个子问题:

3.4.1 误差计算

error = L_target - L_current

// 归一化误差
error_norm = error / L_target

误差的正负决定方向,大小决定步长。

3.4.2 步长控制

步长控制我常用两种策略:

  • 固定步长:简单但容易震荡。误差大时调得慢,误差小时又可能 overshoot。
  • 自适应步长:误差大时大步快调,误差小时微调。我一般用分段线性函数:
if abs(error_norm) > 0.3:
    step = 0.5   // 大步
elif abs(error_norm) > 0.1:
    step = 0.2   // 中步
else:
    step = 0.05  // 微调

3.4.3 防抖与滞回

这是最容易忽略的点。我遇到过画面在亮暗之间来回跳,就是因为没有加滞回。

避坑指南:加一个「死区」——当误差小于某个阈值(比如 3%),就不做调整。再加一个「滞回窗口」——调整方向改变时,延迟几帧再执行。这两个小技巧能解决 90% 的闪烁问题。

3.4.4 曝光参数映射

最后一步是把步长换算成具体的曝光参数。我一般按这个优先级调整:

  1. 先调曝光时间:不引入噪声,但注意运动模糊
  2. 再调模拟增益:噪声可控范围内
  3. 最后调数字增益:会放大噪声,尽量少用
// 伪代码示例
if need_brighter:
    if exposure_time < max_exposure:
        exposure_time *= (1 + step)
    elif analog_gain < max_analog_gain:
        analog_gain *= (1 + step)
    else:
        digital_gain *= (1 + step)
曾经踩过的坑:曝光时间调得太快,画面会出现明显的闪烁感。我后来加了一个「帧间平滑」——每次调整不超过前一帧的 20%,人眼就察觉不到了。