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目标,再到算法实现和收敛策略的完整链路。
4.6 本章小结
AE算法说难不难,说简单也不简单。核心就三件事:
- 知道调什么——曝光三角的参数控制
- 知道调到哪——目标亮度和对比度
- 知道怎么调——算法计算 + 收敛策略
在嵌入式平台上,我建议先从均值法+比例式收敛开始,跑通流程后再逐步升级到直方图法+PID。别一上来就搞复杂的,容易把自己绕进去。
最后提醒:AE算法一定要考虑帧率约束。如果每帧都做AE计算,CPU可能扛不住。我一般每3~5帧做一次AE更新,既保证响应速度,又降低负载。