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

自动曝光,说白了就是让摄像头自己找到那个「不亮不暗刚刚好」的曝光值。我刚开始做摄像头驱动时,总觉得AE不就是调调亮度嘛,后来在户外强光和暗光场景来回切换时才发现——这东西水挺深。

这一章我们聚焦AE的核心:曝光三角怎么配合、AE到底在追求什么、经典算法怎么算、以及收敛策略怎么设计。嗯,都是嵌入式部署里绕不开的硬知识。

4.1 曝光三角:光圈、快门、ISO

曝光量由三个参数共同决定,业内叫「曝光三角」。你想想看,这就像用一个水桶接水——光圈是水龙头开多大,快门是接多久,ISO是桶的放大倍数。

参数 物理含义 对图像的影响 嵌入式控制方式
光圈(Aperture) 镜头通光孔径大小 进光量、景深 电机驱动(手机模组固定光圈居多)
快门(Shutter) 传感器曝光时间 亮度、运动模糊 寄存器配置(行曝光时间)
ISO(增益) 模拟/数字信号放大倍数 亮度、噪声 模拟增益 + 数字增益寄存器

关键认知:在嵌入式AE中,光圈通常是固定的(手机摄像头、监控IPC),我们主要调快门和ISO。但车载或工业相机有时会调光圈,这时候控制逻辑就复杂了。

我在项目中遇到过一个问题:某款安防相机在夜间模式下,ISO拉到很高,结果画面全是噪点。后来我限制了ISO上限,优先拉长快门时间——但快门太长又导致运动拖影。这就是曝光三角的博弈。

4.2 AE目标:亮度与对比度

AE不是单纯让画面变亮。它追求两个核心指标:目标亮度和对比度。

  • 目标亮度:通常用图像平均亮度或中间亮度来衡量。比如YUV空间的Y分量均值,目标值一般设在100~120(8bit范围)。
  • 对比度:画面亮暗差异是否合理。如果整张图都灰蒙蒙的,亮度均值再准也没用。

为什么会这样?因为人眼对亮度的感知不是线性的。一张图亮度均值120,但暗部死黑、亮部过曝,观感就很差。所以好的AE算法要兼顾「整体亮度」和「细节保留」。

我的经验:在嵌入式平台上,我习惯用Y分量的直方图来评估AE效果。如果直方图集中在中间区域且两端有少量像素,说明曝光比较合理。如果直方图贴左墙或右墙,那就是欠曝或过曝了。

4.3 经典AE算法

AE算法有很多种,但嵌入式部署最常用的就两类:均值法和直方图法。我分别讲讲它们的原理和坑。

4.3.1 均值法

均值法是最简单的AE算法。思路很直接:计算整帧图像的亮度均值,然后跟目标亮度比较,调整曝光参数。

// 伪代码:均值法AE
uint8_t calc_mean(uint8_t *frame, int width, int height) {
    uint32_t sum = 0;
    for (int i = 0; i < width * height; i++) {
        sum += frame[i];
    }
    return (uint8_t)(sum / (width * height));
}

void ae_mean_control(uint8_t target_lum, uint8_t *frame) {
    uint8_t current_lum = calc_mean(frame, W, H);
    float ratio = (float)target_lum / (float)current_lum;
    // 根据ratio调整曝光时间和增益
    adjust_exposure(ratio);
}

均值法的优点是计算量小、实现简单。但缺点也很明显——它容易被大面积亮区或暗区「带偏」。比如拍雪景,均值法会觉得画面太亮,拼命降低曝光,结果雪变成灰色。

避坑指南:我曾经在户外强光场景下用均值法,结果画面整体偏暗。后来加了区域权重——中心区域权重高、边缘权重低,效果好了很多。但这也只是权宜之计。

4.3.2 直方图法

直方图法比均值法更精细。它统计每个亮度级别的像素数量,然后根据直方图的分布来调整曝光。

核心思路:让直方图尽量「铺满」整个亮度范围,且不出现明显的贴墙现象。

// 伪代码:直方图法AE
void calc_histogram(uint8_t *frame, int width, int height, uint32_t *hist) {
    memset(hist, 0, 256 * sizeof(uint32_t));
    for (int i = 0; i < width * height; i++) {
        hist[frame[i]]++;
    }
}

void ae_histogram_control(uint32_t *hist, int total_pixels) {
    // 计算暗部比例(亮度<30)
    uint32_t dark_pixels = 0;
    for (int i = 0; i < 30; i++) dark_pixels += hist[i];
    float dark_ratio = (float)dark_pixels / total_pixels;

    // 计算亮部比例(亮度>220)
    uint32_t bright_pixels = 0;
    for (int i = 220; i < 256; i++) bright_pixels += hist[i];
    float bright_ratio = (float)bright_pixels / total_pixels;

    // 根据暗部和亮部比例调整曝光
    if (dark_ratio > 0.1) increase_exposure();
    if (bright_ratio > 0.1) decrease_exposure();
}

直方图法的优势是能感知场景的亮度分布,避免被大面积单色区域欺骗。但计算量比均值法大,在低端MCU上可能扛不住。

对比总结:

  • 均值法:适合光照均匀、场景简单的环境。计算快,但容易翻车。
  • 直方图法:适合复杂光照场景。效果好,但需要更多算力。

4.4 AE收敛策略

AE算法算出目标曝光量后,不能一步到位地调过去。为什么?因为曝光参数突变会导致画面闪烁,用户体验极差。所以需要「收敛策略」——让曝光平滑地过渡到目标值。

常用的收敛策略有三种:

策略 原理 优点 缺点
步进式 每次调整固定步长 实现简单,可控 收敛慢,可能震荡
比例式 按误差比例调整 收敛快,误差大时大步调整 可能超调
PID式 比例+积分+微分 平滑、稳定 参数调校麻烦

我个人最常用的是比例式+PID混合策略。在误差大时用比例式快速逼近,误差小时切到PID做精细调节。这样既快又稳。

实战技巧:我曾经在某个项目中,AE收敛总是出现震荡。后来发现是曝光时间和增益的调整步长不匹配。我的解决办法是:先调曝光时间,等曝光时间稳定后再调增益。分步走,别同时动两个参数。

4.5 AE算法知识体系

下面这张图把本章的核心知识点串起来了。你可以看到从曝光三角到AE目标,再到算法实现和收敛策略的完整链路。

AE算法知识体系 曝光三角 光圈 · 快门 · ISO 物理参数 → 控制寄存器 AE目标 目标亮度 · 对比度 Y分量均值100~120 经典AE算法 均值法 · 直方图法 计算量 vs 效果 收敛策略 步进式 · 比例式 · PID式 · 混合策略 嵌入式部署要点 低算力优化 · 寄存器配置 · 帧率约束 · 防闪烁 核心思路:曝光三角提供控制手段 → AE目标定义评价标准 → 算法计算调整量 → 收敛策略平滑执行 嵌入式部署时需平衡算法效果与计算资源

4.6 本章小结

AE算法说难不难,说简单也不简单。核心就三件事:

  • 知道调什么——曝光三角的参数控制
  • 知道调到哪——目标亮度和对比度
  • 知道怎么调——算法计算 + 收敛策略

在嵌入式平台上,我建议先从均值法+比例式收敛开始,跑通流程后再逐步升级到直方图法+PID。别一上来就搞复杂的,容易把自己绕进去。

最后提醒:AE算法一定要考虑帧率约束。如果每帧都做AE计算,CPU可能扛不住。我一般每3~5帧做一次AE更新,既保证响应速度,又降低负载。