4、对焦评价函数:梯度、方差与熵

各位同学,欢迎来到第四章。前面我们聊了低光照下对焦的难点,也讲了图像预处理怎么把信号质量提上来。那接下来要解决一个核心问题:怎么判断当前画面是不是最清晰的?

说白了,我们需要一把“尺子”,去量每帧图像的清晰度。这把尺子,就是对焦评价函数(Focus Measure)。

我刚开始做嵌入式相机调试时,觉得这玩意儿不就是算个梯度嘛,有啥难的?后来在暗光下跑了一遍,才发现坑多得很。有的函数对噪声敏感得要命,有的算得慢,有的干脆在低对比度场景下“失明”。

今天我们就把这几个常用的评价函数掰开揉碎了讲。你理解透了,后面调AF算法才能做到心中有数。

4.1 梯度类函数:边缘越锐,值越大

图像清晰度,本质上就是边缘的锐利程度。边缘越陡峭,像素之间的灰度变化越剧烈,梯度值就越大。所以,梯度类函数是最直观、最常用的对焦评价手段。

我个人的习惯是,在项目初期先用梯度函数快速验证对焦流程,等系统跑通了再根据场景换更鲁棒的方案。

4.1.1 Brenner 梯度函数

Brenner 是最简单的梯度函数之一。它只计算相隔两个像素的灰度差,然后平方求和。

公式长这样:

F = Σ |I(x+2, y) - I(x, y)|²

为什么只隔两个像素?因为相隔太近(比如1个像素),容易受噪声干扰;相隔太远,又会丢失细节。Brenner 这个“步长2”的设计,算是一种经验折中。

我的经验:Brenner 计算量极小,非常适合资源受限的嵌入式平台。但它在低光照下表现一般——因为暗光噪声会把真实的边缘变化淹没掉。我曾经在一个手机项目里用它做预览对焦,结果画面稍微暗一点就开始来回“拉风箱”。

4.1.2 Sobel 与 Laplacian 算子

Sobel 和 Laplacian 是更“正规”的边缘检测算子。它们通过卷积核计算图像的一阶或二阶导数。

Sobel 算子:计算水平和垂直两个方向的梯度,然后取模或平方和。

Gx = [[-1, 0, 1],
      [-2, 0, 2],
      [-1, 0, 1]]

Gy = [[-1, -2, -1],
      [ 0,  0,  0],
      [ 1,  2,  1]]

F = Σ (Gx² + Gy²)

Laplacian 算子:直接计算二阶导数,对边缘的响应更敏感。

L = [[ 0, -1,  0],
     [-1,  4, -1],
     [ 0, -1,  0]]

F = Σ |L|

这两者的区别,我画了张图帮你理解:

Sobel vs Laplacian 响应对比 Sobel 一阶梯度 响应边缘强度 对噪声较鲁棒 适合低光照场景 Laplacian 二阶梯度 响应边缘+纹理 对噪声极敏感 暗光下容易误判 低光照下,Sobel 比 Laplacian 更稳定,但 Laplacian 对焦峰值更尖锐
关键结论:在低光照场景下,我建议优先用 Sobel。Laplacian 虽然峰值更“尖”,对焦精度高,但它对噪声的放大效应太明显。暗光下一旦 SNR 低于 20dB,Laplacian 曲线就开始剧烈抖动。

4.1.3 Tenengrad 梯度函数

Tenengrad 本质上就是 Sobel 的“升级版”。它用 Sobel 算子算出每个像素的梯度模值,然后对所有像素的模值求和。

F = Σ (Gx² + Gy²)

嗯,公式和 Sobel 一样?对,但 Tenengrad 强调的是全局梯度能量。它不关心边缘方向,只关心整体梯度强度。

我记得在一个安防摄像头的项目中,场景是夜间停车场,光照极低。我们用 Tenengrad 做粗对焦,配合后面要讲的方差函数做精对焦,效果出奇地好。为什么?因为 Tenengrad 对均匀区域的噪声不敏感,它只响应真正的边缘。

4.2 统计类函数:方差与熵

梯度函数依赖边缘,那如果场景本身就没有明显边缘呢?比如拍一面白墙,或者拍天空。这时候梯度函数基本就“躺平”了。

统计类函数就是用来补这个短板的。

4.2.1 灰度方差函数

方差衡量的是图像像素值的离散程度。图像越清晰,像素之间的差异越大,方差就越大。

μ = (1/N) * Σ I(x,y)
F = Σ (I(x,y) - μ)²

这个函数的好处是:不依赖边缘。哪怕是一张纹理很弱的图,只要对焦准确,像素之间的对比度也会提升,方差就会变大。

注意坑:方差函数对光照变化非常敏感。你想想看,如果场景亮度突然变化(比如有人开了灯),方差会瞬间跳变,AF 系统会误以为对焦状态变了。我曾经在这个问题上吃过亏——在户外场景下,云飘过来遮住太阳,方差曲线直接“崩了”,镜头开始乱跑。

4.2.2 熵函数

熵来自信息论,衡量的是图像的信息量。熵越大,说明图像包含的信息越丰富,通常意味着更清晰。

p(i) = 灰度值为 i 的像素占比
F = - Σ p(i) * log₂(p(i))

熵函数在纹理丰富的场景下表现很好,但在低光照下有个致命问题:暗光噪声会引入大量虚假的灰度级,导致熵值虚高。你明明没对焦,熵却告诉你“图像很清晰”。

所以,我个人很少在低光照场景下单独用熵函数。它更适合作为辅助判断,比如结合梯度函数一起用。

4.3 各函数对比与选型建议

说了这么多,到底该用哪个?我整理了一张表,方便你对照选择:

评价函数 计算量 低光照鲁棒性 对焦峰值锐度 适用场景
Brenner 极低 差 中等 高光照、资源受限平台
Sobel 低 较好 中等 通用场景,低光照首选
Laplacian 低 差 尖锐 高 SNR 场景,精度要求高
Tenengrad 低 好 中等 低光照、边缘丰富场景
方差 极低 中等 平缓 纹理弱、均匀场景
熵 中等 差 平缓 纹理丰富、高光照场景
我的调优建议:在低光照场景下,我通常用 Tenengrad + 方差 的组合。Tenengrad 负责粗对焦,快速找到大致位置;方差负责精对焦,在峰值附近做微调。这样既保证了速度,又兼顾了精度。

4.4 实战中的避坑指南

最后,分享几个我踩过的坑,你遇到了可以直接绕开:

  • 不要只用一种函数——低光照场景变化多端,单一函数很容易失效。至少准备两套方案,根据场景自动切换。
  • 注意 ROI 的选择——对焦评价函数只对画面中心区域计算,还是全图计算?我建议用中心权重区域,因为大多数拍摄主体都在画面中央。全图计算容易被背景干扰。
  • 预处理不能省——在计算评价函数之前,一定要做降噪处理。否则你算出来的“清晰度”,有一半是噪声贡献的。
  • 曲线平滑很重要——原始的评价函数曲线往往有很多毛刺。我习惯加一个滑动窗口平均,或者用卡尔曼滤波做平滑,这样对焦马达才不会来回抖动。