4. 全局搜索算法:全局扫描策略、粗扫与精扫结合、搜索范围与步长优化

全局搜索,说白了就是让镜头从一头走到另一头,把每个位置的清晰度都算一遍。听起来很笨,对吧?但在我做过的项目中,这恰恰是最可靠、最不容易出问题的方法。尤其是当你面对一个全新的光学系统,或者环境变化特别大的时候,全局搜索往往是保底方案。

4.1 全局扫描策略:从一端扫到另一端

全局扫描的核心逻辑很简单:确定一个起始位置和一个终止位置,然后按照固定的步长,一步一步地走,每走一步拍一张图,算一个清晰度评价值。最后,从这些评价值里挑出最大值对应的位置,那就是对焦位置。

我习惯把这种策略叫做“穷举法”。它不挑场景,不挑光线,只要你的搜索范围覆盖了真正的焦平面,它就一定能找到。但代价也很明显——慢。

全局扫描的典型参数

  • 搜索范围:通常设置为镜头全行程的 80%~100%。比如一个镜头行程是 1000 步,那搜索范围可能就是 100~900 步。
  • 步长:一般取景深的 1/3 到 1/2。步长太大容易漏掉峰值,步长太小则浪费时间。
  • 采样点数:搜索范围除以步长,就是你要拍的照片数量。这个数直接决定了对焦耗时。

举个例子。我曾经调试过一个工业检测镜头,它的景深只有 20 微米。我一开始用了 10 微米的步长,结果每次对焦都要拍 80 多张图,耗时接近 3 秒。客户说太慢了,要求 1 秒以内。怎么办?只能优化策略。

4.2 粗扫与精扫结合:先找大概,再定精确

全局扫描虽然可靠,但效率太低。我后来想了个办法:为什么不先快速扫一遍,找到峰值的大概位置,然后再在那个小范围里精细地扫一次?这就是粗扫与精扫结合。

粗扫的目的不是找到精确的焦平面,而是把峰值位置锁定在一个较小的区间内。精扫则是在这个区间内,用小步长精确找到峰值。

阶段 步长 采样点数 目标
粗扫 景深的 1~2 倍 10~20 点 锁定峰值区间
精扫 景深的 1/4~1/3 5~10 点 精确找到峰值

你看,粗扫只用了 10~20 个点,精扫又用了 5~10 个点,加起来最多 30 个点。相比之前 80 多个点,时间直接砍了一半多。而且精度一点没降。

我的经验:粗扫的步长不能太大。我曾经试过用景深的 3 倍步长做粗扫,结果峰值区间没锁住,精扫的时候直接跑偏了。后来我定了个规矩:粗扫步长最多不超过景深的 2 倍。

4.3 搜索范围与步长优化:找到那个“甜点”

搜索范围和步长,这两个参数是全局搜索的核心。它们直接决定了对焦的速度和精度。怎么优化?我总结了一套方法。

4.3.1 搜索范围的优化

搜索范围不是越大越好。范围越大,耗时越长。但范围太小,又可能漏掉真正的焦平面。怎么平衡?

  • 利用历史数据:如果系统连续对焦,上一次的焦平面位置可以作为参考。下一次搜索时,以上一次位置为中心,向两边扩展一个安全余量。这个余量通常取景深的 5~10 倍。
  • 利用传感器信息:有些镜头带有位置传感器,可以实时反馈当前位置。如果知道目标距离的大致范围,就可以缩小搜索区间。
  • 动态调整:我做过一个项目,镜头在不同温度下热胀冷缩,焦平面会漂移。后来我加了个温度传感器,根据温度动态调整搜索范围的中心位置。效果很好。

4.3.2 步长的优化

步长太小,采样点太多,耗时太长。步长太大,容易漏掉峰值。怎么选?

这里有个关键概念:清晰度曲线的“峰宽”。峰宽是指清晰度值从峰值下降到某个阈值(比如 80%)时,对应的位置范围。步长必须小于峰宽的一半,否则很可能采样不到真正的峰值。

避坑指南:我曾经在一个低对比度场景中,用了固定的步长。结果清晰度曲线非常平缓,峰宽很大,步长虽然小,但峰值位置根本分辨不出来。后来我改用自适应步长——根据当前曲线的斜率动态调整步长。斜率大的地方步长小,斜率小的地方步长大。这样既保证了精度,又节省了时间。

4.4 全局搜索的完整流程

下面这张图是我自己画的一个全局搜索流程图,包含了粗扫与精扫结合的完整逻辑。你可以参考一下。

全局搜索流程图:粗扫与精扫结合 全局搜索流程图:粗扫与精扫结合 开始全局搜索 设置粗扫参数 大步长 + 宽范围 执行粗扫 找到峰值区间 锁定粗扫峰值位置 ± 余量 设置精扫参数 小步长 + 窄范围 执行精扫 输出焦平面位置 说明 粗扫 + 精扫总点数 ≤ 30 相比纯全局扫描效率提升 60%+

4.5 代码示例:粗扫与精扫的实现

下面是一个简单的 C 语言伪代码,展示了粗扫与精扫的核心逻辑。实际项目中,你需要根据硬件接口做适配。

// 全局搜索:粗扫 + 精扫
int global_search(int start, int end, int coarse_step, int fine_step) {
    int peak_pos = 0;
    float max_sharpness = 0.0f;
    
    // 阶段1:粗扫
    printf("[粗扫] 范围: %d ~ %d, 步长: %d\n", start, end, coarse_step);
    for (int pos = start; pos <= end; pos += coarse_step) {
        move_lens_to(pos);
        float sharpness = calculate_sharpness(capture_image());
        if (sharpness > max_sharpness) {
            max_sharpness = sharpness;
            peak_pos = pos;
        }
    }
    
    // 锁定精扫范围:峰值位置 ± 2倍粗扫步长
    int fine_start = peak_pos - 2 * coarse_step;
    int fine_end   = peak_pos + 2 * coarse_step;
    if (fine_start < start) fine_start = start;
    if (fine_end   > end)   fine_end   = end;
    
    // 阶段2:精扫
    printf("[精扫] 范围: %d ~ %d, 步长: %d\n", fine_start, fine_end, fine_step);
    max_sharpness = 0.0f;
    for (int pos = fine_start; pos <= fine_end; pos += fine_step) {
        move_lens_to(pos);
        float sharpness = calculate_sharpness(capture_image());
        if (sharpness > max_sharpness) {
            max_sharpness = sharpness;
            peak_pos = pos;
        }
    }
    
    return peak_pos;
}

小技巧:粗扫的时候,如果发现清晰度值连续下降 3 次以上,可以提前终止扫描。因为峰值大概率已经过了。这个技巧在峰值比较尖锐的场景中特别管用,能再省 20%~30% 的时间。

4.6 全局搜索的适用场景

全局搜索不是万能的,但它有自己的“舒适区”。我整理了一个表格,方便你判断什么时候该用它。

场景 推荐策略 原因
首次对焦 / 系统初始化 全局搜索(粗扫 + 精扫) 没有历史数据,需要全范围覆盖
低对比度 / 弱光环境 全局搜索(大步长粗扫) 清晰度曲线平缓,小步长容易误判
高精度对焦(如显微镜) 全局搜索(小步长精扫) 需要亚微米级精度,不能漏掉峰值
连续对焦 / 跟踪对焦 不推荐全局搜索 耗时太长,建议用爬山法或预测搜索