第1章:图像清晰度评价函数(上)

各位同学好,我是你们这门课的老朋友。今天咱们聊聊自动对焦里最基础、也最核心的一块——图像清晰度评价函数

说白了,自动对焦就是让镜头动一动,找到画面最清楚的那个位置。但问题来了:怎么判断一张图到底清不清楚? 你总不能让算法像人一样看一眼说“嗯,这个锐利”吧?

所以我们需要一个数学工具,把“清晰度”这个主观感受,变成一个可以计算的数值。这就是清晰度评价函数干的事。

我个人习惯把评价函数分成三大类:基于梯度的、基于频域的、基于统计的。今天先讲前两类,第三类留到下节课。

图像清晰度评价函数体系 清晰度评价函数 基于梯度 Brenner · Tenengrad · Laplacian 基于频域 FFT · DCT 基于统计 Variance · Histogram 核心思想:清晰图像 → 边缘锐利 → 高频丰富 → 灰度变化剧烈 本章重点:梯度类 + 频域类

一、基于梯度的评价函数

先问大家一个问题:一张清晰的图像,和一张模糊的图像,最直观的区别在哪?

答案是边缘。清晰的图像边缘锐利,像素值变化剧烈;模糊的图像边缘平滑,像素值变化缓慢。而梯度,就是用来描述这种变化快慢的数学工具。

梯度越大,说明边缘越锐利,图像越清晰。这个逻辑很直接,对吧?

1.1 Brenner 函数

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

公式长这样:

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

为什么是相隔两个像素?不是相邻?

嗯,这里有个小门道。我刚开始做对焦算法时也纳闷过。后来在实际项目中调试才发现:相邻像素的差值太小了,容易受噪声干扰。相隔两个像素,既能捕捉到边缘信息,又能滤掉一部分高频噪声。说白了,这是一种简单的带通滤波思想。

我的经验: Brenner 计算量极小,适合嵌入式平台。我在一个低功耗摄像头项目里用过它,跑在 30fps 毫无压力。但要注意,它对纹理丰富的场景效果不错,对纯色场景就有点力不从心了。

1.2 Tenengrad 函数

Tenengrad 比 Brenner 更“正规”一些。它使用 Sobel 算子计算水平和垂直方向的梯度,然后求平方和。

代码实现大概是这样的:

// 水平 Sobel 核
int sobel_x[3][3] = {
    {-1, 0, 1},
    {-2, 0, 2},
    {-1, 0, 1}
};

// 垂直 Sobel 核
int sobel_y[3][3] = {
    {-1, -2, -1},
    { 0,  0,  0},
    { 1,  2,  1}
};

// 对每个像素计算梯度幅值
for (int y = 1; y < height-1; y++) {
    for (int x = 1; x < width-1; x++) {
        int gx = 0, gy = 0;
        for (int ky = -1; ky <= 1; ky++) {
            for (int kx = -1; kx <= 1; kx++) {
                gx += sobel_x[ky+1][kx+1] * img[y+ky][x+kx];
                gy += sobel_y[ky+1][kx+1] * img[y+ky][x+kx];
            }
        }
        sum += gx*gx + gy*gy;
    }
}

Tenengrad 的优点是方向性更全面,水平和垂直边缘都能捕捉到。我在做手机摄像头对焦时,Tenengrad 是默认选项之一。

避坑指南: 我曾经在一个项目中直接用 Tenengrad 的原始输出做对焦曲线,结果发现曲线有很多毛刺。后来排查发现,是图像传感器有固定模式噪声(FPN),Sobel 算子把噪声也当成边缘了。解决方案是:先做一次高斯模糊降噪,再计算 Tenengrad。

1.3 Laplacian 函数

Laplacian 用的是二阶导数。一阶导数看变化快慢,二阶导数看变化加速度。边缘处的一阶导数是极值,二阶导数是过零点。

常用的 Laplacian 核是 3x3 的:

Laplacian 核:
[ 0, -1,  0]
[-1,  4, -1]
[ 0, -1,  0]

或者带对角线的版本:

[ 1,  1,  1]
[ 1, -8,  1]
[ 1,  1,  1]

