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 知识体系总览
下面这张图把三种方法的关系和适用场景梳理清楚了:
3.6 实际项目中的选择建议
说了这么多,到底该用哪种?我根据实际项目经验给几个建议:
- 手机摄像头: 权重矩阵法为主,配合人脸检测做动态权重。计算量适中,效果最好
- 安防监控: 直方图统计法 + 分区平均法。因为场景变化大,需要丰富的亮度信息
- 工业相机: 平均亮度法就够了。光照可控,场景单一,没必要搞复杂
- 车载摄像头: 权重矩阵法 + 直方图特征。既要保证中心区域(道路)曝光正确,又要能适应隧道等极端场景
重要提醒: 不管你选哪种方法,一定要在硬件平台上做性能评估。我见过有人在PC上跑得好好的,移植到嵌入式平台后帧率直接掉一半。直方图统计法尤其吃资源,可以考虑用硬件直方图加速模块。