3. AF评价函数:五大核心算法深度解析

自动对焦这事儿,说白了就是让镜头自己找到最清晰的那个位置。那问题来了——相机怎么知道当前画面清不清晰?

答案就是评价函数。它给每一帧画面打个分,分数越高,说明画面越锐利。我做了这么多年ISP,见过不少团队在评价函数上翻车,选错了函数,对焦又慢又抖。

今天咱们就把五种最常用的评价函数掰开揉碎讲清楚。

3.1 评价函数的核心逻辑

所有评价函数都基于同一个直觉:清晰图像边缘锐利,模糊图像边缘平滑。所以,我们只需要设计一个算子,能检测出图像中的高频分量——边缘越强,分数越高。

核心公式:

Focus_Value = Σ |G(x, y)|² 或 Σ |G(x, y)|

其中 G(x, y) 是像素 (x, y) 处的梯度幅值。

嗯,这里要注意:平方和会放大强边缘的贡献,对噪声也更敏感;值求和则相对温和。选哪个,取决于你的应用场景。

3.2 五种评价函数详解

3.2.1 Tenengrad函数

Tenengrad 是我个人最常用的一种。它基于 Sobel 算子,分别计算水平和垂直方向的梯度。

计算步骤:

  1. 用 Sobel 水平核 Gx 卷积图像
  2. 用 Sobel 垂直核 Gy 卷积图像
  3. 每个像素的梯度幅值 = sqrt(Gx² + Gy²)
  4. 对所有像素的梯度幅值求和
// Tenengrad 评价函数实现
float Tenengrad(uint8_t* img, int width, int height) {
    int sobel_x[3][3] = {{-1, 0, 1}, {-2, 0, 2}, {-1, 0, 1}};
    int sobel_y[3][3] = {{-1, -2, -1}, {0, 0, 0}, {1, 2, 1}};
    
    float sum = 0.0f;
    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++) {
                    int pixel = img[(y + ky) * width + (x + kx)];
                    gx += pixel * sobel_x[ky + 1][kx + 1];
                    gy += pixel * sobel_y[ky + 1][kx + 1];
                }
            }
            sum += sqrtf(gx * gx + gy * gy);
        }
    }
    return sum;
}

我的经验:Tenengrad 在纹理丰富的场景表现极佳,但在纯色区域(比如白墙)会失效。我曾经在一个室内监控项目里踩过这个坑——镜头对着白墙,对焦马达来回跑,就是找不到峰值。后来加了区域权重,只对画面中央 60% 区域做评价,问题才解决。

3.2.2 Laplacian函数

Laplacian 用的是二阶微分,对边缘的响应比一阶算子更敏感。说白了,它不光看梯度大小,还看梯度的变化率。

常用核:

// 标准 Laplacian 核
int laplacian[3][3] = {{0, -1, 0}, {-1, 4, -1}, {0, -1, 0}};

// 扩展 Laplacian 核(考虑对角线)
int laplacian_ext[3][3] = {{-1, -1, -1}, {-1, 8, -1}, {-1, -1, -1}};

注意:Laplacian 对噪声极其敏感。图像上哪怕有一点 sensor 噪声,都会被放大成伪边缘。我建议在使用前先做一次高斯滤波,或者配合降噪模块一起用。

3.2.3 Sobel算子

其实 Sobel 算子本身不是评价函数,它是 Tenengrad 的基础。但有些场景下,我们只取水平或垂直方向的 Sobel 响应,也能做对焦评价。

为什么单独拎出来说?

  • 水平Sobel:对垂直边缘敏感,适合拍摄竖条纹场景
  • 垂直Sobel:对水平边缘敏感,适合拍摄横条纹场景
  • 组合使用:就是 Tenengrad

我记得有一次做工业检测的自动对焦,产品表面全是水平纹理。用 Tenengrad 反而引入了垂直方向的噪声,换成纯垂直 Sobel,对焦精度直接提升了 15%。

3.2.4 Brenner函数

Brenner 函数是所有评价函数里计算量最小的。它只考虑相隔两个像素的灰度差。

公式:

