4. 窗口策略优化:多窗口融合、自适应窗口、基于显著性检测的窗口选择、权重分配策略

窗口策略,说白了就是告诉算法:「你往哪儿看?」

在低光照环境下,这个问题变得特别棘手。画面暗、噪声大、对比度低,如果窗口选错了,AF 算法就像在黑夜里找一只黑猫——累死也找不到焦点。我这些年调过的低光照项目,有一半的稳定性问题最后都追到了窗口策略上。

4.1 为什么窗口策略如此关键?

先想一个问题:一个 1080p 的 sensor,有 200 多万个像素。AF 算法不可能每个像素都算一遍,太慢了。所以我们需要划定一个或几个「感兴趣区域」,只在这些区域内计算对焦评价函数。

窗口选得好,信噪比高、对比度丰富、对焦曲线陡峭。窗口选得差,全是噪声、全是平坦区域、对焦曲线像一条死鱼。你想想看,低光照下噪声本来就大,窗口再选到天空或墙壁上,那 AF 基本就废了。

核心原则: 窗口策略的目标不是「覆盖画面」,而是「找到画面中最能反映离焦程度的那块区域」。

4.2 多窗口融合策略

我最开始做 AF 时,只用单个中央窗口。后来发现一个问题:如果主体不在中央,AF 就对着背景对焦去了。于是我开始尝试多窗口。

多窗口的思路很简单:在画面中布置多个固定窗口,比如中央一个、左上、右上、左下、右下各一个,然后把这些窗口的评价结果融合起来。

4.2.1 固定网格窗口

这是最基础的做法。把画面分成 3×3 或 5×5 的网格,每个格子都是一个窗口。每个窗口独立计算对焦值,然后按一定规则融合。

// 伪代码:固定网格窗口融合
#define GRID_ROWS 3
#define GRID_COLS 3

float grid_focus[GRID_ROWS][GRID_COLS];
float final_focus = 0.0f;

for (int r = 0; r < GRID_ROWS; r++) {
    for (int c = 0; c < GRID_COLS; c++) {
        grid_focus[r][c] = calc_focus_measure(window[r][c]);
        // 加权融合:中央窗口权重高
        float weight = (r == 1 && c == 1) ? 0.4f : 0.075f;
        final_focus += grid_focus[r][c] * weight;
    }
}
我的经验: 固定网格窗口在光照充足时效果不错,但低光照下边缘窗口信噪比太低,反而会拉低整体评价质量。我一般会加一个「有效窗口筛选」——只有对比度超过阈值的窗口才参与融合。

4.2.2 十字交叉窗口

另一种我常用的多窗口布局是十字交叉。在画面中央画一个十字形区域,水平方向和垂直方向各取一条带状窗口。这样做的好处是:

  • 覆盖了画面中最重要的水平和垂直纹理方向
  • 窗口面积适中,不会引入太多噪声
  • 对主体在中央附近的情况非常鲁棒

4.3 自适应窗口

固定窗口有个硬伤:它不知道画面里发生了什么。低光照下,主体可能很小、很暗,固定窗口很容易把大量背景噪声算进去。这时候就需要自适应窗口了。

4.3.1 基于亮度统计的窗口缩放

我做过一个项目,晚上拍月亮。月亮在画面里就一个小亮点,周围全是黑漆漆的天空。如果用固定大窗口,AF 值全被噪声淹没了。后来我加了一个预处理:先统计全图的亮度直方图,找到亮度较高的区域,然后只在这些区域周围开窗口。

// 伪代码:基于亮度统计的自适应窗口
void adaptive_window_by_luminance(uint8_t *frame, int width, int height) {
    // 1. 计算亮度直方图
    int hist[256] = {0};
    for (int i = 0; i < width * height; i++) {
        hist[frame[i]]++;
    }
    
    // 2. 找到亮度前 10% 的阈值
    int total_pixels = width * height;
    int cum = 0;
    int bright_threshold = 0;
    for (int i = 255; i >= 0; i--) {
        cum += hist[i];
        if (cum >= total_pixels * 0.1f) {
            bright_threshold = i;
            break;
        }
    }
    
    // 3. 在亮度高于阈值的区域周围生成窗口
    // ... 区域生长或连通域分析
}
注意: 自适应窗口不能太「飘」。如果每一帧窗口位置都剧烈变化,AF 曲线会抖动得很厉害。我一般会加一个时间平滑——当前帧的窗口位置与前一帧做加权平均。

4.3.2 基于对比度分布的窗口调整

另一种自适应思路是直接看对比度。把画面分成小块,计算每块的对比度(比如用拉普拉斯方差),然后选择对比度最高的几块作为窗口。低光照下,对比度高的区域通常就是主体边缘或纹理区域,正是 AF 需要的信息。

4.4 基于显著性检测的窗口选择

这是目前比较前沿的做法。显著性检测,说白了就是模拟人眼的注意力机制——找出画面中最「引人注目」的区域。

4.4.1 频域显著性检测

我记得有一篇经典叫《Saliency Detection: A Spectral Residual Approach》。它的思路很巧妙:对图像做 FFT,提取频谱残差,再反变换回空间域,就能得到显著性图。这个方法计算量不大,适合嵌入式实现。

