2. 图像清晰度评价函数:如何量化“清晰”这件事

自动对焦的核心,说白了就是回答一个问题:当前画面到底清不清晰?

你没法让相机自己“看”出画面好不好。你得给它一把尺子,让它去量。这把尺子,就是图像清晰度评价函数。

我刚开始做对焦算法时,觉得这步很简单——不就是算个梯度嘛。后来踩了不少坑才发现,选错评价函数,对焦系统就像近视眼没戴眼镜,怎么调都糊。

这一章,我把常用的评价函数分成三类来讲:基于梯度、基于频域、基于统计。每种我都会讲清楚原理、给出代码、再聊聊实际项目中的坑。

核心观点:没有完美的评价函数,只有最适合你场景的那一个。

2.1 基于梯度的评价函数

这类方法最直观。你想,一张清晰的图像,边缘肯定锐利,像素值变化剧烈。模糊的图像呢?边缘像被抹了一层黄油,变化平缓。

梯度,就是衡量这种变化快慢的数学工具。

2.1.1 Brenner 函数

Brenner 是所有梯度方法里最简单的。它只计算相隔两个像素的差值,然后平方求和。

公式长这样:

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

为什么隔两个像素?我个人的理解是:它想跳过相邻像素的噪声干扰,直接抓取真正的边缘跳变。

代码实现也很短:

def brenner(img):
    diff = img[:, 2:] - img[:, :-2]
    return np.sum(diff ** 2)
我的经验:Brenner 计算量极小,适合嵌入式平台。但它在弱纹理场景下表现一般,容易“看不清”。

2.1.2 Tenengrad 函数

Tenengrad 比 Brenner 讲究一些。它用 Sobel 算子提取水平和垂直方向的梯度,然后求平方和。

说白了,它考虑了两个方向的边缘信息,不像 Brenner 只盯着水平方向。

def tenengrad(img):
    gx = cv2.Sobel(img, cv2.CV_64F, 1, 0, ksize=3)
    gy = cv2.Sobel(img, cv2.CV_64F, 0, 1, ksize=3)
    return np.sum(gx**2 + gy**2)

我记得有一次做显微镜自动对焦,Brenner 死活找不到峰值,换成 Tenengrad 一下就稳了。为什么?因为显微镜图像里有很多斜向纹理,Brenner 只抓水平方向,漏掉了关键信息。

注意:Sobel 核大小(ksize)会影响结果。ksize=3 最常用,但噪声大时可以试试 ksize=5。

2.1.3 Laplacian 函数

Laplacian 用的是二阶导数。它不直接看灰度变化有多快,而是看变化率的变化率。

你可以这么理解:一阶导数告诉你边缘在哪,二阶导数告诉你边缘有多“陡峭”。

def laplacian(img):
    lap = cv2.Laplacian(img, cv2.CV_64F)
    return np.var(lap)  # 常用方差作为最终评价值

这里有个小技巧:很多人直接用 Laplacian 的值求和,但我习惯用方差。因为方差能更好地反映边缘的分布集中度,对焦峰值更尖锐。

方法 计算量 抗噪性 适用场景
Brenner 极低 中等 嵌入式、实时系统
Tenengrad 中等 较好 通用场景、显微镜
Laplacian 中等 较差 高对比度场景

2.2 基于频域的评价函数

梯度方法在空间域干活,频域方法则换个思路——把图像拆成不同频率的成分。

清晰图像的高频信息多(边缘、细节),模糊图像的高频信息少。所以,高频分量越强,图像越清晰。

2.2.1 FFT(快速傅里叶变换)

FFT 把图像从空间域变换到频率域。你会得到一个频谱图,中心是低频,往外是高频。

评价清晰度的方法很简单:计算高频区域的能量占比。

def fft_sharpness(img):
    f = np.fft.fft2(img)
    fshift = np.fft.fftshift(f)
    magnitude = np.abs(fshift)
    
    # 定义高频区域:中心半径 30% 以外的部分
    h, w = img.shape
    cy, cx = h // 2, w // 2
    mask = np.zeros((h, w), dtype=np.uint8)
    cv2.circle(mask, (cx, cy), int(min(h, w) * 0.3), 1, -1)
    high_freq = magnitude * (1 - mask)
    
    return np.sum(high_freq) / np.sum(magnitude)
避坑指南:我曾经在低光照场景下用 FFT 做评价,结果一塌糊涂。因为噪声在频域里也表现为高频,和真正的细节混在一起。后来我加了带通滤波,只保留中间一段频率,效果才好起来。

FFT 的优点是全局性好,不受局部纹理影响。缺点是计算量大,不适合实时系统。

2.3 基于统计的评价函数

这类方法不直接看边缘或频率,而是从图像的统计特性入手。

2.3.1 图像熵

熵,原本是信息论里的概念,用来衡量信息量。图像熵越大,说明灰度分布越均匀,细节越丰富。

模糊图像的灰度分布往往集中在几个值上,熵就小。清晰图像的灰度分布更散,熵就大。

def entropy(img):
    hist = cv2.calcHist([img], [0], None, [256], [0, 256])
    hist = hist / hist.sum()
    return -np.sum(hist * np.log2(hist + 1e-7))
注意:熵对光照变化很敏感。同一个场景,亮度不同,熵值可能差很多。使用时最好先做直方图均衡化。

2.3.2 灰度方差

方差衡量的是像素值的离散程度。清晰图像对比度高,像素值差异大,方差自然大。

这个方法简单到有点“粗暴”,但有时候意外地好用。

def variance(img):
    return np.var(img)

你想想看,一张纯色背景的照片,方差几乎为零,你根本没法用它来对焦。所以方差方法只适合纹理丰富的场景。

方法 原理 优点 缺点
图像熵 信息量度量 对纹理敏感 受光照影响大
灰度方差 像素离散度 计算极快 依赖纹理丰富度

2.4 知识体系总览

下面这张图,我把三类评价函数的核心逻辑和适用场景画在了一起。你可以把它当作选型时的参考地图。

图像清晰度评价函数分类体系 基于梯度 基于频域 基于统计 Brenner Tenengrad Laplacian FFT 高频能量比 图像熵 灰度方差 选型建议: • 实时嵌入式系统 → Brenner 或灰度方差(计算量最小) • 显微镜/工业检测 → Tenengrad(方向信息完整) • 高精度静态对焦 → FFT(全局性好,但慢) • 纹理丰富场景 → 图像熵(对细节敏感) • 噪声较大场景 → 先滤波再评价,或选 Tenengrad • 我个人的习惯:先用 Tenengrad 做粗调,再用 Laplacian 做精调

2.5 如何选择?我的实战建议

讲完理论,说点实在的。你在项目里到底该用哪个?

我一般按这个思路来:

  1. 先看硬件平台——如果是单片机,别想 FFT,老老实实用 Brenner 或方差。
  2. 再看场景纹理——纹理少?别用方差,试试熵或 Tenengrad。
  3. 最后看精度要求——要求高?用组合策略:粗调 + 精调。
一个真实案例:我之前做手机摄像头对焦,发现 Tenengrad 在室内灯光下表现很好,一到户外强光就出现“假峰值”。后来排查发现是 Sobel 算子对高光过曝区域产生了伪边缘。解决方案是:先做高光抑制,再做梯度计算。

嗯,这一章的内容就到这里。记住一句话:评价函数是自动对焦的“眼睛”,选对眼睛,路才能走对。