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. 在亮度高于阈值的区域周围生成窗口
// ... 区域生长或连通域分析
}
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];
}
}
4.6 窗口策略知识体系
下面这张图总结了窗口策略的核心脉络,我把它画出来方便你对照理解:
4.7 实战建议与避坑指南
最后,我把自己这些年踩过的坑总结一下:
| 场景 | 推荐策略 | 避坑要点 |
|---|---|---|
| 极低光照(< 5 lux) | 自适应窗口 + 亮度统计 | 不要用太多窗口,2~3个足矣 |
| 主体在边缘 | 多窗口融合 + 显著性检测 | 注意边缘窗口的噪声抑制 |
| 画面中有运动物体 | 自适应窗口 + 时间平滑 | 平滑系数要动态调整 |
| 夜景人像 | 中心偏好 + 对比度权重 | 人脸区域权重强制提高 |
嗯,窗口策略这块内容不少,但核心就一句话:在正确的地方,用正确的方式,看正确的内容。 你把这个想明白了,低光照 AF 的稳定性就解决了一半。