图像清晰度评价函数:四大金刚与安防实战
做自动对焦(AF)调优,说白了就是跟“清晰度”打交道。你得先知道什么算清晰,才能让镜头往清晰的方向跑。今天咱们聊聊安防场景下最常用的四个评价函数:Tenengrad、Laplacian、Brenner 和方差函数。
这四种方法,我这些年都踩过坑,也总结了一些经验。咱们一个一个来看。
1. Tenengrad 梯度函数
Tenengrad 的核心思想很简单:图像越清晰,边缘越锐利,梯度值越大。它用 Sobel 算子计算水平和垂直方向的梯度,然后求平方和。
数学表达:
Tenengrad = Σ (Gx² + Gy²)
其中 Gx、Gy 分别是 Sobel 算子在 x、y 方向的输出。
// C 风格伪代码
float tenengrad(uint8_t* img, int w, int h) {
float sum = 0;
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 += gx*gx + gy*gy;
}
}
return sum / (w*h);
}
安防场景适用性:
- 优点:对纹理丰富的场景(如监控画面中的树叶、砖墙、车牌)非常敏感,评价曲线单峰性好。
- 缺点:计算量大,在低端 IPC(网络摄像机)上跑会有点吃力。
- 我的经验:在枪机(固定摄像机)上,我习惯用 Tenengrad 做主评价函数。它稳定,不容易被噪声带偏。
2. Laplacian 算子
Laplacian 算子是二阶微分算子。它不关心边缘的方向,只关心灰度变化的剧烈程度。说白了,它像一个“变化检测器”。
// 标准 3x3 Laplacian 核
int kernel[3][3] = {
{0, 1, 0},
{1, -4, 1},
{0, 1, 0}
};
float laplacian_variance(uint8_t* img, int w, int h) {
float sum = 0;
for (int y = 1; y < h-1; y++) {
for (int x = 1; x < w-1; x++) {
int lap = img[(y-1)*w + x] + img[y*w + (x-1)] - 4*img[y*w + x]
+ img[y*w + (x+1)] + img[(y+1)*w + x];
sum += lap * lap;
}
}
return sum / (w*h);
}
避坑指南:Laplacian 对噪声非常敏感。我曾经在一个低照度场景下直接用 Laplacian,结果评价曲线全是毛刺,根本找不到峰值。后来加了高斯预滤波才解决。
安防场景适用性:
- 优点:计算简单,速度快,适合嵌入式平台。
- 缺点:对噪声敏感,在夜间或低照度场景下表现不佳。
- 建议:适合白天光照充足的场景,比如出入口闸机、收费站。
3. Brenner 函数
Brenner 函数只计算水平方向相邻像素的灰度差平方。它是最简单的梯度函数之一。
float brenner(uint8_t* img, int w, int h) {
float sum = 0;
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 / (w*h);
}
安防场景适用性:
- 优点:计算量极小,适合实时性要求高的场景。
- 缺点:只考虑水平方向,对垂直纹理不敏感。
- 我的经验:在球机(PTZ 摄像机)的快速对焦中,我常用 Brenner 做粗调,再用 Tenengrad 做精调。这样又快又准。
4. 方差函数
方差函数衡量图像灰度值的离散程度。清晰图像通常有更宽的灰度分布,方差更大。
float variance(uint8_t* img, int w, int h) {
float mean = 0;
for (int i = 0; i < w*h; i++) mean += img[i];
mean /= (w*h);
float var = 0;
for (int i = 0; i < w*h; i++) {
float diff = img[i] - mean;
var += diff * diff;
}
return var / (w*h);
}
注意:方差函数对光照变化非常敏感。同一个场景,白天和晚上的方差值可能差好几倍。如果你用固定阈值做对焦终止判断,很容易出问题。
安防场景适用性:
- 优点:实现简单,不需要卷积运算。
- 缺点:对光照敏感,单峰性不如梯度类函数。
- 建议:适合作为辅助评价函数,或者用在光照稳定的室内场景。
5. 四大函数对比
| 函数 | 计算量 | 抗噪性 | 单峰性 | 适用场景 |
|---|---|---|---|---|
| Tenengrad | 高 | 中 | 优 | 枪机、纹理丰富场景 |
| Laplacian | 中 | 差 | 良 | 白天、光照充足场景 |
| Brenner | 低 | 中 | 中 | 球机快速对焦、粗调 |
| 方差 | 低 | 差 | 差 | 室内、光照稳定场景 |
6. 安防场景选型建议
你想想看,安防场景千差万别。室内、室外、白天、黑夜、固定枪机、旋转球机……没有万能药。
- 室外枪机(白天+夜晚):我建议用 Tenengrad 做主函数,配合中值滤波去噪。夜晚切换到红外模式时,可以降低梯度阈值。
- 球机(PTZ):用 Brenner 做快速粗调,Tenengrad 做精调。我曾经在一个项目中,把 Brenner 的步长设成 10 个马达步,Tenengrad 设成 2 个步,对焦速度提升了 40%。
- 室内固定摄像机:Laplacian 或方差函数就够了。光照稳定,噪声小,没必要上复杂的算法。
- 车牌识别场景:必须用 Tenengrad。车牌字符的边缘信息非常关键,Brenner 和方差都容易丢细节。
核心原则:没有最好的函数,只有最合适的组合。实际项目中,我通常会同时计算 2-3 个评价函数,用加权融合的方式得到最终清晰度分数。
7. 知识体系图
下面这张图帮你理清四个函数的关系和选型逻辑: