图像清晰度评价函数:四大金刚与安防实战

做自动对焦(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. 知识体系图

下面这张图帮你理清四个函数的关系和选型逻辑:

图像清晰度评价函数知识体系 图像清晰度评价函数选型体系 清晰度评价函数 Tenengrad Laplacian Brenner 方差函数 计算量:高 抗噪性:中 单峰性:优 计算量:中 抗噪性:差 单峰性:良 计算量:低 抗噪性:中 单峰性:中 计算量:低 抗噪性:差 单峰性:差 推荐组合策略 枪机:Tenengrad(主)+ 中值滤波 球机:Brenner(粗调)+ Tenengrad(精调)