Laplacian 对孤立点、细线、边缘端点特别敏感。但它的缺点也很明显:对噪声极其敏感。你想想看,二阶导数放大了高频成分,而噪声恰恰也是高频的。

三种梯度函数的对比:
函数 计算量 抗噪性 适用场景
Brenner 极低 中等 嵌入式、实时系统
Tenengrad 中等 较好 通用场景
Laplacian 较差 纹理丰富、低噪声场景

二、基于频域的评价函数

梯度方法是在空间域里看问题。但换个角度想:清晰图像的高频成分多,模糊图像的高频成分少。这不就是频域分析最擅长的事吗?

频域方法的核心思想:把图像从空间域变换到频率域,然后看高频分量的能量有多大。

2.1 FFT(快速傅里叶变换)

FFT 是频域分析的经典工具。对图像做二维 FFT 后,低频分量集中在中心,高频分量在四周。清晰度越高,高频区域的能量越强。

评价函数可以这样设计:

1. 对图像做二维 FFT,得到频谱 F(u, v)
2. 计算频谱的幅值 |F(u, v)|
3. 设计一个高通滤波器,只保留高频部分
4. 对高频部分的幅值求和,作为清晰度得分

但说实话,FFT 在嵌入式系统里不太实用。为什么?计算量太大了。一张 1080p 的图像,做一次 FFT 要几毫秒甚至几十毫秒,对焦过程中可能要算几十次,这谁顶得住?

我在一个工业相机项目里试过 FFT 做对焦,最后放弃了。不是因为不准,而是因为太慢了。后来换成了 Tenengrad,效果差不多,速度快了一个数量级。

什么时候用 FFT? 如果你在做离线分析,或者对实时性要求不高(比如静态场景对焦),FFT 是个好选择。它比梯度方法更鲁棒,对噪声不敏感。

2.2 DCT(离散余弦变换)

DCT 是 FFT 的“近亲”,但更实用。JPEG 压缩用的就是 DCT,原因很简单:DCT 是实数变换,计算量比 FFT 小

DCT 的评价思路和 FFT 类似:

1. 对图像分块(比如 8x8 或 16x16)
2. 对每个块做 DCT
3. 提取高频系数(比如右下角的系数)
4. 对所有块的高频系数求和

分块的好处是:可以局部评价清晰度。比如画面中心区域清晰、边缘模糊,分块 DCT 能捕捉到这种空间变化。而全局 FFT 只能给出一个整体值。

我记得有一次做车载摄像头对焦,场景里经常有大面积天空(低频区域)。用全局 FFT 时,天空部分的低频能量淹没了边缘的高频信息,导致对焦不准。后来改成 16x16 分块 DCT,只取高频系数最大的几个块做评价,效果立竿见影。

FFT vs DCT 对比:
  • FFT:复数变换,计算量大,频率分辨率高,适合离线分析
  • DCT:实数变换,计算量适中,可分块处理,适合嵌入式实现
  • 共同点:都能有效区分清晰和模糊图像,对噪声鲁棒性优于梯度方法

三、实际项目中的选择建议

讲了这么多,你可能会问:那我到底该用哪个?

我的建议是分三步走:

  1. 先看平台:如果是单片机、低端 DSP,老老实实用 Brenner 或简单的 Laplacian。别想着上 FFT,跑不动的。
  2. 再看场景:室内灯光稳定、纹理丰富,Tenengrad 就很好。户外光线变化大、有噪声,考虑 DCT 或带预处理的梯度方法。
  3. 最后看精度:如果对焦精度要求极高(比如显微镜、工业检测),可以考虑组合使用——先用 Brenner 粗调,再用 Tenengrad 精调。
一个常见的坑: 不要迷信“越复杂的函数效果越好”。我曾经在一个项目中,用 Laplacian 效果不如 Brenner。后来分析发现,图像传感器噪声较大,Laplacian 把噪声当成了边缘,反而干扰了对焦。有时候,简单就是美

好了,今天的内容就到这里。梯度类和频域类的评价函数,是自动对焦算法的基石。下一节课我们继续聊基于统计的评价函数,以及如何在实际项目中做选择。

记住:没有最好的函数,只有最合适的函数。多动手试试,你会有自己的体会。


公众号:蓝海资料掘金营,微信deep3321