测光模块给出曝光误差后,AE 还不能直接把它全部写进传感器。当前统计通常来自上一帧,新的寄存器命令又可能隔一到数帧才生效;控制过于激进会在目标两侧振荡,控制过慢则会让用户看着画面长时间变亮或变暗。
从静态误差到离散时间控制
若用 表示第 帧的曝光状态,最简单的一阶控制是:
越大,理论收敛越快。但真实统计含噪声,执行路径含延迟,目标本身还可能因 ROI 变化而移动。
曝光为什么不能一步跳到目标
反馈速度越快,亮度响应越及时;但测量噪声、延迟和场景变化会让激进控制产生闪烁。
教学曲线是一阶控制器。真实 AE 还要处理统计延迟、曝光生效延迟、上下行速度不对称和场景切换检测。
量产系统常组合以下策略:
- 目标附近设置死区,忽略不影响观感的小误差;
- 限制每帧最大 EV 步长,防止画面突然跳变;
- 分别设置变亮与变暗速度,例如优先快速保护高光;
- 检测大幅场景变化,临时从稳定模式切换到快速模式;
- 对测光统计做时域滤波,但在镜头切换时重置历史;
- 根据命令生效帧号匹配统计,避免把旧曝光的结果归因于新命令。
若传感器命令延迟为 帧,控制器实际面对的是 的结果。忽略该延迟时,提高 很容易导致过冲和周期性闪烁。调参不能只看静态终值,还要观察完整的逐帧波形。
从误差到可执行命令
把测光篇得到的 EV 误差记为 。死区先消除目标附近的小幅扰动,再用最大步长 限制单帧变化:
上行和下行可使用不同限制,例如突然出现大面积高光时允许更快降低曝光。若命令延迟为 帧,第 帧图像实际由较早的命令产生:
表示场景照度 与传感器执行路径, 表示统计噪声。实现中应为命令附带预计生效帧号,把返回统计与真正生成它的命令对应起来,而不是默认“刚写入就生效”。
人工照明下,理想快门 还要量化到光强周期 的整数倍:
当 时,产品也可能使用与行读出和灯光相位共同验证过的短快门表,而不是强制延长到一个周期。量化后未满足的曝光倍率可由模拟增益补齐:
下面的 NumPy 示例模拟了两帧命令延迟、死区、非对称步长,以及 50 Hz 下的安全快门量化。它打印每一帧的观测与新命令,便于观察闭环而不是只看最终值。
from collections import dequeimport numpy as np
frames = 18target = 0.30scene = np.r_[np.full(5, 0.30), np.full(frames - 5, 0.075)]
delay_frames = 2deadband_ev = 0.06alpha = 0.55max_up_ev, max_down_ev = 0.45, 0.70command_ev = 0.0pipeline = deque([command_ev] * delay_frames)
def controller_step(error_ev): if abs(error_ev) < deadband_ev: return 0.0 error_after_deadband = error_ev - np.sign(error_ev) * deadband_ev return float(np.clip(alpha * error_after_deadband, -max_down_ev, max_up_ev))
22 collapsed lines
def quantize_for_mains(ideal_shutter_s, mains_hz): period = 1.0 / (2.0 * mains_hz) if ideal_shutter_s < period: return ideal_shutter_s # 短快门表在真实产品中需另行标定 return max(1, np.floor(ideal_shutter_s / period)) * period
for n in range(frames): applied_ev = pipeline.popleft() measured = np.clip(scene[n] * 2.0 ** applied_ev, 1e-4, 1.0) error_ev = np.log2(target / measured) command_ev += controller_step(error_ev) command_ev = float(np.clip(command_ev, -4.0, 6.0)) pipeline.append(command_ev)
ideal_shutter = (1.0 / 250.0) * 2.0 ** max(command_ev, 0.0) safe_shutter = quantize_for_mains(ideal_shutter, mains_hz=50.0) residual_gain = ideal_shutter / safe_shutter print( f"{n:02d}: Y={measured:.3f}, error={error_ev:+.2f} EV, " f"cmd={command_ev:+.2f} EV, t={safe_shutter*1e3:.1f} ms, " f"gain×={residual_gain:.2f}" )为保持代码独立可读,示例用 的无饱和线性场景模型,也没有加入传感器寄存器离散值、增益上下界、滚动快门行相位或自动灯频检测。它适合验证控制顺序,不能直接作为量产控制器。
曝光量必须分配给硬件参数
控制器通常先得到期望曝光乘积,再由 exposure program 分配给光圈 、快门时间 和总增益 。简化关系可写为:
手机常使用固定光圈,于是主要在快门与增益之间选择。相同输出亮度并不代表相同画质:长快门积累更多光子但拖长运动轨迹,高增益有助于缩短快门却会放大噪声,并更早消耗高光余量。
快门、增益与防频闪怎样取舍
相同曝光量可以由更长快门或更高增益得到;人工光源下还要把快门吸附到电网半周期。
防频闪常按 50 Hz 的 10 ms 或 60 Hz 的 8.33 ms 半周期约束曝光。LED PWM 可能使用完全不同的频率,需额外检测。
一个常见分配顺序是:亮场先保持低增益并缩短快门;光线变暗后延长快门,直到运动或帧率上限;再提高模拟增益,最后才使用数字增益。实际顺序会根据录像、运动检测、光学防抖、人脸和夜景模式调整。
模拟增益与数字增益也不能简单合并。模拟增益发生在 ADC 前,可能改善量化利用率,但同时放大读出链前部噪声;数字增益发生在采样后,通常不会增加真实光子信噪比。不同传感器还可能在某些增益点切换转换增益模式,使噪声曲线出现台阶。
帧率给快门设置硬上限
30 fps 的帧周期约为 33.3 ms,快门不能无限延长而仍维持同样帧率。曝光还要预留传感器读出与 blanking 时间。低照度下,系统必须在以下选择间明确优先级:
- 保持帧率并提高增益;
- 降低帧率以允许更长曝光;
- 接受较暗画面,让后续降噪与 Tone Mapping 处理;
- 切换到多帧夜景模式,不再维持普通实时预览约束。
录像通常要求帧率稳定,视频会议还重视运动清晰度;静态拍照则可结合陀螺仪和主体运动估计,使用更长曝光换取信噪比。
为什么人工照明会频闪
许多交流灯具的光强随电网周期变化。50 Hz 市电常产生 100 Hz 光强波动,半周期为 10 ms;60 Hz 常产生 120 Hz 波动,半周期约 8.33 ms。若曝光窗口覆盖整数个光强周期,积分能量更稳定:
因此防频闪模式会把理想快门吸附到安全档位,再用增益补齐剩余曝光误差。吸附本身会让曝光参数离散变化,控制器需要用迟滞避免在两个档位间来回跳。
滚动快门逐行开始与结束曝光。同一帧的不同行若采到灯光波形的不同相位,就会形成横向条带;相邻帧采样相位变化,还会产生整体亮度跳动。
50/60 Hz 不是全部答案
LED PWM、调光器、显示屏和车灯可能使用与电网无关的频率,甚至具有复杂脉冲波形。只提供“50 Hz / 60 Hz”开关无法覆盖所有场景。更完整的系统会从逐行统计或多帧亮度序列估计频率与相位,再选择快门或同步策略。
检测也会有混叠:采样帧率不足时,不同实际频率可能表现为相同低频变化。因此防频闪测试需要覆盖真实灯具、不同亮度档位、不同帧率与滚动快门读出时间,不能只用理想正弦波仿真。
一套可验证的状态机
工程实现可将 AE 明确拆成状态:
稳定监视 → 检测场景变化 → 快速搜索→ 接近目标 → 慢速收敛 → 进入死区 ↘ 灯频变化:重新锁定安全快门每次状态切换都应定义进入条件、退出条件、最大停留帧数和历史统计如何处理。这样比堆叠互相覆盖的速度系数更容易复现问题。
验证清单
- 明暗阶跃场景的收敛时间、过冲和稳态波动;
- 曝光命令延迟为 0、1、2 帧时的稳定性;
- 上行与下行速度以及场景切换后的连续性;
- 静止与运动主体下的快门/增益分配曲线;
- 24/25/30/50/60 fps 的最大快门约束;
- 50 Hz、60 Hz 和多种 LED PWM 灯具的条带与亮度波动;
- 防频闪档位切换处是否因迟滞不足而反复跳档;
- HDR 多曝光比例变化时,长短帧是否同时保持稳定。
稳定的 AE 不是追求最快抵达目标,而是在有限帧率、命令延迟、运动、噪声与照明周期之间选择可接受的时间响应。测光决定方向,闭环控制决定过程,曝光分配则决定最终画质成本。