// 伪代码:频域显著性检测
void spectral_residual_saliency(uint8_t *gray, float *saliency_map, int w, int h) {
    // 1. 对图像做 FFT
    complex *fft_result = fft2d(gray, w, h);
    
    // 2. 计算幅度谱和相位谱
    float *magnitude = calc_magnitude(fft_result, w, h);
    float *phase = calc_phase(fft_result, w, h);
    
    // 3. 计算频谱残差
    float *log_magnitude = log(magnitude);
    float *avg_magnitude = average_filter(log_magnitude, w, h, 3);
    float *residual = log_magnitude - avg_magnitude;
    
    // 4. 反变换回空间域
    complex *reconstructed = ifft2d(residual, phase, w, h);
    
    // 5. 取模得到显著性图
    for (int i = 0; i < w * h; i++) {
        saliency_map[i] = abs(reconstructed[i]);
    }
}
避坑指南: 我曾经在低光照下直接用频域显著性检测,结果发现显著性图全是噪声。后来我加了一个预处理——先做高斯滤波去噪,再做显著性检测。效果好了很多。

4.4.2 基于深度学习的轻量显著性检测

如果芯片算力允许,可以用轻量级 CNN 做显著性检测。比如 MobileNet-SSD 或者 SqueezeNet 的变体。不过说实话,在低光照嵌入式平台上跑深度学习,功耗和帧率都是挑战。我个人更倾向于用传统方法,除非项目预算充足、散热也扛得住。

4.5 权重分配策略

窗口选好了,怎么融合?这就涉及到权重分配。权重分配做得好,AF 稳定又平滑。做得不好,AF 像抽风一样来回跳。

4.5.1 基于对比度的动态权重

这是最直观的做法:对比度高的窗口,说明纹理丰富、对焦信息充足,权重就应该大。对比度低的窗口,可能是平坦区域或噪声区,权重就应该小。

// 伪代码:基于对比度的动态权重
float calc_dynamic_weight(float contrast, float max_contrast) {
    // 用 sigmoid 函数做非线性映射
    float normalized = contrast / max_contrast;
    return 1.0f / (1.0f + exp(-10.0f * (normalized - 0.3f)));
}

// 融合时归一化权重
float total_weight = 0.0f;
for (int i = 0; i < NUM_WINDOWS; i++) {
    weights[i] = calc_dynamic_weight(contrasts[i], max_contrast);
    total_weight += weights[i];
}
for (int i = 0; i < NUM_WINDOWS; i++) {
    weights[i] /= total_weight;
}

4.5.2 基于位置的中心偏好权重

在大多数拍照场景中,主体都在画面中央附近。所以即使对比度不高,中央窗口也应该保留一定的权重。我常用的做法是:

  • 基础权重:根据窗口位置设定,中央高、边缘低
  • 动态调整:根据对比度、亮度、运动检测结果修正
  • 最终权重:基础权重 × 动态调整系数,再归一化

4.5.3 时间域权重平滑

权重如果每帧都剧烈变化,AF 曲线就会抖动。我一般会加一个 IIR 低通滤波:

// 伪代码:时间域权重平滑
#define SMOOTH_FACTOR 0.7f  // 平滑系数,越大越平滑

float smoothed_weights[NUM_WINDOWS];

void update_smoothed_weights(float *raw_weights) {
    for (int i = 0; i < NUM_WINDOWS; i++) {
        smoothed_weights[i] = SMOOTH_FACTOR * smoothed_weights[i] 
                            + (1.0f - SMOOTH_FACTOR) * raw_weights[i];
    }
}
注意: 平滑系数不能太大,否则 AF 响应太慢。我一般取 0.6~0.8 之间,具体要看帧率和场景切换速度。

4.6 窗口策略知识体系

下面这张图总结了窗口策略的核心脉络,我把它画出来方便你对照理解:

窗口策略优化知识体系 窗口策略优化 多窗口融合 自适应窗口 显著性检测 权重分配 固定网格窗口 十字交叉窗口 有效窗口筛选 亮度统计缩放 对比度分布调整 时间域平滑 频域残差法 深度学习法 预处理去噪 对比度动态权重 中心偏好权重 IIR平滑滤波 核心目标:在低光照下找到信噪比最高、对比度最丰富的区域 作为 AF 评价函数的计算窗口,提升对焦稳定性和准确性

4.7 实战建议与避坑指南

最后,我把自己这些年踩过的坑总结一下:

场景 推荐策略 避坑要点
极低光照(< 5 lux) 自适应窗口 + 亮度统计 不要用太多窗口,2~3个足矣
主体在边缘 多窗口融合 + 显著性检测 注意边缘窗口的噪声抑制
画面中有运动物体 自适应窗口 + 时间平滑 平滑系数要动态调整
夜景人像 中心偏好 + 对比度权重 人脸区域权重强制提高
我曾经踩过的一个大坑: 在一个低光照安防项目中,我用了 5×5 的固定网格窗口,结果晚上画面全是噪声,每个窗口的对比度都差不多,权重分配完全失效。AF 就在那里来回振荡,永远对不上焦。后来我把窗口数减到 3×3,并且加了有效窗口筛选,问题才解决。窗口不是越多越好,低光照下「少而精」才是王道。

嗯,窗口策略这块内容不少,但核心就一句话:在正确的地方,用正确的方式,看正确的内容。 你把这个想明白了,低光照 AF 的稳定性就解决了一半。