从Sensor到算法:手把手带你拆解手机PDAF相位对焦的完整校准流程(含Gainmap与DCC详解)
先交代一下背景。我之前在一颗RV1126B方案的IPC模组上调试PDAF对焦,sensor是一颗支持2x2 OC的1/2.7英寸CMOS,项目都快量产了,客户反馈近景对焦慢、远景拉风箱。按老经验去查几家主流平台,得到的回答基本都是“相位对焦不准,你重新标一下Gainmap和DCC”,但真正能把这套标定流程讲清楚的人很少。当时我手上只有一份sensor的datasheet、一个半残的调试工具,还有一个快要到期的交付节点。
这篇文章就是那段时间的总结。我会从PDAF的基本原理讲起,重点拆解Gainmap和DCC这两个参数在整条链路里的作用,然后给出一套可以复现的完整校准流程。内容不挑具体平台,无论是手机、行车记录仪、智能摄像头还是机器人视觉模块,只要你的sensor支持PDAF,这套思路都可以直接套用。
1. 内容整体设计与思路拆解:为什么PDAF不是“拿到sensor就能用”
很多人第一次接触PDAF,以为这是一个sensor出厂就固化的功能,接上模组就能自动对焦,事实远不是这样。PDAF能够工作,依赖的是sensor上特殊的像素结构和后端算法的配合,而连接两者的桥梁就是Gainmap和DCC。这两个参数通常不是sensor出厂默认值就能直接用的,它们和模组的镜头、VCM马达、IR滤光片、甚至排线布局都有关系,必须针对具体模组做一次标定。
1.1 相位对焦的核心工作原理
先帮大家把PDAF成像底层补明白,否则后面讲标定全是空的。
普通CMOS的每个像素上都覆盖着微透镜(microlens),用来把入射光汇聚到光电二极管上,尽量提高量子效率。PDAF sensor的做法是在部分像素上做文章:将这些像素的微透镜遮挡一半,或者做半圆形微透镜偏移,使得它们只接收来自特定方向的光线。通俗地说,这些“特殊像素”被分成了左眼看左半边光、右眼看右半边光的成对结构。当镜头处于合焦位置时,这对像素接收到的光强度应该对称一致;当镜头离焦时,两个像素接收到的光强会产生差异,相位差就出来了。
对比度对焦(CDAF)是在像平面上逐渐移动镜头来找“最锐”的位置,本质上是一个搜索过程,搜索就需要来回扫,所以慢。PDAF直接给出一幅图里各区域的相位差偏移量,再通过映射关系换算成当前的离焦方向和大小,镜头一步就能跳到目标位置附近。速度快的原因就在这里——它把“搜索”变成了“测量”。
但问题来了。这个相位差(通常叫PD value)是个无量纲量,它对应的是两个半像素之间的光强偏移,并不是实际的物理离焦量。要把PD value转换成VCM马达需要走的步数(也就是defocus值),就需要DCC。而左右像素哪怕设计上是对称的,实际制造中也很难做到完全一致,尤其是微透镜偏移量和光电二极管有效面积的差异,会导致同一光照下左右像素输出本身就有一个固定的增益差,这个差值由Gainmap来补偿。
1.2 为什么“默认系数”不可信
我见过不少工程师在第一次调试时,直接拿sensor vendor提供的demo settings烧进模组就指望PDAF跑通,结果往往是近处对不上、远处疯狂来回抽动。原因在于sensor vendor提供的参数是在他们自己的reference模组上标定的,镜头型号、VCM行程、马达灵敏度都和你手上的模组不一样。
举一个具体例子:DCC曲线里最关键的一个参数是“PD value到defocus的斜率转换”,它直接取决于镜头的光学设计——同样的物理离焦量,在不同镜头上产生的PD value偏移是不同的。哪怕你换了同一家镜头厂的不同批次,焦距差了0.1mm,DCC的斜率就必须重新拟合。更隐蔽的是温度影响,VCM马达的弹簧弹性和磁力都会随温度变化,导致同一个DCC值在25度和50度下的实际对焦位置不同。这些都是在实际量产中必须考虑的因素,如果只知道拿默认参数,就谈不上真正掌握PDAF。
1.3 整体校准流程的设计框架
一个完整的PDAF标定流程,我建议这样拆分:第一步是环境准备,确定标定环境的光源、物距、标板类型;第二步是PD值采集,在多个已知距离下记录PD值、VCM位置、镜头温度等数据;第三步是Gainmap计算,用均匀光照下的左右像素输出拟合增益补偿系数;第四步是DCC拟合,建立PD值与defocus之间的映射关系;第五步是写入和验证,把参数写入驱动并做实拍测试。
这套流程看起来不复杂,但每一步都有很多坑。比如采集PD值时环境光不均匀,直接导致左右像素灵敏度差异拟合错误;又比如拍摄的标板本身纹理方向单一,导致某个方向的PD值异常。后面我会把每个环节的细节都摊开来讲。
2. 核心细节解析:Gainmap和DCC到底是什么
如果只看各家平台的文档,Gainmap和DCC的概念很容易被绕晕。有的SDK里叫PD calibration、有的叫DCC table、有的把Gainmap混在AWB统计里。这里我把两者的物理意义、作用机制、计算方法掰开揉碎讲清楚。
2.1 Gainmap:校正左右像素的先天“视力差”
前面说过,PDAF像素是由一对分别接收左右光线的子像素构成的。理想情况下,当一个均匀照亮的目标成像在所有像素上时,左右子像素的输出应该完全一致。但实际制造中,两个子像素的光电二极管面积不可能完全相等,微透镜的偏移量也有容差,所以即使在完全均匀的光照下,左右子像素的输出也会有一个固定的比例差,这个差异就是Gainmap要补偿的东西。
Gainmap的计算逻辑其实不复杂。选择一块均匀照明的白色板子,在自动白平衡稳定后,连续抓取若干帧,取左右子像素输出各自的平均值,然后算出它们的比值。这个比值通常是一个接近1.0的数,但会随sensor温度、曝光时间略有漂移。所以在量产标定中,通常会在至少两个不同光照强度下分别采集,然后用线性插值生成一张二维查表,横轴是亮度、纵轴是增益比。
实际应用中,Gainmap是逐像素计算的。因为PDAF像素在sensor上不是成片排列,而是稀疏分布在几个特定行,每一对像素的工艺偏差都不一样,所以标准的做法是存储一张与PDAF像素位置一一对应的增益表,而不是一个全局标量。有些平台把这张表简化为按行存储,这会在边缘区域引入少量误差,但通常可以接受。
2.2 DCC:把“相位差”翻译成“镜头步数”
DCC,全称是Defocus Conversion Coefficient,翻译过来是“离焦转换系数”。这是PDAF里最核心、最容易被误解的参数。它做的事情可以用一句话概括:把从图像中计算出的PD值,映射成VCM马达需要运动的步数(也就是驱动电流),从而让镜头移动到合焦位置。
为什么需要这个转换?因为PD值反映的是光线到达sensor时在水平方向上的偏移量,这个偏移量的大小和离焦量之间的关系,取决于镜头的焦距、光圈、光学畸变、sensor的像素尺寸等多个因素。不同的镜头模块,同样的离焦量产生的PD值可能完全不同。甚至同一个镜头模块,在画面中心区域和边缘区域,因为入射角不同,PD值和defocus的对应关系也不同。所以DCC通常不是一个简单的常数,而是一组分区、分距离的拟合曲线。
在一次完整的标定中,你需要在特定物距(比如10cm、30cm、1m、无穷远)下,手动精确控制VCM位置,记录下每个位置对应的PD值,然后用最小二乘法或者分段线性拟合,得到一条PD-defocus映射曲线。这条曲线就是整个PDAF算法的“灵魂”。
这里需要特别说明一个初学者常踩的坑:DCC和“对焦目标距离”是两回事。有人拿PDAF当测距仪用,问为什么PD值不能直接换算成实际物距。实际上,PD值只能告诉你镜头当前离开合焦位置多远、往哪个方向走,它无法直接告诉你目标物体离你多少米。特别是在大光圈镜头上,PD值随距离的变化是非线性的,越远越不敏感,无限远附近甚至会饱和。把DCC理解成“对焦收敛用的反馈增益”才是正确的打开方式。
2.3 Gainmap和DCC在算法链路中的协作关系
两者在PDAF算法中的协作可以用一个例子说清楚。假设你在暗光环境下拍一个纹理细致的物体,sensor左右子像素的原始输出一个是100 ADU,一个是110 ADU。如果Gainmap是1.05,那么加权后的PD值就是100×1.05-110=-5 ADU,表示镜头需要往后移动。如果Gainmap错误地设成了0.9,那么PD值就变成100×0.9-110=-20 ADU,算法就会认为离焦量大得离谱,给VCM一个很大的步进电压,结果镜头冲过头,再反向修正,来回震荡几次才勉强收敛。这就是很多模组“对焦慢”的真正原因,很多时候并不是算法参数没调好,而是Gainmap本身就是错的。
DCC则负责第二步。当PD值被Gainmap修正后,DCC把它折算成VCM的目标步数。如果DCC斜率过陡,算法会认为很小的PD偏移也需要移动很长距离,导致过冲;如果斜率过缓,算法会反应迟钝,需要多次迭代才能收敛。一个理想的DCC曲线,应该是接近线性的、过零点的、并且在小PD值和中等PD值区域都保持较高的斜率精度。后面我给的标定方法,就是围绕这一目标展开的。
3. 实操过程与核心环节实现:一套可复现的PDAF标定流程
现在进入本文最硬核的部分。下面这套流程是我在RV1126B方案上反复验证过的,也适配其他主流平台。每一步我都会注明操作目的、需要记录的数据、以及我踩过的坑。
3.1 环境准备与设备搭建
做PDAF标定,实验室环境远比想象中重要。我第一次是在普通办公室灯光下做的,结果拟合出的Gainmap在户外场景下偏差很大。原因很简单:不同色温的光谱分布会影响左右子像素的响应差异,黄光下拟合出的增益比和自然光(日光)下完全不同。
建议的标定环境是三要素:第一,使用标准D65光源,或者至少是色温可控的LED面光源,确保整个标板平面的照度均匀性在±5%以内;第二,标板要与镜头完全垂直,如果有俯仰角,画面边缘的PD值会引入系统性偏差;第三,整个系统放在防振台上,或者至少在曝光期间无振动。
这里还要提一个很多人忽视的环节:IR滤光片。PDAF像素的响应通常比普通像素更广的光谱范围,如果模组里的IR滤光片质量不好,红外光会严重干扰左右像素的平衡。所以标定环境的灯光尽量少含红外成分,或者干脆用监控级模组的标准环境光源。否则你拟合出的Gainmap会带一个伪红外偏置,白天户外还行,一到室内红外人造光源多的场景就翻车。
在硬件层面,VCM马达的状态也要确认。用驱动工具让VCM从0跑到满行程,多跑两个来回,确保机械结构没有钝涩、磁缸没有卡滞。记录VCM的阻尼时间,也就是从设定位置到稳定位置的时间常数。这个数据在后续DCC拟合中会用到,因为如果你在VCM还没稳定时就采集图像,PD值里会混入动态误差。
3.2 PD值采集:多距离、多分区、多光照采样
PD值采集是整个标定的基础,数据质量决定参数质量。我在第一次标定时用了一个40cm×30cm的灰阶板,发现数据噪声特别大,后来换成高频纹理的ISO 12233标板才行。原因很简单:PDAF算法是基于局部边缘的相位计算,标板必须提供足够的空间频率信息,纯灰阶板在均匀区域根本算不出PD值。
采集流程建议按以下步骤操作:
第一步,把标板固定在滑轨上,从最远距离开始。对每个距离点,需要使用分辨率足够高的对位标记,保证标板中心在画面中心的±5%误差范围内。
第二步,在每个距离点,让VCM从最近焦到最远焦步进,步长建议设在满行程的1%到2%。每走一步,等待马达稳定,抓拍一帧并记录:当前的VCM code、该帧的PD值(左右子像素的原始差)、sensor温度、环境亮度(曝光时间+增益的换算值)。
第三步,至少采集3个距离点:近距离(如镜头最小物距)、中距离(如30-50cm)、无限远(或者镜头对焦到无限远的VCM位置)。如果镜头行程比较长,建议在5-7个距离点采集,覆盖从最近到无限远的整个对焦范围。
第四步,在暗光和亮光两种条件下各做一遍。因为PD值在低亮度下噪声会显著上升,亮光条件下则容易饱和,两套数据可以验证Gainmap和DCC的动态范围。
在采集过程中,有一个细节特别提一下。PD值和VCM位置并不是一对一的映射关系,它受当前帧的曝光参数影响。如果自动曝光和自动白平衡开着,你采集到的一组PD值里可能夹杂着曝光变化带来的增益变化。所以标定时的可靠做法是:固定曝光时间、固定模拟增益、关掉自动白平衡,只改变光源强度来适应环境亮度变化。手动模式下采集的数据,拟合出来的DCC才稳定可用。
3.3 Gainmap计算与补偿验证
得到采集数据后,第一步先算Gainmap。我在上一节提过基本逻辑,这里给一个更具体的计算过程。
假设sensor上有N对PDAF像素,每对像素在第k帧均匀光照下的输出分别是L(i,k)和R(i,k)。Gainmap的作用是找到一个系数G(i),使得所有帧的R(i,k)×G(i)约等于L(i,k)。最简单的方式是每个PDAF像素位置独立做线性回归,计算均值比:
G(i) = sum_k L(i,k) / sum_k R(i,k)
但这样的结果容易受个别噪声帧影响。更稳的做法是取中位数,或者去掉最大最小各5%的数据再做均值。我在实践中发现,取中位数对应对焦失败场景的鲁棒性更好,因为那些VCM失步的帧会造成统计离群值。
Gainmap计算完后,不能急着写死。要做一次验证:重新在均匀光照下采集一批新数据,把补偿后的左右像素残差计算出来。如果残差的均值超过原始信号的1%,说明Gainmap拟合有问题,需要检查光源均匀性或者sensor散热干扰。
验证中发现一个很典型的坑:很多工程师在算完Gainmap之后,直接把系数写进driver就结束,不做“暗电流补偿”。在长曝光、高增益的暗光场景下,sensor自身的暗电流会在左右像素上产生不同的固定偏置,这个偏置会叠加在光信号上,导致Gainmap在暗光下严重高估。如果在标定中发现暗场景下PDAF容易误判方向,要特别留意是不是这个原因。
暗电流的补偿方式是在完全遮光、相同曝光参数下采集一帧纯噪声数据,记为D(i)。修改Gainmap的计算公式为:
G(i) = sum_k (L(i,k) - D_L(i)) / sum_k (R(i,k) - D_R(i))
这样得到的增益系数才是真正意义上的“光响应增益差”,暗光下的补偿效果会好很多。
3.4 DCC曲线拟合与分区建模
DCC拟合是本流程中最需要细心的一步。我们前面说过,PD值到defocus的映射不一定是线性的,所以在获取足够多的采样点后,通常的做法是用分段线性插值来拟合整条曲线。
具体操作是把VCM的整个行程等分成若干段(比如16段或32段),在每个段的端点位置精确采集PD值。采集方法要先对焦上目标,然后手动让VCM越过合焦位置正负10%的区间,记录对应的PD值。连续的PD-defocus曲线会呈现一个典型的S形:中心区域斜率最大,两端趋于饱和。在中心区域设置更密的采样点,有助于提高对焦收敛精度。
拟合结束后,还需要做“线性化检查”。把拟合出的DCC曲线反向映射,看PD值到defocus的变换是否依然保持单调性。如果不单调,说明某个采样点采集时VCM没稳定,或者标板表面有局部反光。这种时候千万不能图省事直接平滑数据,因为单调性是PDAF算法能正确判断方向的数学前提。我的经验是:遇到多个采样点违背单调性,直接重测更可靠。
还有一个值得说明的点:DCC曲线的中心区域斜率,决定了算法在对焦临界区域的灵敏度。斜率太小,算法会感觉“对焦已经完成了”而提前停止;斜率太大,算法在临界点附近容易过冲抖动。最好的情况是让中心区域斜率保持在0.8到1.2之间,这是实际落地时比较安全的区间。
3.5 参数写入与端到端验证
参数计算完毕以后,就可以写进sensor的驱动或ISP的寄存器配置了。Gainmap通常放在sensor驱动的PD校准区域或者ISP的统计模块;DCC曲线一般放在AF算法的配置文件中,有些平台支持分场景多组DCC,比如normal模式、macro模式、infinity模式各存一组。
写入参数后,要做四件事来验证效果。第一,在近、中、远三个距离分别做对焦测试,记录从触发对焦到锁定焦点的时间,必须小于产品规格要求。第二,反复对焦同一目标10次,看对焦位置是否稳定在一个小范围内,如果不稳定说明DCC曲线在某个区域斜率过陡、增益太大。第三,在暗光和亮光下分别测试,确保Gainmap动态范围覆盖到位。第四,用手遮挡镜头一半后放开,模拟目标切换场景,观察算法是否能够快速收敛到新目标的距离上。
这四轮验证里最容易暴露问题的就是第二项:对焦稳定性。如果你发现同一个目标的最终位置出现了明显的来回抖动,先不要急着去调DCC,回头检查一下Gainmap的逐像素一致性。很多时候抖动不是DCC斜率的问题,而是某几个PDAF像素的Gainmap错误,导致统计结果被带偏。
4. 常见问题与排查技巧实录:我踩过的那些坑
标定PDAF和写普通代码不一样,它的问题是“层叠式”的——你可能费了很大力气调好了Gainmap,结果发现DCC还是要重新拟合;等你重新拟合了DCC,又发现边缘PDAF区域的精度不够,必须回去改标定环境。下面是把我在实际项目中遇到的典型问题整理成表,供大家快速对照。
| 问题现象 | 最可能原因 | 排查方向 | 解决建议 |
|---|---|---|---|
| 对焦来回抽动、无法锁定 | DCC中心区域斜率过陡 | 检查临界区间的PD-defocus映射是否精度足够 | 在合焦点附近加密采样点,降低中心区域增益 |
| 近距离对焦慢 | VCM行程末端非线性 | 检查DCC长距离端是否饱和 | 增加近距离采样点,尝试分段曲线分离标定 |
| 远距离对焦失败 | 无限远PD值饱和 | 检查镜头是否真正到达远焦位置 | 确认VCM infinity位置寄存器值,必要时重新校准 |
| 暗光下对焦误判方向 | 暗电流未补偿 | 检查Gainmap在低亮度下的残差 | 增加暗电流标定,使用去偏置后的公式 |
| 同一目标多次对焦位置漂移 | 特定PDAF像素的Gainmap异常 | 检查逐像素残差矩阵 | 重新采集均匀光照数据,单独修正异常像素系数 |
| 画面边缘区域对焦不准 | 边缘单分区DCC不准 | 检查边缘PD值分布 | 增加画面分区,建议至少5X5分区拟合独立DCC |
4.1 对焦来回抽动:干扰来自身边
抽动问题很经典,尤其在VCM驱动电流比较大的模组上。我遇到过一次很“邪门”的现象:标定台上所有指标都正常,一装到整机里就开始来回抽动。排查到最后,发现是模组的FPC排线布局靠近VCM驱动走线,驱动电流瞬间变化时产生了电磁干扰,影响了PD值采集的模拟信号。重新走线后问题消失。
建议大家在遇到“实验室正常、装机异常”的时候,先检查一下模组线缆的屏蔽和走向,再去动算法参数。这类干扰问题靠参数是调不好的,只会越调越乱。
4.2 暗光下的方向误判
暗光下的误判,很多情况是真冤枉了算法,问题出现在sensor的噪声模型上。PDAF左右像素的输出差值本身就包含了几种噪声:光子散粒噪声、读出噪声、暗电流散粒噪声。在暗光低曝光条件下,读出噪声占主导,它不服从零均值高斯分布,特别是列并行ADC方案中,每一列的偏置电压略有不同,会直接导致特定列区域内的左右像素输出差出现系统性偏移。
解决方法是在标定流程中增加一个“噪声底部”的数据采集——在完全遮光的条件下,用和实际场景相同的曝光时间和增益采集一段数据,把各像素的平均噪声底部作为偏置减去。很多SDK里把这个功能叫做PD dark calibration或者black level calibration,不同平台叫法不同,但作用都是一样的:给PD值做一次“零位校正”。
4.3 多片模组一致性问题
量产阶段最常见的抱怨是“这批模组里随机抽几片,PDAF效果各不相同”。这个问题的本质在于:VCM马达的灵敏度个体差异、镜头组装的倾斜公差、sensor封装应力的释放程度,都会让Gainmap和DCC产生差异。如果在产线上只标定一片“黄金模组”,再把参数复制到所有模组,效果必然打折扣。
我的建议是量产标定至少要做到两件事:一是在产线上增加一个简单的Gainmap标定工位,用标准光源和灰板自动计算每片模组的Gainmap,写入设备端非易失存储;二是对DCC做抽检,每批次至少抽5片做全流程标定,统计DCC曲线斜率的方差,如果方差超过±10%,就要反馈给镜头或马达供应商,要求他们改善一致性。
4.4 RV1126B上的一个具体排查案例
最后分享一个发生在RV1126B平台上的具体案例,这个对参考价值帮助最大。现象是PDAF在10cm近景时对焦速度明显变慢,偶尔会拉到整个VCM行程走一遍才回来。我一开始怀疑是DCC近距离段拟合不准,但重新标定后问题依旧。
后来我直接在ISP的raw域dump了PD区域的像素数据,发现一个细节:近景对焦时,镜头在某个VCM位置附近,PD值的变化率明显下降,几乎进入了一个“平台期”。追查原因,是镜头在近物距时,光线透过镜头的入射角度发生了明显变化,导致左右子像素的遮蔽结构产生的PD值不再随离焦量线性变化。
解决方法是把近物距区间单独做一组DCC拟合,并和正常物距的DCC做平滑过渡,而不是用一条曲线硬套整个行程。改完之后,近景对焦速度从原来的2000ms+降到了400ms以内。这个案例给我们的提醒是:任何一条DCC曲线都有自己的适用范围,不要指望一条曲线覆盖从微距到无穷远的全部场景。如果你的产品经常在近景和远景之间切换,至少要有两到三组DCC做分场景切换。
结尾
做PDAF标定这件事,表面上是改参数、跑曲线、写寄存器,实际上做的是“光学、半导体、机械、算法的交叉拧螺丝”。那块PD像素阵列,可能是整个sensor里最敏感的区域,标定环境的一点温度漂移、VCM的一丝机械滞回,到最后都会转变成实拍画面里的一次对焦失败。
我个人实际操作中最大的体会是:不要迷信vendor的默认参数,也不要跳过任何一步看起来“可以凑合”的校准环节。Gainmap不准确,后面优化再多DCC都是白搭;DCC不拟合当前模组,对焦速度再快的算法也会被糟蹋成“拉风箱”。标定的每一步,本质都是在为算法提供可靠的数据源,把这个源头守住了,后面的事才会顺。
最后再分享一个小技巧。如果你在产线上做PDAF标定,一定把每一次标定的原始数据(PD值、VCM位置、温度、曝光参数)全部留存下来,最好能做到按模组SN号归档。一旦某批模组在用户端出现对焦故障,你回查数据要比重新架起标定台快得多。这个习惯救过我很多次,也建议你从现在开始养成。