第三章 软件架构与算法:AF算法框架

自动对焦算法,说白了就是让相机自己找到最清晰的那张照片。我做了这么多年模组,见过太多硬件调得不错、算法一跑就糊的案例。嗯,这一章我们就把AF算法的骨架拆开看看。

核心观点:AF算法 = 搜索策略 + 评价函数 + 电机控制。三者缺一不可,顺序错了也不行。

AF算法框架总览 图像传感器数据 搜索策略 爬山法 / 全局搜索 / 混合 评价函数 Tenengrad / Laplacian / Brenner 电机控制 PID / 步进 / 音圈马达 闭环反馈(PID) 最佳对焦位置 核心流程:搜索策略决定“去哪找” → 评价函数判断“清不清晰” → 电机控制执行“怎么走” PID闭环确保每次移动都精准到位,避免过冲或震荡

3.1 搜索策略:去哪找最清晰的位置

搜索策略决定了镜头该往哪个方向走、走多远。我见过不少工程师一上来就调PID,结果搜索策略选错了,怎么调都白搭。

策略名称 原理 适用场景 我踩过的坑
爬山法 沿评价函数上升方向移动,直到峰值 连续对焦、视频录制 容易陷入局部峰值,尤其是有纹理干扰时
全局搜索 遍历整个对焦范围,找最大值 初始对焦、拍照模式 速度慢,功耗高,但最可靠
混合搜索 先全局粗搜,再爬山精搜 通用场景(我最推荐) 粗搜步长要合理,否则精搜也救不回来

我的习惯:量产项目中我基本都用混合搜索。粗搜步长设为总行程的1/8,精搜步长设为1/64。这个比例是我试了十几个项目总结出来的,你可以先拿这个参数跑起来。

3.2 评价函数:怎么判断清不清晰

评价函数是AF算法的眼睛。它算出一个数值,数值越大代表图像越清晰。常用的有三种,我一个个说。

3.2.1 Tenengrad

基于Sobel算子,计算水平和垂直方向的梯度。效果最稳定,但计算量也最大。

// Tenengrad评价函数(C语言伪代码)
float tenengrad_eval(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 += sqrtf(gx*gx + gy*gy);
        }
    }
    return sum / (w * h);
}

3.2.2 Laplacian

用二阶导数检测边缘。对噪声敏感,但响应速度快。我一般在低光照场景下用它。

// Laplacian评价函数
float laplacian_eval(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 = 4*img[y*w+x] 
                    - img[(y-1)*w+x] - img[(y+1)*w+x]
                    - img[y*w+(x-1)] - img[y*w+(x+1)];
            sum += abs(lap);
        }
    }
    return sum / (w * h);
}

3.2.3 Brenner

只计算水平方向相隔两个像素的差值。计算量最小,适合嵌入式平台。

// Brenner评价函数
float brenner_eval(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);
}
评价函数 计算量 抗噪性 峰值锐度 我推荐场景
Tenengrad 高 强 高 高分辨率拍照
Laplacian 中 弱 中 低光照视频
Brenner 低 中 低 嵌入式实时对焦

我曾经踩过的坑:在一个项目中我用了Brenner,结果在低对比度场景下评价函数曲线太平,找不到峰值。后来换成Tenengrad,问题就解决了。选评价函数不能只看计算量,要看你的目标场景。

3.3 相位检测数据处理

相位检测(PDAF)是高端模组的标配。它通过像素上的微透镜对,直接算出相位差,再换算成对焦方向。但这里有个关键——PD Pixel校准。

为什么需要校准?因为每个PD像素的灵敏度、位置都有偏差。不校准的话,算出来的相位差全是错的。

// PD Pixel校准流程(简化版)
// 1. 采集均匀光照下的PD像素响应
// 2. 计算每个PD像素的增益补偿系数
// 3. 存储校准表到模组OTP

// 相位差计算
float pd_phase_diff(uint16_t *left_pd, uint16_t *right_pd, int len) {
    float sum_diff = 0;
    for (int i = 0; i < len; i++) {
        // 先应用校准系数
        float l = left_pd[i] * gain_left[i];
        float r = right_pd[i] * gain_right[i];
        sum_diff += l - r;
    }
    return sum_diff / len;  // 正值表示需要向前对焦
}

我的经验:校准表至少要在3个色温下采集(D65、A光源、TL84)。只做一个色温的话,换场景就偏了。这个坑我帮好几个客户填过。

3.4 闭环控制:PID控制器在AF中的应用

电机控制是AF的最后一公里。搜索策略告诉你往哪走,评价函数告诉你走到了没有,但真正让镜头精准停下来的,是PID控制器。

PID在AF中的角色很简单:让镜头快速、平稳地到达目标位置,不超调、不震荡。

// 位置式PID在AF中的应用
typedef struct {
    float kp;   // 比例系数
    float ki;   // 积分系数
    float kd;   // 微分系数
    float integral;
    float prev_error;
} PID_Controller;

int pid_move(PID_Controller *pid, int target_pos, int current_pos) {
    float error = target_pos - current_pos;
    
    // 比例项
    float p_out = pid->kp * error;
    
    // 积分项(带限幅)
    pid->integral += error;
    if (pid->integral > 1000) pid->integral = 1000;
    if (pid->integral < -1000) pid->integral = -1000;
    float i_out = pid->ki * pid->integral;
    
    // 微分项
    float d_out = pid->kd * (error - pid->prev_error);
    pid->prev_error = error;
    
    // 总输出
    int output = (int)(p_out + i_out + d_out);
    
    // 输出限幅(电机最大步进)
    if (output > 200) output = 200;
    if (output < -200) output = -200;
    
    return output;
}
PID参数 作用 调大后的效果 我常用的初始值
Kp(比例) 快速响应误差 响应快,但容易超调 0.5 ~ 1.0
Ki(积分) 消除稳态误差 消除静差,但可能震荡 0.01 ~ 0.05
Kd(微分) 抑制超调 阻尼增加,响应变慢 0.1 ~ 0.3

特别注意:积分项一定要加限幅!我见过一个项目,积分项无限累积,镜头直接冲到机械限位,咔咔响。限幅值设为电机最大行程的10%比较安全。

3.5 实战建议:从零搭建AF算法

  1. 先选评价函数——用Tenengrad起步,稳定后再优化计算量
  2. 再定搜索策略——混合搜索最稳妥,粗搜步长1/8,精搜步长1/64
  3. 然后做PD校准——至少3个色温,校准表存OTP
  4. 最后调PID——先调Kp让镜头能动,再加Kd抑制超调,最后加Ki消除静差

记住:AF算法不是调出来的,是测出来的。每个参数都要在真实场景下验证。我习惯在暗光、强光、纹理、纯色四个场景下各跑100次对焦,统计成功率和时间。达标了再量产。