AF 算法核心:对焦评价函数与搜索策略
自动对焦这事儿,说白了就是让镜头自己找到最清晰的那个位置。我刚开始做摄像头驱动时,觉得 AF 不就是来回动一动嘛,后来才发现——评价函数选不对,搜索策略用不好,画面永远在“拉风箱”。
这一章我们聚焦两个核心问题:怎么判断画面清不清晰(评价函数),以及怎么快速找到最清晰的位置(搜索策略)。
对焦评价函数:清晰度的“尺子”
相机自己又没长眼睛,它怎么知道当前画面清不清晰?靠的是评价函数。评价函数输出一个数值,数值越大,代表图像高频信息越丰富——说白了就是边缘越锐利,画面越清晰。
我见过不少新手直接拿像素灰度方差当评价函数,结果在光照变化时频频误判。真正工程上常用的,是下面这三种基于梯度的算子。
1. Tenengrad 函数
Tenengrad 用的是 Sobel 算子,分别计算水平和垂直方向的梯度,然后求平方和。公式不复杂,但效果很稳。
// Tenengrad 评价函数实现
float tenengrad_eval(uint8_t *img, int w, int h) {
float sum = 0.0f;
int gx, gy;
for (int y = 1; y < h-1; y++) {
for (int x = 1; x < w-1; x++) {
// Sobel 水平梯度
gx = -img[(y-1)*w + (x-1)] + img[(y-1)*w + (x+1)]
-2*img[y*w + (x-1)] + 2*img[y*w + (x+1)]
-img[(y+1)*w + (x-1)] + img[(y+1)*w + (x+1)];
// Sobel 垂直梯度
gy = -img[(y-1)*w + (x-1)] -2*img[(y-1)*w + x] - img[(y-1)*w + (x+1)]
+ img[(y+1)*w + (x-1)] +2*img[(y+1)*w + x] + img[(y+1)*w + (x+1)];
sum += gx*gx + gy*gy;
}
}
return sum / (w * h); // 归一化,方便跨分辨率比较
}
2. Laplacian 函数
Laplacian 算子是二阶微分,对边缘的响应比 Sobel 更敏感。换句话说,它更容易“看到”微小的模糊变化。
// Laplacian 评价函数(简化版)
float laplacian_eval(uint8_t *img, int w, int h) {
float sum = 0.0f;
int lap;
for (int y = 1; y < h-1; y++) {
for (int x = 1; x < w-1; x++) {
lap = 4*img[y*w + x]
- img[(y-1)*w + x] - img[(y+1)*w + x]
- img[y*w + (x-1)] - img[y*w + (x+1)];
sum += abs(lap); // 取值,避免正负抵消
}
}
return sum / (w * h);
}
3. Sobel 函数(仅水平或垂直)
有时候我们只关心某个方向的清晰度。比如文字识别场景,水平方向的边缘更重要。这时候可以只算 Sobel 水平梯度。
| 评价函数 | 计算量 | 抗噪性 | 适用场景 |
|---|---|---|---|
| Tenengrad | 中 | 较好 | 通用场景,纹理丰富 |
| Laplacian | 低 | 较差 | 微距、高对比度场景 |
| Sobel(单方向) | 低 | 中等 | 条码、文字、特定纹理 |
搜索策略:怎么找到峰值
评价函数算出来了,接下来就是让镜头移动,找到评价函数的最大值。这就像爬山——你站在山脚,看不见山顶在哪,只能一步一步试探。
1. 爬山法(Hill Climbing)
爬山法是最常用的搜索策略。思路很简单:朝一个方向移动,如果评价函数值变大,就继续;如果变小,就回头。
// 爬山法搜索峰值(伪代码)
int hill_climbing_search() {
int pos = current_position;
float val = evaluate(pos);
int step = STEP_SIZE;
int direction = 1; // 1: 正向, -1: 反向
while (1) {
int next_pos = pos + direction * step;
float next_val = evaluate(next_pos);
if (next_val > val) {
// 还在上升,继续
pos = next_pos;
val = next_val;
} else {
// 下降或持平,换方向
direction = -direction;
step = step / 2; // 步长减半,精细搜索
if (step < MIN_STEP) break;
}
}
return pos;
}
2. 全局搜索(Global Search)
全局搜索就是暴力遍历整个对焦范围。虽然慢,但一定能找到全局最大值。
- 优点:不会陷入局部峰值,结果可靠。
- 缺点:速度慢。如果对焦步进有 1000 步,每一步都要算一次评价函数,耗时可能几百毫秒。
- 适用场景:初始化对焦、场景切换后的首次对焦。
我一般在产品里这样搭配:首次对焦用全局搜索,找到大致位置;后续跟踪用爬山法,微调即可。这样既保证了准确性,又兼顾了速度。
评价曲线长什么样?
下面这张图展示了理想情况下的评价函数曲线。横轴是镜头位置,纵轴是清晰度评价值。
实际项目中的选择建议
你可能会问:那我到底该用哪个?我个人的习惯是这样的——
- 先看场景:如果是室内、光照稳定、纹理丰富,Tenengrad + 爬山法就够了。
- 再看硬件:如果镜头步进电机精度高、响应快,可以用更细的步长做全局搜索。
- 最后看功耗:爬山法计算量小,适合电池供电的设备;全局搜索费电,适合插电的设备。
小结
这一章我们聊了两个核心模块:评价函数和搜索策略。评价函数是“眼睛”,搜索策略是“腿”。眼睛要准,腿要稳。Tenengrad、Laplacian、Sobel 各有优劣,爬山法和全局搜索各有适用场景。实际工程中,往往是组合使用——全局搜索找大方向,爬山法做精细调整。