3. 对焦评价函数(下):基于频域与统计的评价函数

上一节我们聊了基于梯度的评价函数,那是时域里的活儿。这一节我们换个视角——从频域和统计学的角度来审视图像清晰度。说白了,就是把图像从像素空间变换到频率空间,或者用统计量来刻画图像的“信息量”。

我个人习惯把这两种方法叫做“频谱派”和“统计派”。它们各有各的脾气,用对了地方就是神器,用错了……嗯,我踩过坑,后面会讲。

3.1 基于频域的评价函数

为什么要把图像变换到频域?你想想看,一张清晰的图像,边缘锐利,细节丰富——这些在频域里对应的是高频分量。而模糊的图像,高频被衰减了,能量集中在低频。所以,高频分量的多少,直接反映了图像的清晰度。

3.1.1 FFT(快速傅里叶变换)评价函数

FFT 把图像从空间域变换到频率域。我们通常用功率谱或幅值谱来量化高频能量。

核心思路:

  • 对图像块做 FFT,得到频谱图
  • 计算高频区域的能量总和
  • 能量越高,图像越清晰

代码实现其实不复杂:

// 简化版 FFT 清晰度评价
float evaluateSharpness_FFT(uint8_t* img, int width, int height) {
    // 1. 对图像做 FFT(实际工程中会用库函数)
    Complex* spectrum = fft2d(img, width, height);
    
    // 2. 计算高频能量(跳过直流分量和低频区域)
    float highFreqEnergy = 0.0f;
    int centerX = width / 2;
    int centerY = height / 2;
    int lowFreqRadius = width / 8;  // 低频区域半径
    
    for (int y = 0; y < height; y++) {
        for (int x = 0; x < width; x++) {
            int dx = abs(x - centerX);
            int dy = abs(y - centerY);
            float dist = sqrt(dx*dx + dy*dy);
            
            if (dist > lowFreqRadius) {
                highFreqEnergy += spectrum[y * width + x].magnitude();
            }
        }
    }
    
    free(spectrum);
    return highFreqEnergy;
}
性能警告:FFT 的计算量很大。我曾经在一个 1080p 的预览流上跑全图 FFT,结果帧率直接掉到个位数。后来改成只对中心 ROI 做 FFT,才勉强能用。移动端慎用!

3.1.2 DCT(离散余弦变换)评价函数

DCT 是 JPEG 压缩的核心,也是很多 ISP 模块的标配。相比 FFT,DCT 只有实数运算,没有复数,计算量小一些。

DCT 评价函数的思路和 FFT 类似:

  • 对图像块做 DCT 变换
  • 提取 AC 系数(非直流分量)
  • 计算 AC 系数的能量总和

这里有个小技巧——DCT 的系数矩阵天然按频率排列,左上角是直流,右下角是最高频。我们可以直接取右下角的一块区域作为高频能量:

float evaluateSharpness_DCT(uint8_t* block, int blockSize) {
    // 假设 blockSize = 8(8x8 DCT 块)
    float dctCoeff[64];
    dct2d(block, blockSize, dctCoeff);
    
    // 取高频系数:右下角 4x4 区域
    float highFreqEnergy = 0.0f;
    for (int y = 4; y < 8; y++) {
        for (int x = 4; x < 8; x++) {
            highFreqEnergy += fabs(dctCoeff[y * 8 + x]);
        }
    }
    
    return highFreqEnergy;
}
工程经验:DCT 评价函数特别适合用在 JPEG 编码器已经存在的平台上。我在一个 SoC 项目里,直接复用了硬件 DCT 引擎,几乎零开销就拿到了清晰度评价结果。这叫“借力打力”。

3.2 基于统计的评价函数

统计派的思路更直接——清晰的图像信息量大,模糊的图像信息量小。我们用统计量来量化这个“信息量”。

3.2.1 熵(Entropy)评价函数

熵来自信息论,衡量的是图像的“混乱程度”或“信息丰富度”。

计算公式:

