3. 评价函数选型:梯度、统计与频域
自动对焦的核心,说白了就是找一个靠谱的“清晰度判据”。镜头推来推去,到底哪一帧最清楚?你得有个打分机制。这个打分机制,就是评价函数。
低光照下,信号本身就弱,噪声还特别多。选错了评价函数,AF 系统就像在黑夜里找东西——明明焦点就在那儿,它却死活不认。我这些年调过的低光 AF 项目,踩坑最多的就是这一步。
今天我把主流评价函数分成三类来讲:基于梯度、基于统计、基于频域。咱们一个一个看。
3.1 基于梯度的函数
这类函数的核心逻辑很简单:图像越清晰,边缘越锐利,梯度值越大。低光照下,边缘信息会被噪声淹没,所以梯度类函数对噪声特别敏感。
Sobel 算子
Sobel 算水平/垂直两个方向的梯度,然后求和。我习惯用 3×3 的核,计算量小,效果也还行。
// Sobel 梯度计算(简化版)
float sobel_score(uint8_t* img, int w, int h) {
float sum = 0.0f;
for (int y = 1; y < h-1; y++) {
for (int x = 1; x < w-1; x++) {
int gx = -img[(y-1)*w + (x-1)] + img[(y-1)*w + (x+1)]
-2*img[y*w + (x-1)] + 2*img[y*w + (x+1)]
-img[(y+1)*w + (x-1)] + img[(y+1)*w + (x+1)];
int gy = -img[(y-1)*w + (x-1)] -2*img[(y-1)*w + x] - img[(y-1)*w + (x+1)]
+img[(y+1)*w + (x-1)] +2*img[(y+1)*w + x] + img[(y+1)*w + (x+1)];
sum += sqrtf(gx*gx + gy*gy);
}
}
return sum / (w * h);
}
Laplacian 算子
Laplacian 是二阶微分算子,对边缘的响应比 Sobel 更尖锐。但问题也很明显——它对噪声的放大更厉害。
你想想看,低光照图像里,噪声的梯度往往和真实边缘的梯度差不多大。Laplacian 一上去,噪声和边缘一起被放大,评价函数就乱跳了。
Tenengrad 函数
Tenengrad 其实是 Sobel 的变种。它用 Sobel 算梯度,然后只保留超过某个阈值的梯度值。这样做的好处是——可以滤掉一部分噪声引起的弱梯度。
公式长这样:
// Tenengrad 评分
float tenengrad_score(uint8_t* img, int w, int h, int threshold) {
float sum = 0.0f;
for (int y = 1; y < h-1; y++) {
for (int x = 1; x < w-1; x++) {
int gx = ...; // Sobel gx
int gy = ...; // Sobel gy
float mag = sqrtf(gx*gx + gy*gy);
if (mag > threshold) {
sum += mag * mag; // 平方放大差异
}
}
}
return sum;
}
阈值怎么选?我一般取图像平均梯度的 1.5~2 倍。太低滤不掉噪声,太高会把弱边缘也滤掉。
3.2 基于统计的函数
这类函数不直接看边缘,而是看图像的整体统计特性。低光照下,统计量往往比梯度更鲁棒。
Brenner 函数
Brenner 只计算相隔两个像素的灰度差。计算量极小,适合嵌入式实时处理。
// Brenner 评分
float brenner_score(uint8_t* img, int w, int h) {
float sum = 0.0f;
for (int y = 0; y < h; y++) {
for (int x = 0; x < w-2; x++) {
int diff = img[y*w + x] - img[y*w + x + 2];
sum += diff * diff;
}
}
return sum;
}
Variance(方差)
方差衡量图像的对比度。图像越清晰,像素值分布越分散,方差越大。
但低光照下有个问题:整体偏暗,像素值集中在低灰度区,方差本身就小。再加上噪声会把方差“撑大”,导致评价函数在失焦时反而得分不低。
我建议:用方差做评价函数时,一定要配合自动增益控制(AGC)。否则暗场景下方差曲线太平,没法用。
Entropy(熵)
熵来自信息论,衡量图像的“信息量”。清晰图像纹理丰富,熵值高;模糊图像信息少,熵值低。
理论上熵很完美。但实际用起来,计算量太大——你要统计灰度直方图,还要算对数。在嵌入式平台上,跑一帧熵的时间,够跑三次 Sobel 了。
3.3 基于频域的函数
这类函数把图像变换到频域,看高频分量有多少。清晰图像高频多,模糊图像高频少。这个逻辑在低光照下依然成立——但噪声也在高频区。
FFT(快速傅里叶变换)
FFT 能精确分析频谱。但计算量巨大,一帧 1080p 图像做 FFT,在普通嵌入式芯片上可能要几十毫秒。
我一般只取 FFT 结果中的高频环带(比如半径 0.4~0.8 归一化频率的区域)求和,作为清晰度评分。
// FFT 高频评分(伪代码)
float fft_highfreq_score(complex* fft_result, int w, int h) {
float sum = 0.0f;
float cx = w/2, cy = h/2;
for (int y = 0; y < h; y++) {
for (int x = 0; x < w; x++) {
float r = sqrtf((x-cx)*(x-cx) + (y-cy)*(y-cy)) / (w/2);
if (r > 0.4f && r < 0.8f) {
sum += cabs(fft_result[y*w + x]);
}
}
}
return sum;
}
DCT(离散余弦变换)
DCT 是 JPEG 压缩的核心变换。它比 FFT 计算量小一些,而且能量集中——大部分信息集中在低频系数里。
我常用的做法:只取 DCT 的左上角 8×8 或 16×16 块的高频系数,求和作为评分。这样计算量可控,效果也还行。
3.4 三类函数的对比
| 类别 | 代表函数 | 低光鲁棒性 | 计算量 | 适用场景 |
|---|---|---|---|---|
| 梯度类 | Sobel / Laplacian / Tenengrad | 中(需降噪配合) | 低~中 | 通用场景,低光需预处理 |
| 统计类 | Brenner / Variance / Entropy | 高(Brenner 尤佳) | 低(熵除外) | 低光、低功耗嵌入式 |
| 频域类 | FFT / DCT | 低(噪声干扰大) | 高 | 离线标定、高算力平台 |
3.5 评价函数选型框架
下面这张图是我自己总结的选型流程。每次做新项目,我都会先走一遍这个框架。
3.6 我的选型建议
做了这么多年 AF,我总结了几条铁律:
- 低光照下,优先选统计类函数。Brenner 是我的首选,计算量小,抗噪性好。
- 梯度类函数必须配降噪。我习惯用双边滤波或引导滤波做预处理,再用 Sobel 或 Tenengrad。
- 频域类函数慎用。除非你的平台算力足够,而且场景噪声可控。否则 FFT 和 DCT 在低光下就是灾难。
- 不要迷信单一函数。我现在的做法是:粗搜索用 Brenner,细搜索用 Tenengrad。两个函数互补,效果比单个好得多。