第四章:反差对焦原理——图像清晰度评价函数与爬山算法详解
各位同学,欢迎来到自动对焦的核心章节。
前面我们聊了基础光学和传感器,今天要讲的是反差对焦——说白了,就是让相机自己判断“哪张照片最清楚”。
我刚开始做嵌入式相机时,总觉得对焦是个玄学。后来踩了无数坑才明白,核心就两件事:怎么算清晰,以及怎么找到最清晰的位置。
4.1 图像清晰度评价函数
先问个问题:一张模糊的照片和一张清晰的照片,在数学上有什么区别?
答案是边缘的锐利程度。清晰图像的边缘变化剧烈,模糊图像的边缘平缓。评价函数就是量化这个“剧烈程度”的。
4.1.1 Tenengrad 函数
Tenengrad 是我个人最常用的评价函数之一。它基于 Sobel 算子,计算图像在水平和垂直方向的梯度。
公式其实不复杂:
Tenengrad = Σ ( Gx² + Gy² )
其中 Gx 和 Gy 分别是 Sobel 算子在 x 和 y 方向的卷积结果。
核心思想:梯度平方和越大,图像越清晰。
我在项目中遇到过一个问题:用 Tenengrad 评价夜景照片时,噪声会被放大,导致误判。后来我加了一个高斯预滤波,效果好了很多。
4.1.2 Laplacian 函数
Laplacian 用的是二阶导数。它比 Tenengrad 更敏感,但也更容易受噪声影响。
Laplacian = Σ | L(x, y) |
其中 L(x, y) 是 Laplacian 算子卷积结果。
我的经验:Laplacian 适合纹理丰富的场景。如果画面很平滑(比如拍白墙),它的表现就不如 Tenengrad。
4.1.3 Brenner 函数
Brenner 是最简单的评价函数之一。它只计算相邻像素的灰度差。
Brenner = Σ | I(x+2, y) - I(x, y) |
你没看错,就这么简单。它只考虑相隔两个像素的差值。
为什么是相隔两个像素?我当年也纳闷。后来想明白了:相隔一个像素对噪声太敏感,相隔三个又太迟钝。两个像素是个折中。
| 评价函数 | 计算量 | 抗噪性 | 适用场景 |
|---|---|---|---|
| Tenengrad | 中 | 中 | 通用场景 |
| Laplacian | 低 | 低 | 纹理丰富 |
| Brenner | 极低 | 高 | 嵌入式实时 |
注意:不要迷信某个函数。我建议你在项目中至少测试两种,看哪个更稳定。
4.2 爬山算法详解
有了评价函数,下一步就是找到峰值。爬山算法是最直观的方法。
4.2.1 基本原理
想象你站在一座山上,蒙着眼,只能通过脚底的坡度判断方向。你会怎么走?
没错,往坡上走。爬山算法就是这个逻辑:
- 朝一个方向移动镜头
- 计算当前清晰度值
- 如果值变大,继续同方向移动
- 如果值变小,反方向移动
- 重复直到找到峰值
4.2.2 步长策略
步长是个关键参数。步长太大,容易跳过峰值;步长太小,对焦太慢。
我常用的策略是粗调+细调:
- 先用大步长快速接近峰值区域
- 再用小步长精确找到峰值
// 伪代码示例
step_size = LARGE_STEP
while (step_size > MIN_STEP) {
move_lens(step_size)
current_value = evaluate_sharpness()
if (current_value > previous_value) {
continue
} else {
move_lens(-step_size * 2) // 回头
step_size = step_size / 2 // 步长减半
}
}
避坑指南:我曾经在步长减半时没处理好边界条件,导致镜头在峰值附近来回震荡。后来加了死区判断,问题才解决。
4.2.3 局部峰值问题
爬山算法有个致命弱点:容易陷入局部峰值。
比如你拍一个物体,背景是虚化的。评价函数可能会出现多个小峰值。爬山算法可能停在第一个小峰上,而不是真正的对焦位置。
怎么解决?我一般用两种方法:
- 多起点爬山:从不同位置开始爬,取最高值
- 全局扫描+局部爬山:先大步扫描整个范围,找到几个候选区域,再精细爬山
4.3 知识体系总览
下面这张图是我自己整理的,把本章的核心逻辑串起来了:
从图像输入开始,经过评价函数计算清晰度,再用爬山算法找到峰值位置。每一步都有坑,但掌握了这些,你就掌握了反差对焦的核心。
总结一下:
- Tenengrad 用 Sobel 梯度,通用性好
- Laplacian 用二阶导数,敏感但怕噪声
- Brenner 最简单,适合嵌入式实时系统
- 爬山算法要配合步长策略,注意局部峰值
嗯,这一章就到这里。内容不少,但都是实战中反复验证过的。你回去可以写个小程序,用摄像头采集几帧图像,试试不同评价函数的效果。亲手跑一遍,比看十遍书都管用。