2. 搜索策略入门:全局搜索与爬山算法

自动对焦,说白了就是让镜头自己找到最清晰的那个位置。你想想看,镜头从近到远,或者从远到近,总有一个点画面最锐利。那怎么找到它?这就是搜索策略要干的事。

我个人习惯把搜索策略分成两大类:全局搜索和局部搜索。这一章我们先啃两个最基础的——全局搜索和爬山算法。别小看它们,很多复杂的对焦策略都是在这两个基础上搭起来的。

2.1 全局搜索:最笨但最可靠的方法

全局搜索的思路特别简单:把整个对焦范围走一遍,记录每个位置的清晰度值,最后挑最大的那个。就像你在一排钥匙里挨个试,总能找到能开门的那一把。

核心思想:遍历所有可能的对焦位置,选择清晰度评价函数值最大的位置。

2.1.1 算法流程

  1. 设定搜索范围:从近端限位到远端限位,比如步进电机的 0 ~ 1000 步。
  2. 设定步长:每次移动的步数,比如 10 步、20 步。
  3. 逐点采样:从起点开始,每移动一步长,采集一帧图像并计算清晰度值。
  4. 记录最大值:遍历过程中持续比较,记住清晰度最高的位置。
  5. 移动到峰值:遍历结束后,将镜头直接移动到记录的最佳位置。

嗯,这里要注意:步长选多大很关键。步长太大,可能跳过真正的峰值点;步长太小,耗时太长。我在项目中遇到过选 5 步做全局搜索,结果一个来回花了 3 秒多,用户直接投诉对焦太慢。

2.1.2 代码实现

// 全局搜索实现(C语言风格伪代码)
int global_search(int start_pos, int end_pos, int step) {
    int best_pos = start_pos;
    float best_sharpness = 0.0f;
    float current_sharpness;
    
    for (int pos = start_pos; pos <= end_pos; pos += step) {
        move_lens_to(pos);                    // 移动镜头到指定位置
        delay_ms(10);                         // 等待镜头稳定
        current_sharpness = calc_sharpness(); // 计算当前帧清晰度
        
        if (current_sharpness > best_sharpness) {
            best_sharpness = current_sharpness;
            best_pos = pos;
        }
    }
    
    // 移动到最佳位置
    move_lens_to(best_pos);
    return best_pos;
}

2.1.3 优缺点分析

优点 缺点
实现简单,不容易出错 速度慢,遍历时间长
一定能找到全局最优(步长足够小时) 步长与精度矛盾
不受局部峰值干扰 不适合实时性要求高的场景
适合标定和调试阶段 功耗高,电机频繁移动
避坑指南:我曾经在量产项目里直接用全局搜索做初次对焦,结果发现某些模组在远端的清晰度曲线有双峰。全局搜索虽然找到了最高峰,但那个位置其实已经超出了镜头的机械限位。后来我加了边界检查和限位保护,才把这个问题解决。

2.2 爬山算法:更聪明的局部搜索

全局搜索太慢了,对吧?那有没有更快的方法?有,爬山算法就是其中之一。它的思路是:先往一个方向走,如果清晰度在变好,就继续走;如果变差了,就掉头。就像你在雾天爬山,看不见山顶在哪,但你知道往上走是对的。

核心思想:沿着清晰度上升的方向移动,直到找到局部峰值。

2.2.1 算法流程

  1. 确定起始位置:通常是当前位置或预设的起始点。
  2. 选择一个方向:先向近端或远端移动一个步长。
  3. 比较清晰度:移动后计算清晰度,与移动前比较。
  4. 判断方向:
    • 如果清晰度上升,继续同方向移动。
    • 如果清晰度下降,立即反向移动。
    • 如果清晰度变化很小(小于阈值),认为到达峰值。
  5. 重复步骤 3-4,直到满足停止条件。

2.2.2 代码实现

// 爬山算法实现(C语言风格伪代码)
int hill_climbing(int start_pos, int step) {
    int current_pos = start_pos;
    float current_sharpness = calc_sharpness();
    int direction = 1;  // 1: 正向(远端), -1: 反向(近端)
    int stuck_count = 0;
    
    while (stuck_count < 2) {
        // 尝试移动
        int next_pos = current_pos + direction * step;
        
        // 边界检查
        if (next_pos < MIN_POS || next_pos > MAX_POS) {
            direction = -direction;  // 掉头
            stuck_count++;
            continue;
        }
        
        move_lens_to(next_pos);
        delay_ms(10);
        float next_sharpness = calc_sharpness();
        
        // 比较清晰度
        if (next_sharpness > current_sharpness + SHARP_THRESHOLD) {
            // 清晰度上升,继续前进
            current_pos = next_pos;
            current_sharpness = next_sharpness;
            stuck_count = 0;
        } else if (next_sharpness < current_sharpness - SHARP_THRESHOLD) {
            // 清晰度下降,掉头
            direction = -direction;
            stuck_count++;
        } else {
            // 变化很小,认为到达峰值
            stuck_count++;
        }
    }
    
    return current_pos;
}

2.2.3 爬山算法的陷阱

爬山算法快是快,但有个致命问题——容易陷入局部最优。你想想看,如果清晰度曲线有多个波峰,爬山算法只能爬到它遇到的第一个峰顶,不一定是全局最高的那个。

场景 问题描述 影响
多峰曲线 镜头模组在多个位置都有较高清晰度 可能停在次优位置
平坦区域 清晰度变化不明显 算法难以判断方向,来回震荡
噪声干扰 图像噪声导致清晰度值抖动 误判方向,反复掉头
步长过大 跳过峰值点 永远找不到真正的峰值
我的经验:爬山算法的步长最好做成动态的。刚开始用大步长快速接近峰值区域,接近后再换成小步长精细搜索。我在一个安防摄像头项目里就是这么做的,对焦时间从 1.2 秒降到了 0.4 秒。

2.3 两种策略的对比与选择

全局搜索和爬山算法,一个求稳,一个求快。实际项目中怎么选?我一般看这几个因素:

  • 场景是否已知:如果场景固定、清晰度曲线单一,爬山算法就够了。
  • 实时性要求:视频通话、运动抓拍必须用爬山算法或更快的策略。
  • 精度要求:高精度场景(如显微镜、工业检测)建议用全局搜索做标定。
  • 硬件能力:电机响应慢、图像采集帧率低,全局搜索会让人崩溃。

2.4 知识体系图

下面这张图帮你理清两种策略的核心逻辑和关系:

搜索策略知识体系图 搜索策略入门:核心逻辑与对比 全局搜索(Global Search) 爬山算法(Hill Climbing) 遍历所有位置 记录最大值 精度高 速度慢 沿梯度方向 局部搜索 速度快 可能局部最优 对比 选择建议 标定、调试、高精度场景 → 全局搜索 实时对焦、运动场景、已知环境 → 爬山算法

2.5 实战中的选择建议

说实话,在实际产品里,很少有人只用一种策略。我经手的项目,大部分都是混合策略:

  • 初次对焦:先用全局搜索(大步长)快速定位大致范围,再用爬山算法精细调整。
  • 跟踪对焦:目标移动时,用爬山算法在小范围内跟踪。
  • 场景切换:检测到场景剧烈变化,重新触发全局搜索。
一个小技巧:爬山算法的停止条件别只设一个。我习惯同时检查三个条件:清晰度变化小于阈值、连续掉头次数超过限制、搜索时间超时。任何一个满足就停止,防止死循环。

好了,这一章就到这里。全局搜索和爬山算法是自动对焦的基石,理解透了,后面学更复杂的策略会轻松很多。记住:没有最好的策略,只有最适合场景的策略。