// Brenner 评价函数
float Brenner(uint8_t* img, int width, int height) {
    float sum = 0.0f;
    for (int y = 0; y < height; y++) {
        for (int x = 0; x < width - 2; x++) {
            int diff = img[y * width + x] - img[y * width + x + 2];
            sum += diff * diff;  // 平方放大差异
        }
    }
    return sum;
}

适用场景:嵌入式平台、低功耗设备、实时性要求高的场景。Brenner 只需要做减法运算,没有乘法和开方,在 ARM Cortex-M 系列上跑起来飞快。

你想想看,如果你的设备每秒需要处理 30 帧 1080p 图像,用 Tenengrad 可能 CPU 就飙到 80% 了,换成 Brenner 能降到 20% 以下。代价是什么?精度略低,对弱边缘不敏感。

3.2.5 方差函数

方差函数不走梯度路线,它从统计角度评价清晰度。清晰图像的灰度分布更分散,方差更大;模糊图像的灰度分布更集中,方差更小。

公式:

// 方差评价函数
float Variance(uint8_t* img, int width, int height) {
    float mean = 0.0f;
    int total = width * height;
    
    // 计算均值
    for (int i = 0; i < total; i++) {
        mean += img[i];
    }
    mean /= total;
    
    // 计算方差
    float var = 0.0f;
    for (int i = 0; i < total; i++) {
        float diff = img[i] - mean;
        var += diff * diff;
    }
    return var / total;
}

避坑指南:我曾经在一个夜景对焦项目里用方差函数,结果发现画面越暗方差越小,镜头直接推到无穷远。后来才意识到——方差受光照影响太大。解决方案是先把图像做归一化,或者改用归一化方差(方差/均值²)。

3.3 五种函数对比

评价函数 计算量 精度 抗噪性 适用场景
Tenengrad 高 高 中 通用场景,纹理丰富
Laplacian 中 高 低 需要高灵敏度,配合降噪
Sobel(单方向) 中 中 中 特定纹理方向
Brenner 低 中 高 嵌入式、低功耗
方差 低 低 低 光照稳定场景

3.4 评价函数的选型策略

选哪个?我一般按这个思路来:

  1. 先看硬件资源:如果是手机 ISP 芯片,有硬件加速,直接上 Tenengrad 或 Laplacian。如果是单片机,老老实实用 Brenner。
  2. 再看场景:室内灯光稳定,方差函数也能用。户外光线变化大,必须用梯度类函数。
  3. 最后看精度要求:医疗影像、工业检测,精度优先,用 Laplacian 配合降噪。普通拍照,Tenengrad 足够了。

重要提醒:不要迷信单一函数。我做过一个项目,白天用 Tenengrad 表现完美,晚上噪点一上来就崩了。最后方案是:根据环境亮度动态切换——亮光用 Tenengrad,暗光用 Brenner+预处理降噪。

3.5 知识体系总览

AF评价函数知识体系 AF评价函数 Tenengrad Laplacian Sobel算子 Brenner 方差函数 核心思想:清晰图像边缘锐利 → 梯度/方差大;模糊图像边缘平滑 → 梯度/方差小 评价函数 = 对全图梯度或统计量求和,分数越高代表越清晰 选型三要素 硬件资源 应用场景 精度要求 实践建议 • 单一函数不够用?试试动态切换:亮光用Tenengrad,暗光用Brenner+降噪 • 计算资源紧张?优先考虑Brenner或方差函数,牺牲精度换速度 没有最好的函数,只有最合适的函数

3.6 实战中的坑与对策

最后分享几个我踩过的坑:

  • 峰值不唯一:有时候评价函数会出现多个峰值。我遇到过镜头在某个位置分数最高,但实际画面是模糊的。后来发现是 sensor 上的灰尘造成的伪边缘。解决方案:做中值滤波预处理。
  • 对焦窗口选择:不要对全图做评价。我一般只取画面中央 1/3 区域,或者根据人脸检测框动态调整窗口。
  • 帧率与精度的平衡:如果每帧都做全图评价,帧率会掉得很厉害。我的做法是:粗对焦时跳帧采样(每 4 像素取 1 个),精对焦时全分辨率计算。

一句话总结:评价函数是自动对焦的「眼睛」。选对了,对焦快准稳;选错了,马达跑断腿。建议你在项目初期就把这五种函数都跑一遍,看看实际数据,别凭感觉选。