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 知识体系结构图
下面这张图帮你理清这几种评价函数的关系和适用边界:
3.5 实战选型建议
说了这么多,到底怎么选?我给出几条实战经验:
- 移动端实时对焦:优先方差或梯度类函数。FFT 和 DCT 的计算量在移动端很难跑满 30fps。
- 安防监控(固定场景):可以用熵或 DCT。场景固定,光照变化小,统计方法很稳定。
- 医疗影像(后处理):FFT 是首选。精度要求高,不要求实时,可以慢慢算。
- 混合方案:我现在的项目里,粗搜用方差(快),精搜用 DCT(准)。两个函数互补,效果比单一函数好很多。
我的习惯:不管选哪种评价函数,我都会在实验室里跑一遍“场景覆盖测试”——对着不同纹理、不同光照、不同运动状态的场景录数据,然后离线分析评价函数的响应曲线。这一步能帮你发现很多“纸上谈兵”发现不了的问题。