
前阵子我拿到一台返修机预览画面在合焦和虚焦之间反复拉锯AF日志显示马达DAC一直在跳但始终停不到正确位置。硬件排了一圈发现Sensor没坏、马达没坏、驱动代码也正常最后定位到模组OTP里烧录的DCC曲线是另一个视场角项目的。这颗Sensor本身没问题PDAF却完全没法用问题就出在校准数据上。这个案例基本概括了PDAF相位对焦校准在整个手机成像链路里的位置Sensor和马达只是硬件真正让“相位差”变成“对焦动作”的是一套必须逐颗模组标定的校准数据。本文就按产线校准的完整顺序把PDAF从Sensor像素结构到算法依赖的Gainmap和DCC曲线拆开讲清楚。做Sensor驱动、ISP调试、AF算法或者模组量产的朋友可以直接照着这个思路搭建自己的校准流程想弄清楚手机对焦原理的开发者也能从中看到整条链路是怎么串起来的。1. PDAF像素的“出厂状态”与校准的本质1.1 相位差到底是怎么读出来的PDAF的物理基础是Sensor上专门布置的相位检测像素。早期方案是半遮式用金属遮蔽层把普通像素遮住一半只让左侧或右侧的光线进入后来主流转向2x1 OC2x1 On-Chip Lens让两个子像素共用一个微透镜利用微透镜的折射特性把入射光线按角度分开。再往后又出现了Dual PD也就是单个像素内部做两个光电二极管兼顾成像和对焦。不管结构怎么演进核心思想都一样让感光面上存在“看左边”和“看右边”的两类像素从它们的响应差异里读出离焦方向。我在实际调试中更喜欢用“两只眼”来理解这件事。普通像素相当于闭上一只眼看世界没有深度信息PDAF像素则保留了左右视野。合焦状态下微透镜把场景里同一点的光均匀分到左右子像素两边响应一致焦前或焦后时光线汇聚点偏移一边亮一边暗左右子像素的响应差就会出现正负方向的偏移这个偏移就是相位差Phase Difference。这个相位差本身只是像素亮度的差异经过归一化和相关性计算后可以得到一个带方向的数值正数表示焦点偏前负数表示焦点偏后数值大小近似对应离焦量。但这里有个关键问题——这个数值并不是直接等于透镜该走的距离Sensor给不出“DAC 100”或者“DAC 250”这种马达驱动值它能输出的只有“左眼和右眼差了大概多少单位”。如何把像素层面的差异换算成马达的驱动步进就是整套校准要做的事。1.2 为什么每一颗模组的曲线都不一样很多人第一次接触PDAF校准时会问既然算法是通用的为什么不能写死一张转换表让所有模组共用答案是从光到电到机械每一颗模组都有个体差异。首先是光电层面的离散性。PD像素半遮蔽结构、微透镜偏移、IRCF镀膜厚度、Sensor表面微透镜阵列的制造公差都会改变同一颗镜头下PD像素的响应比例。边缘视场的入射角更大微透镜偏移更明显左右子像素的失衡也更严重这些因素叠加在一起每颗Sensor的“像素响应差-离焦量”关系都不一样。其次是镜头和马达的差异。VCM马达的磁路、弹簧劲度系数、对焦行程、左右移动的线性度每个批次甚至每颗都有偏差。同一颗镜头装在不同马达上达到同一对焦位置需要的DAC code完全不同。镜头本身的曲率、镜片间距、温度漂移也会影响离焦量与相位差的关系。再加上组装公差——镜头倾斜、模组倾斜、Sensor平面与镜头光轴不垂直都会让同一视场角下不同区域的相位差表现不一致。我遇到过一台广角模组左上角和右下角的最佳对焦点差了快30个DAC这种“像面倾斜”如果不通过分区域校准数据去补偿AF算法无论如何调参都救不回来。所以校准的本质可以概括为一句话把“像素响应的相位差”逐颗地翻译成“该模组VCM的DAC code”并把这张翻译表存到模组能带出厂的地方。两个人都说“往左一点”但具体往左多少必须各自量过才知道。2. 校准产线搭建光路、目标卡和最容易翻车的环境2.1 核心设备和它们的脾气PDAF校准不是把手机放桌上拍几张照片就行需要专门的光学环境和可控的电动位移机构。产线上常见设备清单如下每一项都有它的用途也有它的“脾气”。设备用途常见问题积分球/均匀光源生成Gainmap的核心条件提供亮度绝对均匀的面光源出光口亮度不均、色温漂移AF测试卡/SFR Chart提供高对比度纹理用于DCC曲线采集时判定合焦位置对比度不足、图案周期与PD采样不匹配电动距离台精确控制模组到目标卡的距离用于不同物距下的DCC采集零点漂移、回程误差、振动暗箱/遮光罩隔离环境光干扰箱内反射光污染模组控制板/上位机通过I2C控制VCM DAC、触发Sensor曝光、读取Raw图时序不稳、寄存器读写异常频闪检测仪确认光源没有频闪市电频率干扰、LED驱动纹波其中最容易翻车的不是设备本身而是对光源“均匀”和“稳定”的理解。积分球看起来亮得很均匀但有些低端积分球出光口边缘和中心色温能差几百KPD像素对波长敏感色温不一致会让Gainmap出现局部偏差。正规做法是用光谱照度计在出光口九点采样确认亮度和色温在允许范围内再开始攻略。2.2 光源频闪和色温对采集的实际影响校准时光源的频闪是最隐蔽的坑。普通视觉检查根本看不到LED频闪但Sensor在毫秒级曝光下会把频闪的相位采进去同一颗模组连续采集两帧PD像素的差值可能会有明显波动。用这种数据去拟DCC曲线噪声会大得离谱。我这里有一个很典型的排查经历。有段时间产线突然反馈DCC曲线拟合成功率下降换灯管、换Sensor、换了整个模组批次都没解决。后来查到最后是车间供电电压不稳LED驱动器的输出纹波变大导致灯箱在100Hz频率上出现了可见的亮度脉动。Sensor曝光时间只要落在不同的亮度相位上PD响应就会跟着跳。从那以后我们校准间加了一条规定每天正式采集前先用频闪仪测光源再用固定参数连续拍十帧Raw做亮度差异统计波动超过0.3%就必须排查供电和灯具。色温对相位差的影响也容易被忽略。PD像素的微透镜衍射和光电二极管的响应随波长变化同一个离焦量下6500K色温和3000K色温测出的相位差数值是有差异的。理论上如果算法侧有对应的色温补偿系数校准光源色温可以不和场景完全一致但如果没做补偿就必须在所有校准和测试环节统一色温。市场上不少模组厂的DCC曲线会分低色温和高色温两套产线用两套灯轮流测。3. 从Raw数据到GainmapPD像素增益补偿的完整推算3.1 Gainmap要解决的具体问题在PDAF语境下Gainmap是一张“PD像素响应补偿图”不是手机拍照HDR里那个增益图这两个名字一样但完全不是一回事初入这行的人经常混。PD像素由于半遮蔽结构或共享微透镜进光量天然低于普通像素。如果直接拿PD像素的响应去和相邻普通像素比亮度一定偏暗。更重要的是这种偏暗不是均匀的——视场中心PD像素和普通像素的响应比例和视场边缘的比例不同因为边缘入射光角度大微透镜偏移效应会把光线更多地偏向某一侧子像素。如果不做补偿会同时影响两条链路。第一条是成像链路PD像素直接显示在预览画面上时会形成网格状暗斑必须用Gainmap把PD像素的亮度拉回到和周围普通像素一致。第二条是对焦链路AF算法在计算相位差时如果拿到的左右子像素响应本身就带了一个视场位置相关的偏置就会把“离焦量”误判成“像素结构差异”导致某些区域的对焦始终有固定偏移。我给一个简单数值例子帮助理解。假设视场中心PD像素响应是普通像素的60%视场边缘因为微透镜偏移只有55%。如果不做Gainmap中心区域的相位差计算里会混入小偏置边缘区域混入更大的偏置最后体现在DCC曲线上就是同一个离焦量在不同视场位置对应的相位差不一致。AF算法根本没法处理这种“同因异果”的输入。3.2 积分球采集和生成Gainmap的实操Gainmap的采集条件比较苛刻必须在积分球均匀光场下进行只有光场绝对均匀像素响应的差异才能完全归因于PD结构和微透镜偏移。采集步骤如下把模组固定在暗箱内正对积分球出光口确保整个Sensor画幅都落在均匀光场内。设置一个中间灰的曝光参数让Raw响应落在最大值的40%到60%区间避开暗电流噪声区和高光饱和区。关闭一切图像处理关闭镜头校正、坏点校正、去马赛克拿到纯Bayer Raw。连续采集多帧Raw做平均消除随机噪声对增益计算的影响。根据Sensor的PD像素排布图逐个像素判断它是不是PD像素。如果是就用它周围普通像素的平均响应作为参考计算增益值。最后这一步的计算逻辑可以写成下面这样import numpy as np from scipy.ndimage import median_filter # raw_data: 单帧线性Rawshape(H, W)uint16 # pd_mask: 相同shape的bool数组True表示该位置是PD像素 raw_data raw_data.astype(np.float32) # 统计整帧普通像素的平均响应 ref_mean np.mean(raw_data[~pd_mask]) # 初始化增益图默认值为1的非PD区域 gainmap np.ones_like(raw_data, dtypenp.float32) # 逐个PD像素取周围非PD像素的局部均值 for idx in np.argwhere(pd_mask): y, x idx patch raw_data[max(0, y-3):y4, max(0, x-3):x4] mask_patch pd_mask[max(0, y-3):y4, max(0, x-3):x4] local_ref np.mean(patch[~mask_patch]) gainmap[y, x] local_ref / (raw_data[y, x] 1e-6) # 中值滤波平滑去掉个别异常点 gainmap[gainmap 0.2] np.nan # 异常点置空 gainmap[gainmap 5.0] np.nan gainmap median_filter(gainmap, size5, modeconstant, cvalnp.nan) np.save(pd_gainmap.npy, gainmap)这段代码在工程上够用了但实际量产时逐像素遍历Python循环性能不够一般会预先计算PD像素索引表改写成矩阵运算。严格来说生成的增益还要配合相邻PD像素的插值因为PD像素呈网格状分布而不是连续排列算法在读PD像素时需要从Gainmap上对应坐标取值。3.3 Gainmap的落地形式和校验方法生成好的Gainmap要压缩成适合量产和驱动加载的形式。常见做法有两种一是把全视场的增益值以查找表形式烧进OTP或者校准分区驱动起来后构建成内存表格二是用低阶多项式去拟合增益分布只保存多项式的几个系数驱动侧实时计算省空间但精度略低。我实测下来纯查找表形式在Sensor分辨率越来越高、PD像素数量动辄几百万的情况下占用空间会变得可观。部分平台会做分块压缩把画幅分成16x16或32x32的格子每格只存一个平均增益值使用时双线性插值。对PD增益补偿来说这种低频持续变化用分块压缩精度损失很小是个值得推荐的折中方案。Gainmap生成后的校验不能只看图像效果。我的习惯是在积分球光场下先应用Gainmap然后对全画幅的PD像素区域做响应一致性统计。理想情况下补偿过后所有PD像素响应和周围普通像素的比值应该接近1.0最大偏差不超过1%。超过这个范围就要回头查积分球均匀性、曝光是否饱和、或者PD像素掩膜坐标是否标错——坐标错位在产线很常见PD排布图版本更新后没有同步给校准工具生成的Gainmap全体错位画面暗斑反而加重。4. DCC曲线拟合相位差到VCM DAC code的换算4.1 数据采集步进扫描和对焦位置判定有了Gainmap让PD像素响应回归正常下一步就是采集DCC曲线的原始数据。这一步的目标是建立“相位差”和“DAC code”之间的关系表。实际操作中我会针对多个目标物距分别采集。至少要选无穷远、1米、0.5米、0.2米四档广角模组或者对微距性能要求高的再加一档最近对焦距离。每个物距下把VCM从最近端步进到最远端步长可以取DAC 8到10每走一步采集一帧Raw并做两件事一是通过左右PD像素相关性算出相位差二是用SFR或者对比度算法评价当前焦点的清晰度。合焦位置的判定是这一步的核心。相位差为0的位置不一定是画面对比度最高的位置因为PD像素本身有偏差Gainmap对亮度的校正是准确的但DCC曲线的目标是把相位差映射到马达能到的最佳锐化位置。所以要把对焦清晰度的峰值单独标出来——通常取对比度曲线的极值点在此基础上记录相位差和DAC的对应关系。采集到的原始数据大致是下面这张表的形式物距最佳合焦DAC该物距下相位差范围相位差0对应DAC无穷远96-42 ~ 36921米160-38 ~ 321550.5米246-35 ~ 302400.2米380-28 ~ 24372注意每一行的采集都是独立完成的不同物距下VCM从不同方向步进机械回程差会导致同一位置扫上和扫下差了几个DAC。这一步我建议统一步进方向产线上通常只按一个方向扫算法侧使用时也要保持一致否则校准数据和实际对焦动作之间会凭空多出一段“反向间隙”。4.2 分段线性拟合和曲线验证得到每个物距下一系列“相位差-最佳DAC”采样点之后就可以构建查找表了。查找表的横坐标是相位差纵坐标是VCM DAC codeAF算法实时把相位差丢进来用插值拿到目标DAC。这里我的建议是宁可做分段线性也不要贪心做高阶多项式拟合。高阶拟合看着精度高但容易在采样点之间产生龙格振荡导致AF马达在目标位置附近来回抖动。分段线性只要采样点足够密误差就能控制在可接受范围。剔除异常点这一步要在拟合前做。DCC曲线上如果有个别点明显偏离相邻趋势大概率是采集时环境振动、目标卡局部反光、或者Sensor噪声引起的此时要先做中值滤波或者手动剔除拟合结果才稳定。拟合本身用numpy就能完成import numpy as np # 无穷远物距下的采样点phase为相位差dac为最佳对焦DAC phase np.array([-42, -30, -18, -6, 0, 12, 24, 36]) # 示例值 dac np.array([54, 68, 78, 88, 92, 105, 118, 132]) # 按相位差排序保证插值单调 order np.argsort(phase) phase_sorted phase[order] dac_sorted dac[order] # 分段线性插值直接用numpy的interp # 使用时target_dac np.interp(measured_phase, phase_sorted, dac_sorted)拟合完成后必须做回验把这个查找表写进AF库重新采集一组不同物距、不同初始位置的数据看AF算法能不能快速稳定地落在最佳合焦DAC附近。误差一般要控制在±3个DAC以内超过这个范围就要回调采集密度或检查系统误差。还有一个容易踩的细节无穷远物距的采样点其实是在有限距离下拍的只是目标卡距离足够远比如10米以外。如果目标卡距离不够远无穷远DAC段的曲线斜率都会偏最终导致预览时远处物体对焦偏近处方向。有条件最好用平行光管模拟无穷远没有平行光管就保证目标卡距离达到镜头超焦距的三倍以上。4.3 温度和微距对DCC的影响DCC曲线不是一条走到黑的直线温度和物距都会改变相位差和DAC的换算关系。VCM马达的磁体在温度变化时磁感应强度会变弹簧的劲度系数也受温度影响导致同一DAC code下的实际镜头位移在不同温度下不同。量产机型的DCC校准至少要分常温、高温、低温三组。常见做法是保留下电参数随温度变化的曲线或者直接给驱动里的温度传感器接口让AF算法按当前温度在相邻两条DCC曲线之间插值。如果产品对温度不敏感也可以在验证过影响范围后只用常温DCC但一定要确认室外严寒和暴晒场景下对焦速度没有明显劣化。我踩过一个真实的坑一台机器在实验室里DCC曲线一切正常户外冬天拍远景总是过一会就重新拉风箱找焦点。日志里DAC一直在合焦点附近小幅跳动但清晰度总是差一点。后来把模组放进高低温箱一测低温下峰位DAC整体偏移了快20步常温DCC完全失去参照。补了低温校准数据并做线性插值之后问题才彻底解决。微距端的处理则是另一个话题。近距离大离焦量下相位差和DAC之间往往偏离线性需要更密集的采样点和专门的平滑处理。有些方案会为微距单独建一条DCC曲线或加一段补偿系数避免在大离焦状态下DCC斜率过陡导致马达冲过头或者来回振荡。设置相位差钳位范围也很关键超出校准覆盖范围的相位差直接判为不可信让AF算法走CDAF或者重试。5. 量产落地与排错经验从实验室曲线到产线一致性的坑5.1 参数写入OTP与SDK对接校准数据生成之后要沿着“校准工具 → OTP/校准分区 → 驱动 → AF算法库”这条路走通。Gainmap和DCC一般分开存储Gainmap可能比较大放在Flash分区更合适DCC的查找表比较小经常连同镜头ID、马达ID一起写进OTP或者模组EEPROM。驱动侧的工作量通常在格式转换上。不同平台的AF库对DCC表的要求不一样有的平台要求直接给“相位差-DAC”表有的平台只认斜率还有的平台要你提供归一化的无偏置曲线由AF库自己去拟合增益。我在对接一款瑞芯微平台时它的AF库对DCC表有严格的步长要求表数据必须等间隔排列否则驱动直接拒绝加载。校准工具导出的表不满足条件就得在驱动里补一层插值重采样。SDR对接完成后还要检查一件事ISP里的坏点校正一定要跳过PD像素位置否则预处理算法会把PD像素当成坏点再用周围普通像素填掉PDAF信息就丢失了。这个错误很基础但在新项目里反复出现尤其是第一次移植PDAF功能的平台出图流程里BPC环节默认全开PD像素全被抹掉相位差恒为0AF算法直接废掉。我之前做过一个Sensor出图全流程的调试卡在PDAF失效问题上查了两天才发现是坏点校正表中的PD坐标漏配。5.2 常见反直觉故障的排查链路量产现场出现对焦故障很多时候不是算法精度问题而是校准数据和应用状态不匹配。我整理了几个典型的反直觉案例。现象直觉判断实际根因排查顺序合焦后画面发虚但AF日志显示DAC停在目标位算法没对到焦点合焦判定用了对光谱响应不正常的通道最佳焦点偏移先查SFR判定通道和Raw来源近距离对焦慢远处干脆不动微距DCC没标定边缘视场PD增益补偿失效远处相位差被偏置先验Gainmap再查DCC低温曲线强光下对焦速度变慢算法需要调优PD像素饱和相位差整体丢失PDAF回退CDAF先看Raw里PD像素是否溢出同批次部分机器DCC异常Sensor批次不良产线机身换型后校准工具配置未同步更新先核对OTP内容与工具版本户外低温对焦反复拉风箱马达故障低温DCC补偿缺失DAC漂移超出容忍先看温度传感器数据和DCC曲线切换第一个案例值得展开。当时现象是预览画面老是差一点点AF库里MILMatch Image Length之类的指标都显示马达到位了但画面的高频细节就是不够锐。查到最后发现目标卡用的是斜边SFR卡而斜边方向与PD像素的左右划分刚好平行SFR峰值位置的判定和真正合焦位置之间相差了几个DAC。后来换成十字斜边目标卡这个偏差才消失。所以DCC校准时目标卡方向和PD像素取向之间的关系看起来是小问题影响却非常大。强光下PD像素饱和这个事也很有意思。相位差本质上依赖左右子像素的响应差异一旦两侧都饱和差异就消失PDAF等于失去输入。有些Sensor会针对PD像素单独做短曝光控制但如果没有这个功能AF算法只能依赖CDAF。产线验证环境如果只测低照度场景这个恶化路径根本暴露不出来必须把强光场景写进PVT测试用例。5.3 验证指标与量产筛选建议DCC和Gainmap校准完需要一套可量化的验证指标来定义什么叫“通过”。我的项目里常用下面几项对焦命中率对每个测试物距从多个随机初始位置触发AF看最终DAC是否落在合焦中心±3步以内。通常要求不低于95%。对焦耗时TTF从触发对焦到稳定合焦的时间一般在室内光照条件下要求小于300ms暗光下另有标准。相位差-焦距单调性整条DCC查找表内相位差和DAC的对应关系不能出现明显跳变否则算法会看到“同一个相位差对应两个不同位置”的歧义。Gainmap补偿残差积分球下补偿后PD像素响应和普通像素响应比值在0.99~1.01之间。环境稳定性高低温、强光、暗光、高色温、低色温下的重复性测试结果都在允差内。产线筛选不能只在实验室条件下测一遍就算完。我强烈建议增加模组在真实机身里的整机抽测因为模组校准台只能保证光电链路的数据正确装进机身以后喇叭磁铁、马达、天线等器件都可能干扰VCM驱动或Sensor读取。这种整机级对焦性能差异哪怕只有个别算例也说明校准数据的系统余量不足。另一个容易被忽视的量产问题是校准工具版本管理。DCC表格式、Gainmap压缩方式、OTP烧录字段偏移任何一个环节改版都要通知整条链路同步。我见过一次产线换了一版校准工具工具导出的Gainmap空间尺寸和驱动侧定义不一致但烧录照常结果所有机器暗光下取到的PD增益都偏大AF曲线整体漂移。这种问题很难在线体上即时发现等抽检到了一批料才暴雷。最后再分享一个我自己踩了很多次才记住的教训如果产线突然反馈批量DCC曲线异常先不要急着怀疑Sensor和镜头先检查距离台的机械零点有没有发生偏移。电动滑台的零点因为限位碰撞漂移几个毫米所有物距的DCC都会整体平移看起来就像模组不良一样。把距离台重新标零之后DCC曲线就恢复正常了。校准这件事很多坑不在算法而在整个测量链路的每一次复位和每一次参数传递。流程上的严谨比某一个拟合算法的精妙更能决定量产的对焦体验。