3. AE核心算法:直方图统计、平均亮度法、权重矩阵法

自动曝光(AE)说白了就是让摄像头自己找到合适的亮度。你想想看,光线一变,画面要么过曝白花花一片,要么欠曝黑乎乎什么都看不见。AE算法就是干这个活的——它得快速判断当前场景亮不亮,然后告诉镜头和传感器该怎么调。

我做了这么多年ISP,发现很多新手一上来就调PID参数,结果画面忽明忽暗。其实AE的核心不在PID,而在你用什么方法去评估亮度。今天咱们就把三种最常用的亮度评估方法讲透。

3.1 直方图统计法

直方图是什么?说白了就是一张图里每个亮度级别有多少个像素。0代表纯黑,255代表纯白,中间是各种灰阶。

我习惯把直方图当作AE的"眼睛"。它能告诉你画面的亮度分布情况,比单纯看平均亮度靠谱得多。

核心思路: 统计每个灰度级的像素数量,形成分布曲线,然后根据分布特征判断曝光是否合理。

3.1.1 直方图统计的数学表达

假设图像有L个灰度级(通常是256级),第k个灰度级的像素数为h(k),那么:

// 直方图统计伪代码
for each pixel (x, y) in image:
    gray = get_luminance(pixel(x, y))
    histogram[gray]++

// 归一化直方图
total_pixels = width * height
for k = 0 to 255:
    normalized_histogram[k] = histogram[k] / total_pixels

3.1.2 直方图特征提取

拿到直方图之后,我们得从中提取有用的特征。我个人常用这几个:

特征名称 计算公式 物理含义
均值 μ = Σ(k × h(k)) / Σh(k) 整体亮度水平
标准差 σ = √(Σ(k-μ)² × h(k) / Σh(k)) 对比度高低
偏度 Skew = Σ((k-μ)/σ)³ × h(k) / Σh(k) 亮部还是暗部占主导
峰度 Kurt = Σ((k-μ)/σ)⁴ × h(k) / Σh(k) - 3 亮度集中程度
我的经验: 偏度这个指标特别有用。如果偏度为正,说明画面偏暗区域多,需要增加曝光;偏度为负则相反。我在做安防摄像头时,就靠偏度来判断是白天还是夜晚场景。

3.1.3 直方图法的优缺点

  • 优点: 信息丰富,能反映亮度分布细节;适合复杂场景的判断
  • 缺点: 计算量大(尤其是全图统计);对噪声敏感;需要额外算法来提取特征

3.2 平均亮度法

平均亮度法是最简单粗暴的方法。就是把所有像素的亮度值加起来,除以像素总数。嗯,就这么简单。

// 平均亮度计算
uint32_t sum = 0;
for (int y = 0; y < height; y++) {
    for (int x = 0; x < width; x++) {
        sum += get_luminance(image[y][x]);
    }
}
float avg_luminance = (float)sum / (width * height);

然后拿这个平均值和目标亮度(比如120)比较,差值就是曝光调整的依据。

注意坑: 平均亮度法在均匀光照场景下表现不错,但遇到逆光或大光比场景就完蛋了。我曾经在一个项目中,用平均亮度法做AE,结果逆光时人脸全黑,背景却正常。后来不得不换成权重矩阵法。

3.2.1 平均亮度法的变种

为了改善效果,业界有一些改进方案:

  • 中值亮度法: 取所有像素亮度的中位数,能抵抗极端亮暗区域的干扰
  • 截断平均法: 去掉最亮和最暗的5%像素后再平均,减少噪声影响
  • 分区平均法: 把图像分成N×N块,每块算平均,再取这些块的平均

3.3 权重矩阵法

这才是真正实用的方法。你想想看,一张照片里,不同区域的重要性是不一样的。人脸区域肯定比天空重要,画面中心区域比边缘重要。权重矩阵就是干这个的——给不同区域分配不同的权重。

核心思想: 图像中心区域权重高,边缘区域权重低;或者根据人脸检测结果动态调整权重。

3.3.1 固定权重矩阵

最常用的就是中心权重矩阵。把图像分成M×N块,中心块的权重最高,越往外权重越低。

// 5×5中心权重矩阵示例
float weight_matrix[5][5] = {
    {1, 2, 3, 2, 1},
    {2, 4, 6, 4, 2},
    {3, 6, 9, 6, 3},
    {2, 4, 6, 4, 2},
    {1, 2, 3, 2, 1}
};

// 归一化权重
float total_weight = 0;
for (int i = 0; i < 5; i++)
    for (int j = 0; j < 5; j++)
        total_weight += weight_matrix[i][j];

// 加权平均亮度
float weighted_avg = 0;
for (int i = 0; i < 5; i++) {
    for (int j = 0; j < 5; j++) {
        float block_avg = get_block_average(image, i, j);
        weighted_avg += block_avg * weight_matrix[i][j] / total_weight;
    }
}

3.3.2 动态权重矩阵

固定权重矩阵虽然简单,但不够灵活。我做过一个项目,需要优先保证人脸曝光正确。这时候就得用动态权重了。

动态权重的思路是:先做人脸检测,检测到人脸的区域权重翻倍;如果没检测到人脸,就退回到中心权重。

// 动态权重调整伪代码
if (face_detected) {
    for each face_region:
        weight_matrix[face_block] *= 2.0;
} else {
    // 使用默认中心权重
    use_default_weight();
}

// 重新归一化
normalize_weight_matrix();
避坑指南: 动态权重要注意权重突变导致的画面闪烁。我曾经遇到过这个问题——人脸一出现,亮度突然跳变。解决办法是加一个平滑过渡,让权重在几帧内渐变过去。

3.4 三种方法的对比

方法 计算量 场景适应性 实现难度 推荐场景
直方图统计法 高 强 中 复杂场景、专业相机
平均亮度法 低 弱 低 均匀光照、低成本方案
权重矩阵法 中 较强 中 手机、安防、车载

3.5 知识体系总览

下面这张图把三种方法的关系和适用场景梳理清楚了:

AE核心算法知识体系 亮度评估方法 直方图统计法 平均亮度法 权重矩阵法 特征提取:均值/标准差/偏度 适用:复杂场景、专业相机 变种:中值/截断/分区平均 适用:均匀光照、低成本方案 固定权重 + 动态权重 适用:手机、安防、车载 实际项目中常组合使用 直方图做场景判断 + 权重矩阵做精确控制

3.6 实际项目中的选择建议

说了这么多,到底该用哪种?我根据实际项目经验给几个建议:

  • 手机摄像头: 权重矩阵法为主,配合人脸检测做动态权重。计算量适中,效果最好
  • 安防监控: 直方图统计法 + 分区平均法。因为场景变化大,需要丰富的亮度信息
  • 工业相机: 平均亮度法就够了。光照可控,场景单一,没必要搞复杂
  • 车载摄像头: 权重矩阵法 + 直方图特征。既要保证中心区域(道路)曝光正确,又要能适应隧道等极端场景
重要提醒: 不管你选哪种方法,一定要在硬件平台上做性能评估。我见过有人在PC上跑得好好的,移植到嵌入式平台后帧率直接掉一半。直方图统计法尤其吃资源,可以考虑用硬件直方图加速模块。