3. AE基础

AE基础:简单平均亮度AE算法 学习卡片

任务目标

实现一个基于平均亮度的自动曝光(AE)算法:输入一帧图像的亮度统计信息(平均亮度),通过迭代调整曝光时间或模拟增益,使图像平均亮度收敛到设定的目标值。本案例聚焦算法核心逻辑,不涉及具体传感器驱动或ISP管线。

核心原理

AE控制本质是一个负反馈闭环。设当前平均亮度为 Y_avg,目标亮度为 Y_target,当前总曝光为 E_current = exposure_time × gain。由于亮度与曝光近似线性关系,可按下式直接计算理想总曝光:

E_target = E_current × (Y_target / Y_avg)

随后将 E_target 按优先级分配给曝光时间和增益:优先调整曝光时间(避免高增益引入噪声),当曝光时间达到上限后,再增加增益。

代码实现

class SimpleAE:
    def __init__(self, target_luma=110, min_gain=1.0, max_gain=16.0,
                 min_exp_time=1.0, max_exp_time=300.0, step_ratio=0.2):
        self.target = target_luma
        self.gain = 1.0
        self.exp_time = 50.0  # 初始曝光时间(ms)
        self.min_gain = min_gain
        self.max_gain = max_gain
        self.min_exp = min_exp_time
        self.max_exp = max_exp_time
        self.step = step_ratio  # 单次最大调整比例,防止过冲

    def _clip_exp_time(self, val):
        return max(self.min_exp, min(self.max_exp, val))

    def _clip_gain(self, val):
        return max(self.min_gain, min(self.max_gain, val))

    def calculate(self, avg_luma):
        """输入当前帧平均亮度,返回调整后的 (exposure_time, gain)"""
        if avg_luma <= 0:
            avg_luma = 1e-6  # 避免除零

        # 理想总曝光倍数
        ratio = self.target / avg_luma
        # 限制单步调整幅度,防止闪烁
        ratio = max(1.0 - self.step, min(1.0 + self.step, ratio))

        # 当前总曝光
        cur_total = self.exp_time * self.gain
        new_total = cur_total * ratio

        # 优先分配曝光时间
        new_exp = self._clip_exp_time(new_total / self.gain)
        # 若曝光时间已饱和,剩余部分用增益补偿
        new_gain = self._clip_gain(new_total / new_exp)

        # 若增益也饱和,则重新计算曝光时间(可能被增益上限拉低)
        if new_gain >= self.max_gain:
            new_exp = self._clip_exp_time(new_total / new_gain)

        self.exp_time = new_exp
        self.gain = new_gain
        return self.exp_time, self.gain

    def reset(self):
        self.gain = 1.0
        self.exp_time = 50.0

运行与验证

使用模拟亮度响应验证收敛性。假设传感器输出亮度与曝光成正比(忽略噪声),用以下脚本测试:

ae = SimpleAE(target_luma=110)
# 模拟:初始场景亮度系数为0.8(即曝光=100时亮度=80)
scene_coef = 0.8
exp, gain = ae.exp_time, ae.gain
for i in range(10):
    luma = scene_coef * exp * gain  # 模拟传感器输出
    exp, gain = ae.calculate(luma)
    print(f"iter {i}: luma={luma:.1f}, exp={exp:.1f}, gain={gain:.2f}")
    if abs(luma - 110) < 1.0:
        print("Converged")
        break

预期输出(前几行):

iter 0: luma=40.0, exp=60.0, gain=1.00
iter 1: luma=48.0, exp=72.0, gain=1.00
iter 2: luma=57.6, exp=86.4, gain=1.00
iter 3: luma=69.1, exp=103.7, gain=1.00
iter 4: luma=83.0, exp=110.0, gain=1.00
iter 5: luma=88.0, exp=110.0, gain=1.00
iter 6: luma=88.0, exp=110.0, gain=1.00

由于单步限制为20%,亮度逐步逼近目标。当曝光时间达到上限(300ms)后,算法会自动开始提升增益。验证通过条件:运行上述脚本,亮度值单调趋近目标值110,且曝光时间优先于增益增加(在曝光未饱和时gain保持1.0)。

关键说明

  • 代码中 step_ratio=0.2 防止单帧调整过大导致画面闪烁,实际项目中可调。
  • 若场景亮度极低,曝光时间达到 max_exp_time 后,算法自动切换为提升增益,符合AE策略。
  • 本实现假设亮度与曝光严格线性,真实传感器存在噪声与非线性,但作为AE收敛核心逻辑已完整。
AE反馈控制流程 传感器输出 亮度统计 AE算法 曝光/增益 反馈 控制量 目标亮度