AE经典算法:均值法、中心权重法、分区权重法、直方图法
自动曝光(AE)说白了,就是让摄像头自己找到那个「不亮不暗刚刚好」的亮度值。我入行那会儿,第一个任务就是调AE算法。当时觉得不就是算个平均亮度嘛,后来才发现,这里面的门道深着呢。
今天咱们就把四种最经典的AE算法掰开揉碎了讲。每种算法都有它的脾气,选对了,画面通透;选错了,要么过曝一片白,要么暗部死黑。
1. 均值法——最朴素的曝光策略
均值法的思路很简单:把整帧图像所有像素的亮度值加起来,除以像素总数,得到一个平均亮度。然后跟目标亮度(通常是128,8位图像的中值)比较,调整曝光时间或增益。
// 均值法核心逻辑
uint32_t sum = 0;
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
sum += frame[y][x];
}
}
uint8_t avg_luma = sum / (width * height);
int error = TARGET_LUMA - avg_luma;
// 根据error调整曝光参数
我在项目中遇到过一个问题:大面积的蓝天背景下,人脸完全欠曝。均值法把整个天空的亮度平均进去了,导致人脸区域曝光不足。这就是均值法的致命伤——它一视同仁,不区分前景和背景。
适用场景:光照均匀、没有明显主体、背景简单的场景。比如仓库监控、走廊、隧道内部。
注意:均值法对极端亮暗区域非常敏感。画面中如果有一盏灯或者一块反光板,整个画面的曝光都会被带偏。
2. 中心权重法——聚焦画面中央
中心权重法认为,画面的主体通常放在中央区域。所以它给中央区域更高的权重,边缘区域权重低一些。说白了,就是「中央说了算,边缘靠边站」。
常见的权重分配方式是这样的:
| 区域 | 权重系数 | 说明 |
|---|---|---|
| 中央区域(约占画面30%) | 0.6 | 主体通常在这里 |
| 中间区域(约占画面40%) | 0.3 | 过渡区域 |
| 边缘区域(约占画面30%) | 0.1 | 干扰信息,权重最低 |
// 中心权重法示意
float weight_map[HEIGHT][WIDTH];
// 初始化权重,中央高,边缘低
float weighted_sum = 0.0f;
float total_weight = 0.0f;
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
weighted_sum += frame[y][x] * weight_map[y][x];
total_weight += weight_map[y][x];
}
}
uint8_t weighted_avg = (uint8_t)(weighted_sum / total_weight);
中心权重法比均值法聪明一些。我记得有一次做门禁摄像头,人脸正好在画面中央,中心权重法表现很好。但问题来了——如果主体不在中央呢?比如监控画面中,人从左边走进来,中央权重法就会把背景当作主体,导致人脸过曝。
我的经验:中心权重法适合固定机位、主体大概率在中央的场景。比如会议摄像头、考勤机、人脸识别闸机。但安防监控中,人经常从边缘进入画面,这时候中心权重法就不太够用了。
3. 分区权重法——灵活的区域策略
分区权重法把画面分成若干块(比如3×3、5×5、7×7),每块独立计算平均亮度,然后给每块分配不同的权重。权重可以根据场景动态调整,也可以预设。
我常用的分区方式是5×5网格:
// 5×5分区权重法
#define GRID_X 5
#define GRID_Y 5
uint32_t block_sum[GRID_Y][GRID_X] = {0};
uint32_t block_count[GRID_Y][GRID_X] = {0};
// 统计每个分区的亮度总和
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
int by = y * GRID_Y / height;
int bx = x * GRID_X / width;
block_sum[by][bx] += frame[y][x];
block_count[by][bx]++;
}
}
// 计算每个分区的平均亮度
uint8_t block_avg[GRID_Y][GRID_X];
for (int i = 0; i < GRID_Y; i++) {
for (int j = 0; j < GRID_X; j++) {
block_avg[i][j] = block_sum[i][j] / block_count[i][j];
}
}
// 应用权重矩阵(可动态调整)
float weight[GRID_Y][GRID_X] = {
{0.02, 0.03, 0.05, 0.03, 0.02},
{0.03, 0.06, 0.10, 0.06, 0.03},
{0.05, 0.10, 0.20, 0.10, 0.05},
{0.03, 0.06, 0.10, 0.06, 0.03},
{0.02, 0.03, 0.05, 0.03, 0.02}
};
float final_luma = 0.0f;
for (int i = 0; i < GRID_Y; i++) {
for (int j = 0; j < GRID_X; j++) {
final_luma += block_avg[i][j] * weight[i][j];
}
}
分区权重法的好处是灵活。你可以根据场景动态调整权重矩阵。比如检测到人脸时,把人脸所在区域的权重提高;检测到运动目标时,把目标区域的权重提高。
实战技巧:我在做智能交通摄像头时,把路面区域权重设高,天空区域权重设低。这样不管晴天阴天,车牌和车身都能保持合适的曝光。
分区权重法也有坑。分区太细,计算量大,嵌入式芯片扛不住;分区太粗,又失去了分区的意义。我一般建议在嵌入式平台上用5×5或7×7,再大就得上硬件加速了。
4. 直方图法——基于统计分布的曝光
直方图法不只看平均亮度,它分析整帧图像的亮度分布。直方图的横轴是亮度(0~255),纵轴是像素数量。好的曝光,直方图应该分布均匀,没有明显的「断崖」或「堆积」。
直方图法的核心思路:让直方图尽量覆盖整个亮度范围,同时避免过曝(像素堆积在255附近)和欠曝(像素堆积在0附近)。
// 直方图统计
uint32_t histogram[256] = {0};
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
histogram[frame[y][x]]++;
}
}
// 计算过曝和欠曝比例
uint32_t total_pixels = width * height;
uint32_t overexposed = 0; // 亮度 > 250
uint32_t underexposed = 0; // 亮度 < 10
for (int i = 250; i < 256; i++) overexposed += histogram[i];
for (int i = 0; i < 10; i++) underexposed += histogram[i];
float over_ratio = (float)overexposed / total_pixels;
float under_ratio = (float)underexposed / total_pixels;
// 如果过曝比例超过阈值,降低曝光
if (over_ratio > 0.05f) {
// 减少曝光时间或增益
}
// 如果欠曝比例超过阈值,增加曝光
if (under_ratio > 0.05f) {
// 增加曝光时间或增益
}
直方图法还有一个进阶玩法——直方图均衡。它不只是调整曝光,还能提升对比度。但要注意,直方图均衡在安防监控中要慎用,因为它会改变图像的「真实感」,对后续的智能分析(比如人脸识别)可能产生负面影响。
我曾经踩过的坑:有一次做夜间监控,直方图法把暗部提得太亮,结果噪点全出来了,画面全是雪花。后来我加了限制——暗部提亮不超过原始亮度的1.5倍,同时配合3D降噪,才把效果做出来。
四种算法对比
| 算法 | 计算量 | 抗干扰能力 | 主体适应性 | 典型场景 |
|---|---|---|---|---|
| 均值法 | 低 | 差 | 差 | 均匀光照场景 |
| 中心权重法 | 低 | 中 | 中 | 人脸识别、会议 |
| 分区权重法 | 中 | 强 | 强 | 智能监控、交通 |
| 直方图法 | 高 | 强 | 中 | 复杂光照、逆光 |
你想想看,实际项目中怎么选?我个人习惯是:先跑均值法做基准,然后根据场景需求叠加分区权重或直方图约束。没有万能的算法,只有合适的组合。
我的建议:在嵌入式平台上,优先用分区权重法。它比均值法和中心权重法灵活,又比直方图法省资源。如果芯片算力够(比如有NPU或ISP硬件加速),再上直方图法做精细调优。
嗯,四种算法讲完了。最后说一句:算法是死的,场景是活的。别死磕一种算法,多试试组合方案。我见过太多工程师拿着均值法跑所有场景,结果被客户骂到怀疑人生。灵活一点,你的摄像头才能「聪明」起来。