4、窗口权重分配:均匀权重、中心权重、高斯权重、自适应权重算法

权重分配这事儿,说白了就是决定窗口里每个像素的「话语权」。

你想想看,一个对焦窗口少说也有几十乘几十个像素。如果每个像素都平等投票,那边缘的噪点、低反差的区域,就会跟中心的高频细节抢风头。我早年做手机相机调试时,就吃过这个亏——明明中心有纹理,窗口边缘一片天空,结果对焦马达来回拉风箱,就是锁不住。

所以,权重分配不是数学游戏,它直接决定了你的对焦系统「听谁的」。下面这四种算法,我按从简单到智能的顺序讲。

4.1 均匀权重:最朴素的方案

均匀权重,也叫矩形窗或Box窗。窗口内每个像素的权重都是1/N,N是像素总数。

// 均匀权重示例
float uniform_weight(int x, int y, int win_w, int win_h) {
    return 1.0f / (win_w * win_h);
}

优点很明显:计算量极小,适合硬件实现。缺点也很致命:边缘像素和中心像素一样重要。

避坑:我曾经在一个夜景场景里用过均匀权重,结果窗口边缘的暗电流噪声被平等对待,对焦评分曲线出现大量毛刺。后来换成中心权重,曲线立刻平滑了。

什么时候用均匀权重?我个人习惯是:

  • 场景纹理分布均匀(比如拍书本、白墙上的文字)
  • 窗口尺寸很小(比如16x16以下)
  • 硬件资源极度受限(比如低端MCU)

4.2 中心权重:聚焦画面核心

中心权重,顾名思义,越靠近窗口中心的像素权重越大。最常见的实现是线性衰减或二次衰减。

// 中心权重示例(线性衰减)
float center_weight(int x, int y, int cx, int cy, int win_w, int win_h) {
    float dx = (float)(x - cx) / (win_w / 2);
    float dy = (float)(y - cy) / (win_h / 2);
    float dist = sqrtf(dx*dx + dy*dy);
    return fmaxf(0.0f, 1.0f - dist);  // 线性衰减到0
}

中心权重的核心思想是:用户通常把主体放在画面中央。所以中央像素的「意见」更重要。

权重类型 中心像素权重 边缘像素权重 计算开销
均匀权重 1/N 1/N 极低
线性中心权重 1.0 0.0~0.3 低
二次中心权重 1.0 0.0~0.1 低

经验:我建议中心权重的衰减半径不要设得太小。如果只有中心10%的区域有高权重,一旦主体稍微偏移,对焦就会失败。一般取窗口宽高的30%~50%作为有效半径比较稳妥。

4.3 高斯权重:平滑且自然

高斯权重,就是用二维高斯函数作为权重分布。它比线性衰减更平滑,也更符合光学系统的特性——镜头本身的MTF就是中心高、边缘低。

// 高斯权重示例
float gaussian_weight(int x, int y, int cx, int cy, float sigma) {
    float dx = (float)(x - cx);
    float dy = (float)(y - cy);
    float dist2 = dx*dx + dy*dy;
    return expf(-dist2 / (2.0f * sigma * sigma));
}

sigma的取值很关键。sigma太小,权重集中在中心几个像素,浪费了窗口信息;sigma太大,又退化成均匀权重。

核心参数:sigma一般取窗口宽度的1/6到1/4。比如64x64的窗口,sigma取10~16。这样既能保证中心主导,又能让边缘像素贡献少量信息。

我记得有一次调试4K视频的对焦,用线性中心权重时,画面边缘的轻微运动会导致评分抖动。换成高斯权重后,曲线平滑了很多。为什么?因为高斯函数的导数连续,评分曲线不会出现「台阶」。

4.4 自适应权重:动态调整

自适应权重,是让权重分布根据图像内容自己调整。这是目前旗舰手机和高端相机的主流方案。

核心思路:

  1. 纹理检测:计算每个像素或子块的梯度/方差
  2. 权重映射:纹理丰富的区域给高权重,平坦区域给低权重
  3. 归一化:保证所有权重之和为1
