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 全局搜索的完整流程
下面这张图是我自己画的一个全局搜索流程图,包含了粗扫与精扫结合的完整逻辑。你可以参考一下。
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 全局搜索的适用场景
全局搜索不是万能的,但它有自己的“舒适区”。我整理了一个表格,方便你判断什么时候该用它。
| 场景 | 推荐策略 | 原因 |
|---|---|---|
| 首次对焦 / 系统初始化 | 全局搜索(粗扫 + 精扫) | 没有历史数据,需要全范围覆盖 |
| 低对比度 / 弱光环境 | 全局搜索(大步长粗扫) | 清晰度曲线平缓,小步长容易误判 |
| 高精度对焦(如显微镜) | 全局搜索(小步长精扫) | 需要亚微米级精度,不能漏掉峰值 |
| 连续对焦 / 跟踪对焦 | 不推荐全局搜索 | 耗时太长,建议用爬山法或预测搜索 |