第三章 CDAF实现细节:搜索窗口、步长策略与速度精度权衡
反差对焦(CDAF)说起来原理不复杂——找图像对比度最大的位置嘛。但真要在嵌入式平台上跑起来,坑可不少。我最早做手机摄像头调试时,就吃过搜索窗口选得太小的亏,画面明明合焦了,拍出来却是糊的。这一章咱们就把CDAF落地的三个关键点掰开揉碎:搜索窗口怎么选、步长怎么定、速度和精度怎么平衡。
3.1 搜索窗口的选择
搜索窗口,说白了就是你在画面哪个区域算对比度。选错了窗口,对焦就偏了。
3.1.1 窗口位置
传统做法是把窗口放在画面中央,占画面面积的10%~20%。为什么?因为用户习惯把主体放中间。但现在的场景复杂多了:
- 中央单窗口:最简单,适合人像、静物。我早期项目一直用这个,直到遇到拍集体照的场景——中间是背景墙,两边才是人,结果对焦对到墙上去了。
- 多窗口加权:比如9宫格、13宫格,每个窗口给不同权重。人脸检测框内的权重最高,边缘区域权重低。我建议至少用3×3的网格,人脸区域权重设2.0,其他区域0.5~1.0。
- 自适应窗口:根据场景动态调整。比如检测到人脸就缩小窗口到人脸框,检测到风景就放大到中央区域。
3.1.2 窗口大小
窗口大小直接影响对比度计算的灵敏度。太小了容易受噪声干扰,太大了会平滑掉细节。
| 窗口尺寸(像素) | 适用场景 | 优缺点 |
|---|---|---|
| 32×32 ~ 64×64 | 低分辨率预览(640×480) | 计算快,但精度低 |
| 128×128 ~ 256×256 | 高清预览(1080p) | 平衡了速度和精度 |
| 512×512 以上 | 高精度对焦(4K/8K) | 精度高,但计算量大 |
嗯,这里要注意:窗口大小要和镜头景深匹配。大光圈镜头景深浅,窗口可以小一点;小光圈镜头景深大,窗口要适当放大。我曾经在一个F1.8的镜头上用64×64的窗口,结果对焦总是来回震荡——窗口太小,对比度曲线太平坦了。
3.2 步长策略:粗搜 + 细搜
步长策略是CDAF的灵魂。你想想看,如果每一步都走得很细,对焦速度就慢;如果每一步都走得很粗,精度又不够。所以业界普遍采用两阶段策略:粗搜 + 细搜。
3.2.1 粗搜阶段
粗搜的目标是快速找到对比度峰值的大致位置。步长通常设为镜头全行程的1/10 ~ 1/20。
// 粗搜伪代码
void coarse_search() {
int step = total_focus_range / 12; // 步长:全行程的1/12
int best_pos = 0;
float max_contrast = 0;
for (int pos = 0; pos <= total_focus_range; pos += step) {
move_lens(pos);
float contrast = calculate_contrast(window);
if (contrast > max_contrast) {
max_contrast = contrast;
best_pos = pos;
}
}
return best_pos;
}
粗搜的步长怎么定?我有个经验公式:步长 ≤ 景深对应的镜头位移量。比如景深是20个马达步,那步长就不能超过20步。否则可能直接跳过峰值区域。
3.2.2 细搜阶段
细搜在粗搜找到的峰值附近进行,步长通常是粗搜的1/4 ~ 1/8。细搜的搜索范围一般覆盖粗搜峰值前后各2~3个粗搜步长。
// 细搜伪代码
void fine_search(int coarse_peak) {
int fine_step = coarse_step / 5; // 细搜步长
int search_start = coarse_peak - 2 * coarse_step;
int search_end = coarse_peak + 2 * coarse_step;
int best_pos = coarse_peak;
float max_contrast = 0;
for (int pos = search_start; pos <= search_end; pos += fine_step) {
move_lens(pos);
float contrast = calculate_contrast(window);
if (contrast > max_contrast) {
max_contrast = contrast;
best_pos = pos;
}
}
return best_pos;
}
细搜阶段有个关键点:要不要做插值?我个人建议做抛物线插值。用峰值点及其左右两个点的对比度值,拟合一条抛物线,抛物线的顶点就是更精确的合焦位置。这样可以用更少的采样点达到更高的精度。
3.3 对焦速度与精度的权衡
速度和精度,永远是CDAF的一对矛盾。你想想看,要快就得大步走,要准就得小步挪。怎么平衡?
3.3.1 影响速度的因素
- 搜索步数:每走一步都要采集图像、计算对比度,这是最耗时的。
- 图像采集延迟:从发出指令到拿到图像数据,通常有1~3帧的延迟。
- 马达响应时间:镜头移动到目标位置需要时间,步进马达通常5~20ms。
- 对比度计算时间:窗口越大、算法越复杂,耗时越长。
3.3.2 影响精度的因素
- 步长大小:步长越小,能找到的峰值位置越精确。
- 噪声水平:低光照下噪声大,对比度曲线会抖动,影响峰值判断。
- 场景纹理:纹理丰富的场景对比度曲线尖锐,容易找到峰值;平坦场景曲线平缓,精度下降。
- 窗口选择:窗口内如果有多个深度层次,对比度曲线可能出现多峰。
3.3.3 权衡策略
实际项目中,我通常采用动态策略:
- 场景识别:通过帧差法判断场景变化程度。变化大时用快速粗搜,变化小时用精细搜索。
- 自适应步长:根据当前对比度曲线的陡峭程度调整步长。曲线陡峭时步长可以大一些,曲线平缓时步长要小。
- 提前终止:如果连续3步的对比度都在下降,说明已经过了峰值,可以提前终止搜索。
- 历史位置缓存:记住上一次的合焦位置,下次搜索时优先从附近开始。
3.4 知识结构图
下面这张图把CDAF实现的核心要素串起来了,方便你理解整体逻辑:
3.5 实战中的权衡案例
我去年做一个旗舰手机项目时,遇到了一个典型的速度精度矛盾。场景是这样的:用户快速切换拍摄对象,从近处的花(10cm)切换到远处的山(无穷远)。
如果按标准流程走:粗搜12步 + 细搜10步 = 22步。每步包括马达移动(10ms)+ 图像采集(33ms,30fps)+ 对比度计算(5ms),总共约1秒。用户等不了。
我的解决方案:
- 第一步:检测到场景突变(帧差法判断),直接跳到上一次的无穷远位置。
- 第二步:在无穷远附近做3步细搜,确认合焦。
- 第三步:如果对比度不够,再回退到粗搜。
这样大部分场景下只需要3~5步,对焦时间控制在200ms以内。只有少数复杂场景才走完整流程。