H = -Σ p(i) * log2(p(i))

其中 p(i) 是灰度值 i 出现的概率(归一化直方图)。

代码实现:

float evaluateSharpness_Entropy(uint8_t* img, int size) {
    // 1. 统计直方图
    int hist[256] = {0};
    for (int i = 0; i < size; i++) {
        hist[img[i]]++;
    }
    
    // 2. 计算熵
    float entropy = 0.0f;
    for (int i = 0; i < 256; i++) {
        if (hist[i] > 0) {
            float p = (float)hist[i] / size;
            entropy -= p * log2f(p);
        }
    }
    
    return entropy;
}
关键洞察:熵对纹理丰富的场景非常敏感,但对纯色场景(比如蓝天、白墙)几乎不反应。我遇到过一个问题——镜头对着白墙,熵值几乎不变,对焦马达一直在原地抖动。后来加了梯度辅助判断才解决。

3.2.2 方差(Variance)评价函数

方差衡量的是像素值的离散程度。清晰的图像像素值变化大,方差大;模糊的图像像素值趋于平均,方差小。

公式很简单:

Var = (1/N) * Σ (I(i) - μ)²

其中 μ 是图像均值。

代码:

float evaluateSharpness_Variance(uint8_t* img, int size) {
    // 1. 计算均值
    float mean = 0.0f;
    for (int i = 0; i < size; i++) {
        mean += img[i];
    }
    mean /= size;
    
    // 2. 计算方差
    float variance = 0.0f;
    for (int i = 0; i < size; i++) {
        float diff = img[i] - mean;
        variance += diff * diff;
    }
    variance /= size;
    
    return variance;
}
注意:方差对光照变化非常敏感。同样的场景,亮度增加 10%,方差可能增加 20%。我在做室内外切换对焦时,方差曲线经常出现“假峰值”——其实是光照变了,不是对焦准了。解决方案是归一化方差(除以均值的平方)。

3.3 优缺点对比

我把这几种方法放在一起对比,方便你选型:

评价函数 优点 缺点 适用场景
FFT 频率信息完整,可精细分析 计算量大,有复数运算 静态场景、后处理分析
DCT 计算量小于 FFT,可复用硬件 块效应影响精度 有硬件 DCT 的平台
熵 信息论基础,对纹理敏感 纯色场景失效 纹理丰富的场景
方差 计算简单,实时性好 对光照敏感 光照稳定的场景

3.4 知识体系结构图

下面这张图帮你理清这几种评价函数的关系和适用边界:

频域与统计评价函数知识体系 频域与统计评价函数知识体系 对焦评价函数 频域派(频谱分析) 统计派(信息度量) FFT 复数运算·全频谱 DCT 实数运算·可硬件化 熵 信息论·纹理敏感 方差 计算简单·光照敏感 选型建议 • 追求精度且计算资源充足 → FFT(但移动端慎用) • 有硬件 DCT 引擎 → DCT(零开销评价) • 纹理丰富场景 → 熵(信息量直接反映清晰度) • 光照稳定且需要实时 → 方差(计算量最小) 核心原则:没有万能函数,只有合适场景

3.5 实战选型建议

说了这么多,到底怎么选?我给出几条实战经验:

  1. 移动端实时对焦:优先方差或梯度类函数。FFT 和 DCT 的计算量在移动端很难跑满 30fps。
  2. 安防监控(固定场景):可以用熵或 DCT。场景固定,光照变化小,统计方法很稳定。
  3. 医疗影像(后处理):FFT 是首选。精度要求高,不要求实时,可以慢慢算。
  4. 混合方案:我现在的项目里,粗搜用方差(快),精搜用 DCT(准)。两个函数互补,效果比单一函数好很多。
我的习惯:不管选哪种评价函数,我都会在实验室里跑一遍“场景覆盖测试”——对着不同纹理、不同光照、不同运动状态的场景录数据,然后离线分析评价函数的响应曲线。这一步能帮你发现很多“纸上谈兵”发现不了的问题。