第四章 AF评价函数:清晰度评价与对比度计算

各位工程师朋友,咱们今天聊聊AF调优里最核心的东西——评价函数。

说白了,自动对焦就是让镜头找到一个位置,让画面最清晰。但“清晰”这个词,机器怎么理解?它得有个数学标准。这个标准,就是评价函数。

我刚开始做AF的时候,觉得这东西不就是算算梯度嘛,有啥难的?结果第一个项目就被现实狠狠教育了一顿。嗯,咱们今天就把这块彻底讲透。

4.1 为什么需要评价函数?

你想想看,相机传感器看到的只是一堆像素值。它不知道什么是“清晰”,什么是“模糊”。

我们需要一个数值,来量化图像的清晰程度。这个数值就叫清晰度评价值。对焦的过程,就是让这个值达到最大。

核心思想:清晰图像边缘锐利,像素间变化剧烈;模糊图像边缘平滑,像素间变化平缓。评价函数就是捕捉这种“变化剧烈程度”的数学工具。

常见的评价函数分三类:

  • 基于梯度:Tenengrad、Laplacian、Brenner、Sobel
  • 基于频域:FFT、DCT
  • 基于统计:方差、熵

实际项目中,梯度类函数用得最多。原因很简单——计算快,效果稳。

4.2 Tenengrad函数

Tenengrad是我个人最常用的函数之一。它基于Sobel算子,计算图像在水平和垂直方向的梯度。

数学原理:

G(x,y) = sqrt(Gx² + Gy²)

其中:
Gx = Sobel_x * Image
Gy = Sobel_y * Image

Tenengrad值 = Σ G(x,y)   (对所有像素求和)

代码实现:

// C语言实现,适用于嵌入式平台
int32_t Tenengrad_Evaluate(uint8_t *img, int32_t width, int32_t height) {
    int32_t sum = 0;
    int32_t gx, gy;
    
    // Sobel核:水平方向
    // [-1, 0, 1]
    // [-2, 0, 2]
    // [-1, 0, 1]
    
    for (int y = 1; y < height - 1; y++) {
        for (int x = 1; x < width - 1; x++) {
            gx = -img[(y-1)*width + (x-1)] + img[(y-1)*width + (x+1)]
                 -2*img[y*width + (x-1)] + 2*img[y*width + (x+1)]
                 -img[(y+1)*width + (x-1)] + img[(y+1)*width + (x+1)];
            
            gy = -img[(y-1)*width + (x-1)] - 2*img[(y-1)*width + x] - img[(y-1)*width + (x+1)]
                 + img[(y+1)*width + (x-1)] + 2*img[(y+1)*width + x] + img[(y+1)*width + (x+1)];
            
            sum += abs(gx) + abs(gy);  // 用值近似,省去开方
        }
    }
    return sum;
}

我的经验:实际项目中,我一般用 abs(gx) + abs(gy) 代替 sqrt(gx² + gy²)。开方运算太慢了,对最终结果影响不大。这个优化在嵌入式平台上能省下30%的计算时间。

4.3 Laplacian函数

Laplacian算子是二阶微分算子。它不区分方向,直接检测图像中的边缘。

数学原理:

∇²f = ∂²f/∂x² + ∂²f/∂y²

离散形式(3x3核):
[0,  1, 0]
[1, -4, 1]
[0,  1, 0]

Laplacian值 = Σ |∇²f|

代码实现:

int32_t Laplacian_Evaluate(uint8_t *img, int32_t width, int32_t height) {
    int32_t sum = 0;
    int32_t lap;
    
    for (int y = 1; y < height - 1; y++) {
        for (int x = 1; x < width - 1; x++) {
            lap = img[(y-1)*width + x] 
                + img[y*width + (x-1)] 
                - 4*img[y*width + x] 
                + img[y*width + (x+1)] 
                + img[(y+1)*width + x];
            
            sum += abs(lap);
        }
    }
    return sum;
}

