4、对比度AF算法详解:爬山算法原理、粗搜与精搜策略、搜索步长设计、峰值判定与防抖、窗口选择策略
对比度AF,说白了就是找图像最清晰的那个位置。它不像相位检测那么快,但胜在原理简单、硬件成本低。我最早接触AF调优时,就是从对比度算法入手的。今天咱们把它的核心模块拆开揉碎了讲。
4.1 爬山算法原理
爬山算法的思路很直观:镜头移动时,图像的对比度值会变化。对比度越高,图像越清晰。算法就像爬山一样,一步步往高处走,直到找不到更高的地方。
核心逻辑:对比度值 = 图像高频分量能量。对焦准确时,边缘最锐利,高频能量最大。
我习惯用拉普拉斯算子或Sobel算子来计算对比度。举个例子,一个简单的对比度计算函数:
// 计算图像块的对比度值(拉普拉斯能量)
float calcContrast(uint8_t* img, int width, int height) {
float sum = 0.0f;
for (int y = 1; y < height - 1; y++) {
for (int x = 1; x < width - 1; x++) {
int idx = y * width + x;
// 拉普拉斯算子
int lap = abs(img[idx] * 4
- img[idx - 1] - img[idx + 1]
- img[idx - width] - img[idx + width]);
sum += lap * lap; // 平方累加,放大差异
}
}
return sum / (width * height); // 归一化
}
嗯,这里要注意:归一化很重要。不同场景的亮度不同,不归一化的话,对比度值会受亮度影响,导致误判。
4.2 粗搜与精搜策略
爬山算法不能一上来就精细搜索,那样太慢了。我一般分两步走:粗搜定位大致范围,精搜锁定精确位置。
| 搜索阶段 | 步长 | 目标 | 典型场景 |
|---|---|---|---|
| 粗搜 | 大步长(8~16步) | 快速找到峰值附近区域 | 初始对焦、场景切换 |
| 精搜 | 小步长(1~2步) | 精确锁定峰值位置 | 微调、跟踪对焦 |
粗搜时,我建议用大步长快速扫描整个对焦范围。一旦发现对比度值开始下降,说明已经越过了峰值,立即掉头进入精搜阶段。
个人经验:粗搜的步长不要固定。我遇到过光线暗的场景,大步长直接跳过了峰值。后来我改成动态步长——根据当前对比度变化率自动调整。变化率大时步长大,变化率小时步长小。
4.3 搜索步长设计
步长设计是爬山算法的关键。步长太大容易跳过峰值,步长太小又太慢。怎么平衡?
我总结了一套步长设计原则:
- 镜头行程:步长不能超过镜头景深的一半。否则可能永远找不到峰值。
- 场景纹理:纹理丰富的场景,对比度曲线陡峭,可以用小步长。纹理稀疏的场景,曲线平缓,需要大步长。
- 运动状态:静止场景用精细步长,运动场景用大步长快速锁定。
举个例子,一个典型的步长配置:
// 步长配置结构体
typedef struct {
int coarseStep; // 粗搜步长,单位:马达步数
int fineStep; // 精搜步长
int minStep; // 最小步长(防止死循环)
int maxStep; // 最大步长(防止越界)
} StepConfig;
// 默认配置
StepConfig defaultConfig = {
.coarseStep = 12,
.fineStep = 2,
.minStep = 1,
.maxStep = 20
};
避坑指南:我曾经在一个项目中,步长设置得太小,导致对焦时间超过2秒。用户反馈说“拍照像在等火车”。后来我把粗搜步长从4改成12,对焦时间直接降到0.5秒以内。步长不是越小越好,要兼顾速度和精度。
4.4 峰值判定与防抖
找到峰值不难,难的是在噪声和抖动中准确找到峰值。你想想看,手持拍摄时,手会抖,光线会变,对比度值会上下跳动。这时候怎么判定真正的峰值?
我常用的峰值判定策略:
- 连续下降判定:对比度值连续下降N次(我一般设3次),才认为已经越过峰值。
- 阈值判定:对比度值变化小于某个阈值时,认为进入平坦区,停止搜索。
- 回退验证:找到疑似峰值后,回退一步重新测量,确认对比度确实下降。
防抖方面,我推荐两种方法:
- 多帧平均:每次测量取3~5帧的平均值,可以有效抑制随机噪声。
- 滞后比较:设置一个滞回区间,对比度值变化超过滞回区间才认为有效。这招在弱光环境下特别管用。
核心要点:峰值判定不要只看当前值,要看趋势。我习惯维护一个长度为5的滑动窗口,窗口内的对比度值持续下降才判定为越过峰值。
4.5 窗口选择策略
窗口选择直接影响对比度计算的准确性。窗口太大,包含太多背景,对比度值被稀释。窗口太小,可能错过主体。
我总结了几种窗口选择策略:
| 策略 | 窗口位置 | 适用场景 | 优缺点 |
|---|---|---|---|
| 中心窗口 | 图像中心区域 | 主体在中心 | 简单快速,但主体偏离时失效 |
| 多区域窗口 | 多个固定区域 | 主体位置不确定 | 覆盖范围广,但计算量大 |
| 人脸/物体跟踪窗口 | 动态跟踪主体 | 人像、运动物体 | 精度高,但需要额外的检测算法 |
| 自适应窗口 | 根据场景自动调整 | 通用场景 | 灵活,但实现复杂 |
我个人最常用的是多区域窗口。在画面中均匀分布9个或16个窗口,每个窗口独立计算对比度,取最大值或加权平均。这样即使主体不在中心,也能准确对焦。
实战技巧:窗口大小建议取图像宽高的1/8到1/4。太小了噪声大,太大了细节丢失。我习惯用64x64像素的窗口,在1080p分辨率下效果不错。
下面这张图展示了爬山算法的完整流程:
爬山算法虽然经典,但也不是万能的。我遇到过一些极端场景,比如纯色墙壁、夜景、强烈逆光,对比度曲线几乎是一条直线,爬山算法直接失效。这时候就需要结合其他AF算法,比如激光测距或相位检测,来做辅助判断。
重要提醒:爬山算法有一个致命缺陷——它只能找到局部峰值,不是全局峰值。如果场景中有多个清晰区域(比如透过玻璃窗拍照),算法可能停在玻璃上的灰尘处,而不是窗外的风景。我建议在粗搜阶段多采样几个点,选择对比度最高的区域作为起始点,降低陷入局部峰值的概率。
好了,对比度AF的核心内容就这些。爬山算法虽然看起来简单,但真正调优起来,细节非常多。步长、窗口、峰值判定,每一个环节都值得反复打磨。希望今天的分享能帮你少走一些弯路。