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 峰值判定与防抖

找到峰值不难,难的是在噪声和抖动中准确找到峰值。你想想看,手持拍摄时,手会抖,光线会变,对比度值会上下跳动。这时候怎么判定真正的峰值?

我常用的峰值判定策略:

  1. 连续下降判定:对比度值连续下降N次(我一般设3次),才认为已经越过峰值。
  2. 阈值判定:对比度值变化小于某个阈值时,认为进入平坦区,停止搜索。
  3. 回退验证:找到疑似峰值后,回退一步重新测量,确认对比度确实下降。

防抖方面,我推荐两种方法:

  • 多帧平均:每次测量取3~5帧的平均值,可以有效抑制随机噪声。
  • 滞后比较:设置一个滞回区间,对比度值变化超过滞回区间才认为有效。这招在弱光环境下特别管用。

核心要点:峰值判定不要只看当前值,要看趋势。我习惯维护一个长度为5的滑动窗口,窗口内的对比度值持续下降才判定为越过峰值。

4.5 窗口选择策略

窗口选择直接影响对比度计算的准确性。窗口太大,包含太多背景,对比度值被稀释。窗口太小,可能错过主体。

我总结了几种窗口选择策略:

策略 窗口位置 适用场景 优缺点
中心窗口 图像中心区域 主体在中心 简单快速,但主体偏离时失效
多区域窗口 多个固定区域 主体位置不确定 覆盖范围广,但计算量大
人脸/物体跟踪窗口 动态跟踪主体 人像、运动物体 精度高,但需要额外的检测算法
自适应窗口 根据场景自动调整 通用场景 灵活,但实现复杂

我个人最常用的是多区域窗口。在画面中均匀分布9个或16个窗口,每个窗口独立计算对比度,取最大值或加权平均。这样即使主体不在中心,也能准确对焦。

实战技巧:窗口大小建议取图像宽高的1/8到1/4。太小了噪声大,太大了细节丢失。我习惯用64x64像素的窗口,在1080p分辨率下效果不错。

下面这张图展示了爬山算法的完整流程:

爬山算法对焦流程 开始对焦 粗搜:大步长扫描 步长=12,快速定位峰值区域 对比度 下降? 否 继续粗搜 是 精搜:小步长微调 步长=2,精确锁定峰值 峰值判定与防抖 对焦完成 关键参数 粗搜步长:8~16 精搜步长:1~2 下降判定:连续3次 窗口大小:64x64

爬山算法虽然经典,但也不是万能的。我遇到过一些极端场景,比如纯色墙壁、夜景、强烈逆光,对比度曲线几乎是一条直线,爬山算法直接失效。这时候就需要结合其他AF算法,比如激光测距或相位检测,来做辅助判断。

重要提醒:爬山算法有一个致命缺陷——它只能找到局部峰值,不是全局峰值。如果场景中有多个清晰区域(比如透过玻璃窗拍照),算法可能停在玻璃上的灰尘处,而不是窗外的风景。我建议在粗搜阶段多采样几个点,选择对比度最高的区域作为起始点,降低陷入局部峰值的概率。

好了,对比度AF的核心内容就这些。爬山算法虽然看起来简单,但真正调优起来,细节非常多。步长、窗口、峰值判定,每一个环节都值得反复打磨。希望今天的分享能帮你少走一些弯路。