// 自适应权重示例(基于局部方差)
void adaptive_weights(uint8_t* img, int w, int h, float* weights) {
    // 第一步:计算每个像素的局部方差(3x3窗口)
    float* variance = (float*)malloc(w * h * sizeof(float));
    for (int y = 1; y < h-1; y++) {
        for (int x = 1; x < w-1; x++) {
            float mean = 0, var = 0;
            for (int dy = -1; dy <= 1; dy++)
                for (int dx = -1; dx <= 1; dx++)
                    mean += img[(y+dy)*w + (x+dx)];
            mean /= 9.0f;
            for (int dy = -1; dy <= 1; dy++)
                for (int dx = -1; dx <= 1; dx++)
                    var += powf(img[(y+dy)*w + (x+dx)] - mean, 2);
            variance[y*w + x] = var / 9.0f;
        }
    }
    // 第二步:方差映射为权重(加一个小的epsilon防止除零)
    float eps = 1e-6f;
    for (int i = 0; i < w*h; i++)
        weights[i] = variance[i] + eps;
    // 第三步:归一化
    float sum = 0;
    for (int i = 0; i < w*h; i++) sum += weights[i];
    for (int i = 0; i < w*h; i++) weights[i] /= sum;
    free(variance);
}

注意:自适应权重的计算量比前三种大一个数量级。如果每帧都重新计算权重,对实时性要求高的场景(比如60fps视频)可能扛不住。我一般建议:

  • 每5~10帧更新一次权重
  • 或者只在场景切换时重新计算
  • 或者用硬件加速(GPU/DSP)做局部方差计算

4.5 四种算法对比

算法 计算量 抗噪性 场景适应性 硬件友好度 典型应用
均匀权重 ★☆☆☆☆ ★☆☆☆☆ ★☆☆☆☆ ★★★★★ 低端摄像头、工业检测
中心权重 ★★☆☆☆ ★★★☆☆ ★★☆☆☆ ★★★★☆ 手机主摄、监控
高斯权重 ★★☆☆☆ ★★★★☆ ★★☆☆☆ ★★★☆☆ 单反、微单、视频
自适应权重 ★★★★☆ ★★★★★ ★★★★★ ★★☆☆☆ 旗舰手机、专业相机

嗯,这里要注意:表格里的「抗噪性」一栏,均匀权重得分最低,是因为它把噪声和纹理同等对待。自适应权重得分最高,是因为它天然会忽略平坦区域的噪声。

4.6 知识结构图

下面这张图,把四种权重算法的核心逻辑和适用场景串起来了。

窗口权重分配算法体系 权重分配算法 均匀权重 中心权重 高斯权重 自适应权重 特性 计算量最低 抗噪性差 适合均匀纹理 特性 中心主导 线性/二次衰减 适合主体居中 特性 平滑衰减 导数连续 适合视频对焦 特性 动态调整 纹理驱动 旗舰机标配 选择原则:场景复杂度越高,越需要自适应权重 资源受限时,优先考虑高斯权重

4.7 实战建议

最后,我根据实际项目经验,给几个选型建议:

  • 如果你在做入门级产品:用均匀权重就够了,把精力放在窗口位置选择上。
  • 如果你在做中端手机:用高斯权重,sigma取窗口宽度的1/5,效果和性能平衡得很好。
  • 如果你在做旗舰机:上自适应权重。但记得加一个「保底机制」——当纹理检测失效时(比如全黑或全白场景),回退到高斯权重。
  • 如果你在做视频对焦:千万别用均匀权重。我踩过这个坑,画面边缘的微小变化会让评分曲线剧烈抖动,导致对焦频繁误触发。

小技巧:自适应权重可以和中心权重结合。先计算纹理权重图,再乘以一个中心高斯的衰减系数。这样既保留了中心优先的直觉,又能让纹理丰富的边缘区域获得一定话语权。我在一个项目中用这个方案,对焦成功率从87%提升到了96%。