2. 搜索策略入门:全局搜索与爬山算法
自动对焦,说白了就是让镜头自己找到最清晰的那个位置。你想想看,镜头从近到远,或者从远到近,总有一个点画面最锐利。那怎么找到它?这就是搜索策略要干的事。
我个人习惯把搜索策略分成两大类:全局搜索和局部搜索。这一章我们先啃两个最基础的——全局搜索和爬山算法。别小看它们,很多复杂的对焦策略都是在这两个基础上搭起来的。
2.1 全局搜索:最笨但最可靠的方法
全局搜索的思路特别简单:把整个对焦范围走一遍,记录每个位置的清晰度值,最后挑最大的那个。就像你在一排钥匙里挨个试,总能找到能开门的那一把。
核心思想:遍历所有可能的对焦位置,选择清晰度评价函数值最大的位置。
2.1.1 算法流程
- 设定搜索范围:从近端限位到远端限位,比如步进电机的 0 ~ 1000 步。
- 设定步长:每次移动的步数,比如 10 步、20 步。
- 逐点采样:从起点开始,每移动一步长,采集一帧图像并计算清晰度值。
- 记录最大值:遍历过程中持续比较,记住清晰度最高的位置。
- 移动到峰值:遍历结束后,将镜头直接移动到记录的最佳位置。
嗯,这里要注意:步长选多大很关键。步长太大,可能跳过真正的峰值点;步长太小,耗时太长。我在项目中遇到过选 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 算法流程
- 确定起始位置:通常是当前位置或预设的起始点。
- 选择一个方向:先向近端或远端移动一个步长。
- 比较清晰度:移动后计算清晰度,与移动前比较。
- 判断方向:
- 如果清晰度上升,继续同方向移动。
- 如果清晰度下降,立即反向移动。
- 如果清晰度变化很小(小于阈值),认为到达峰值。
- 重复步骤 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 知识体系图
下面这张图帮你理清两种策略的核心逻辑和关系:
2.5 实战中的选择建议
说实话,在实际产品里,很少有人只用一种策略。我经手的项目,大部分都是混合策略:
- 初次对焦:先用全局搜索(大步长)快速定位大致范围,再用爬山算法精细调整。
- 跟踪对焦:目标移动时,用爬山算法在小范围内跟踪。
- 场景切换:检测到场景剧烈变化,重新触发全局搜索。
一个小技巧:爬山算法的停止条件别只设一个。我习惯同时检查三个条件:清晰度变化小于阈值、连续掉头次数超过限制、搜索时间超时。任何一个满足就停止,防止死循环。
好了,这一章就到这里。全局搜索和爬山算法是自动对焦的基石,理解透了,后面学更复杂的策略会轻松很多。记住:没有最好的策略,只有最适合场景的策略。