注意:Laplacian对噪声非常敏感。我曾经在一个低光照项目里用它,结果评价值乱跳,根本没法用。如果你发现Laplacian曲线抖动厉害,先检查一下图像噪声水平。

4.4 Brenner函数

Brenner函数是最简单的梯度函数之一。它只计算水平方向相邻像素的差异。

数学原理:

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

注意:跳过一个像素,步长为2

代码实现:

int32_t Brenner_Evaluate(uint8_t *img, int32_t width, int32_t height) {
    int32_t sum = 0;
    int32_t diff;
    
    for (int y = 0; y < height; y++) {
        for (int x = 0; x < width - 2; x++) {
            diff = img[y*width + x + 2] - img[y*width + x];
            sum += diff * diff;  // 平方放大差异
        }
    }
    return sum;
}

避坑指南:Brenner的步长为什么是2?因为步长为1时,对轻微模糊不敏感。步长太大又会丢失细节。我一般建议在1080p分辨率下用步长2,4K下可以考虑步长3或4。

4.5 三种函数的对比

函数 计算量 抗噪性 灵敏度 适用场景
Tenengrad 中 中 高 通用场景,推荐首选
Laplacian 低 低 中 低噪声、高对比度场景
Brenner 极低 高 低 资源受限的嵌入式平台

我个人习惯:先上Tenengrad。如果计算资源紧张,换Brenner。Laplacian我一般只用在实验室环境,或者作为辅助验证。

4.6 对比度计算

对比度是另一个重要的评价维度。它反映的是图像整体的明暗差异程度。

常用方法:

  • RMS对比度:计算像素值的标准差
  • Michelson对比度:(Imax - Imin) / (Imax + Imin)
  • 局部对比度:分块计算,取平均值

代码实现(RMS对比度):

float RMS_Contrast(uint8_t *img, int32_t width, int32_t height) {
    uint64_t sum = 0;
    uint64_t sum_sq = 0;
    int32_t total = width * height;
    
    for (int i = 0; i < total; i++) {
        sum += img[i];
        sum_sq += img[i] * img[i];
    }
    
    float mean = (float)sum / total;
    float variance = (float)sum_sq / total - mean * mean;
    
    return sqrtf(variance);  // 标准差即为RMS对比度
}

关键点:对比度不能单独作为对焦评价函数。为什么?因为一张全黑或全白的图,对比度是0,但它并不“模糊”。对比度通常和梯度函数配合使用,用来排除低对比度场景下的误判。

4.7 知识体系图

下面这张图,帮你理清本章的核心逻辑:

AF评价函数知识体系 AF评价函数知识体系 清晰度评价函数 梯度类函数 频域类函数 统计类函数 Tenengrad (Sobel) Laplacian (二阶) Brenner (步长2) FFT高频能量 DCT系数分析 图像方差 信息熵 对比度计算(辅助评价) RMS对比度(标准差) Michelson对比度 局部对比度(分块)

4.8 实战建议

最后,给各位几个实在的建议:

  1. 先跑仿真:在PC上用Python或Matlab把几种函数都跑一遍,看看曲线形状。我习惯用Tenengrad做基准,对比其他函数的表现。
  2. 关注曲线单峰性:好的评价函数,在从模糊到清晰再到模糊的过程中,应该只有一个明显的峰值。多峰曲线会导致对焦来回震荡。
  3. 注意计算耗时:嵌入式平台上,每帧的计算时间不能超过帧间隔的20%。否则会影响帧率。
  4. 组合使用:我最近一个项目里,用Brenner做粗对焦(快速找到大致位置),用Tenengrad做精对焦(精确到最佳位置)。效果比单用任何一种都好。

曾经踩过的坑:有一次我在一个户外监控项目里,直接用Laplacian做评价函数。白天效果很好,晚上画面噪声一大,评价值直接炸了。后来换成Tenengrad,配合一个简单的均值滤波预处理,问题才解决。记住:没有万能的评价函数,只有适合场景的评价函数。