上周调试双目视觉系统时同事突然问我一个问题同样一组棋盘格图像昨天标定的焦距和今天差了2%到底是相机没固定牢还是光照变化引起的我没有直接回答而是让他先盖上镜头盖拍30帧暗场再用均匀面光源拍一组不同曝光时间的平场。等光子转移曲线PTC的参数出来之后问题就清楚了传感器的读出噪声和增益基本没变标定结果的偏差更多来自棋盘格平放时的覆盖角度不够以及标定板表面轻微反光造成的角点抖动。这个排查思路本质上就是把计算成像里的两个经典工具放在一起用一个是描述像素灰度可信度的噪声模型一个是描述像素几何映射关系的张正友标定。前者回答“信号有几成是真的”后者回答“像素坐标到空间坐标怎么换算”。今天我们就把这两块内容完整走一遍从泊松噪声的物理来源到光子转移曲线的数据采集和拟合再到张正友标定的原理、实操和踩坑经验一次讲透。1. 为什么噪声模型和几何标定要放在一起看很多做视觉的开发者会有一个误区觉得张正友标定只是“拍图、找角点、算内参”三步走只要重投影误差小于0.1像素就万事大吉。但实际工程里标定结果的稳定性高度依赖于图像本身的质量而图像质量的下限恰恰由噪声模型决定。一张标定板图像里每个像素的灰度值可以看成三部分叠加真实信号、散粒噪声、读出噪声。散粒噪声服从泊松分布方差等于信号本身的电子数读出噪声是传感器读电路引入的固定底噪。这两个参数直接决定了角点检测的亚像素精度能到多少。比如我在某台工业相机上测过读出噪声大约4个电子增益2.5 e-/DN在暗光环境下拍摄棋盘格时暗部角点的灰度波动折算成亚像素偏差可以达到0.3像素以上。这种误差会直接污染标定内参。换个角度说几何标定假设“像素灰度服从理想成像模型”噪声标定则告诉你这个假设的边界在哪里。两者放在一起才构成完整的相机模型PTC告诉你每个DNDigital Number相当于多少个电子、系统底噪多高、满阱容量多大张正友标定告诉你每个像素从传感器坐标到空间坐标的准确映射关系。前者决定了灰度层面的物理可信度后者决定了几何层面的空间可信度。所以这一讲的核心逻辑很明确先用噪声模型和PTC把传感器的物理参数摸清再做张正友几何标定保证标定输入图像是干净和可信的。实际项目里这两步我通常放在同一个标定场景内完成先固定相机和光源采集暗场和平场再摆放棋盘格一气呵成。这样后期如果标定结果出现异常至少可以排除图像质量这个变量。2. 相机噪声的物理来源与数学模型2.1 光子散粒噪声为什么必然存在光是由大量光子组成的光子到达传感器感光面的时间点并不是均匀分布的。单位时间内到达的光子数服从泊松分布该分布的一个数学性质是期望值等于方差。也就是说如果平均有N个光子到达那么单次测量中光子数的波动标准差是根号N。这个波动叫散粒噪声也叫泊松噪声。它是量子统计决定的无法通过延长曝光时间或提升传感器质量完全消除只能通过累积更多光子来提高信噪比。比如平均收到100个光子时噪声标准差是10信噪比是10收到10000个光子时噪声标准差是100信噪比提升到100。这解释了为什么高曝光、大光圈能改善暗光成像本质上是在提高光子计数样本量。把光子转换为光电子后散粒噪声同样存在。光电子数的方差等于光电子数的期望值这是泊松过程的传递结果。在图像上表现为均匀灰板图像中每个像素的灰度并不是完全一致的而是围绕平均值上下波动波动幅度随信号强度增大而增大但增大的速度低于信号本身所以亮部信噪比更高。2.2 读出噪声、暗电流与固定模式噪声散粒噪声以外传感器还存在几种固定的噪声源。读出噪声是每个像素的光电荷经过模拟电路读出、放大、模数转换时引入的电子噪声它和曝光时间无关近似服从高斯分布。我们可以把传感器输出信号模型中的电子数表示为μ_e (Φ×QE×t) I_d×t μ_dark其中Φ是光子通量QE是量子效率t是曝光时间I_d是暗电流速率μ_dark是暗信号基准。读出噪声σ_r是在暗场条件下测得的电子数标准差它叠加在最终数据上。暗电流是半导体热激发产生的电子温度每升高约7℃暗电流大致翻倍。长时间曝光时暗电流会成为主导噪声源而且像素之间的暗电流速率并不完全相同导致暗场图像出现固定的图案噪声也就是固定模式噪声。此外像素响应率差异也会形成另一个固定模式分量表现为亮场图像中的网格状条纹。2.3 全链路噪声合成模型把上述噪声统一起来从电子域到ADU域的全链路模型可以写为σ²_total σ²_shot σ²_dark σ²_readout σ²_FPN其中散粒噪声方差等于信号电子数期望值μ_e暗电流散粒噪声方差等于暗电流电子数期望值读出噪声是常数FPN按像素固定偏移处理。换到ADU域后需要除以增益Ke-/DN的平方σ²_ADU (μ_e / K²) (σ_r_e² / K²)写成更直观的形式σ²_ADU (S_ADU / K) σ_readout_ADU²这个式子就是光子转移曲线的基本出发方程。S_ADU是扣除暗场后的平均灰度σ²_ADU是图像灰度的总方差K是系统增益σ_readout_ADU是读出噪声的DN值标准差。从形式上看方差-均值曲线是一条斜率约为1/K的直线截距是读出噪声方差饱和后曲线拐弯向下。这个规律让PTC实验成为可能。3. 光子转移曲线实验与参数标定3.1 数据采集暗场与平场光子转移曲线的实验本质是测量不同信号水平下的图像方差再拟合成方差-均值图。采集分为两部分暗场数据盖上镜头盖分别用几个短曝光时间拍30帧以上暗场图像。注意曝光时间不能太长否则暗电流累积会把方差推高污染读出噪声测量。我通常用最接近实际曝光但不超过20ms的几组时间并确保环境完全避光。平场数据用均匀面光源照射相机固定光圈和增益调整曝光时间从短到长逐步增加让平均灰度从接近暗场逐渐增加到接近饱和。每个曝光级别拍30帧以上。这里的关键是光源要均匀和稳定尤其是LED面光源预热10分钟后再开始采集否则亮度漂移会直接变成额外方差。3.2 数据整理与增益计算对每个曝光级别计算两组统计量一组是时间维度的平均信号一组是单帧图像内的空间方差。实际使用中我倾向于混合计算先计算所有帧的均值图再求该均值图的空间均值作为信号水平同时计算每帧的空间方差再对所有帧的方差取平均得到该水平下的总方差。这样能抑制帧间光源波动的影响。以手头某工业相机的一组实测数据为例我整理如下K2.5 e-/DN曝光时间(ms)平均信号(DN)总方差(DN²)信号电子数(e-)10420172105020820332205040168067642008033501342837512051202066128001606700270416750将平均信号作为横轴、总方差作为纵轴取线性区间的数据点做线性拟合。理想情况下拟合结果是σ²_ADU 0.396 × S_ADU 2.5于是增益K 1 / 0.396 ≈ 2.53 e-/DN。截距2.5 DN²开根号约1.58 DN换算成电子域就是约4个电子对应传感器标称读出噪声水平。3.3 从PTC延伸出的满阱容量与动态范围PTC曲线进入饱和后方差不再随均值上升这个临界点对应的信号电子数就是满阱容量。以上面的数据为例如果饱和平均信号约12000 DN那么满阱容量约30000 e-。结合读出噪声4个电子可以得到动态范围约75 dB换算成有效位数约12.5 bit。这些参数对HDR合成、多帧降噪和曝光策略设计非常有用。我在工程里的用法是先用PTC确认相机当前增益模式下有没有开额外的机内降噪或暗场扣除如果有拟合出来的方差曲线在低信号段会有明显非线性拐点读到这种曲线就说明测量设置脏了需要关掉相机内部的降噪、锐化和自动增益后再测。注意EMVA 1288标准里对增益K的定义是e-/DN所以方差-均值图的斜率是1/K。有的资料把增益定义成DN/e-那么斜率就直接等于K。两种资料混看时很容易算反建议统一用e-/DN并在报告中写清楚公式形式。3.4 PTC实操中的关键细节第一平场光源不均匀性会抬高方差。判断方法是拍摄同一信号水平下的两帧图像相减如果相减后图像还残留固定条纹说明光源有空间结构或者传感器有很强的响应不均匀性。这种情况下可以先对平场做白平衡归一化处理或者改用积分球光源。第二暗场热像素会影响方差统计。热像素在长曝光时表现为异常亮点和暗点它们在空间方差中贡献巨大。一个可行办法是使用中值滤波或分位数法剔除上下5%的像素后再计算方差。第三每档曝光条件下帧数不能太少。我通常不少于30帧经验上少于20帧时方差估计自身的波动会达到10%以上。如果追求更高精度可以拍到50帧按均值上下浮动不超过1个DN来判断数据是否稳定。4. 张正友标定完整实操与原理拆解4.1 标定板选择与图像采集规范张正友标定的输入是一组不同姿态的平面标定板图像。标定板优先选择棋盘格因为棋盘格的角点检测非常简单亚像素精度高且角点附近没有不规则纹理对镜头畸变的敏感度较低。圆点阵列标定板也可以使用不过在透视畸变较大时圆点会变成椭圆椭圆中心拟合法不如棋盘格角点稳健。采集图像时有几个硬性要求棋盘格在画面中至少占1/4以上太小会导致角点间距在像素域不足。每张图的棋盘格姿态要有明显差异包括偏转、倾斜和前移后移整套图要覆盖画面的中心和四个角落。我习惯一次拍15到20张宁可多拍不可少拍但也不要只在一个平面上平移拍摄那样会退化成单平面约束内参解会不稳定。光照方面要保证棋盘格亮部和暗部对比清晰避免反光和阴影遮挡角点。如果使用机械臂或人工摆放标定板边缘不能有遮挡物。镜头光圈在标定过程中保持不变固定焦距后再标定标定结束后不要再旋动变焦环否则内参失效。对变焦镜头每个常用焦距段都需要单独标定一次。4.2 张正友算法的原理为什么平面标定板就够了张正友算法的核心出发点是标定板平面上的点与其像素像点之间存在一个单应性矩阵H表达式为s×p̃ K×[r1 r2 t]×P̃其中p̃是像素齐次坐标P̃是棋盘格平面上的齐次坐标K是内参矩阵r1、r2是旋转矩阵的前两列t是平移向量。由于标定板的Z坐标恒为0单应性矩阵H可以写为H K×[r1 r2 t]单应性矩阵H有8个自由度。位姿不同H也不同。但无论位姿怎么变K始终是一样的。利用旋转矩阵的性质r1和r2必须正交且长度相等于是从每个H可以得到两个约束方程。内参矩阵有4个未知数fx、fy、cx、cy加上畸变系数理论上三张姿态差异足够大的图就可以求解初值但实际建议15张以上来抵抗噪声和角点检测误差。这个过程可以理解成我们用一把已知尺寸的“尺子”棋盘格在相机前摆出各种朝向从不同角度下尺子的投影变形来反推镜头内部参数。摄像头不需要知道尺子在空间中的绝对位置算法会从单应性矩阵中一并解出位姿。4.3 基于OpenCV的实现步骤用OpenCV做张正友标定流程可以拆解为四步角点检测、亚像素细化、内参初解、非线性优化。角点检测使用 findChessboardCorners亚像素细化使用 cornerSubPix代码如下import cv2 import numpy as np # 棋盘格内角点尺寸例如 9x6 pattern_size (9, 6) objp np.zeros((pattern_size[0] * pattern_size[1], 3), np.float32) objp[:, :2] np.mgrid[0:pattern_size[0], 0:pattern_size[1]].T.reshape(-1, 2) obj_points [] img_points [] criteria (cv2.TERM_CRITERIA_EPS cv2.TERM_CRITERIA_MAX_ITER, 30, 1e-5) for fname in image_paths: img cv2.imread(fname) gray cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) ret, corners cv2.findChessboardCorners(gray, pattern_size, None) if ret: corners2 cv2.cornerSubPix(gray, corners, (11, 11), (-1, -1), criteria) obj_points.append(objp) img_points.append(corners2) cv2.drawChessboardCorners(img, pattern_size, corners2, ret) ret, mtx, dist, rvecs, tvecs cv2.calibrateCamera( obj_points, img_points, gray.shape[::-1], None, None, flagscv2.CALIB_RATIONAL_MODEL)这里我建议打开 CALIB_RATIONAL_MODEL把畸变模型扩展到k1、k2、k3、p1、p2共5个参数。OpenCV默认只有4个参数打开后能更好处理较大畸变的镜头。4.4 标定结果如何判读calibrateCamera 返回的 ret 是重投影均方根误差单位像素。一般来说普通镜头在0.05到0.15像素之间属于正常达到0.1像素左右就算不错。如果超过0.3像素先不要急着改代码应该回头检查标定板是不是翘曲了或者某张图的棋盘格角点检测错了。内参矩阵里的fx、fy对应镜头的等效焦距单位是像素。可以用传感器像素物理尺寸把fx换算成物理焦距看是否与镜头标称值接近。例如某相机像素尺寸3.45μm标定得到的fx为4200像素那么物理焦距约14.5mm如果镜头标称焦距15mm基本吻合。畸变系数里k1通常为负值表示桶形畸变如果k1为正可能是枕形畸变需要结合镜头规格判断。径向畸变主导时k1的绝对值在0.1到0.3之间很常见如果k1值异常大比如超过1多半是标定数据质量有问题或者畸变模型选择不合适。切向畸变p1、p2通常都很小如果p的绝对值超过0.1优先怀疑镜头装配或者标定板倾斜过度。我自己在项目里验证标定结果是否可用的一个土办法把标定板放在画面不同位置用标定出的内参和畸变系数把角点图像去畸变再手动测量棋盘格横纵像素距离是否在不同位置保持一致。如果标定准确去畸变后的棋盘格应该在不同位置都有相同边长的矩形。5. 常见问题与排查技巧实录5.1 PTC数据拟合异常问题可能原因排查与解决方差-均值曲线在低信号段明显上弯相机开了降噪或暗场扣除关闭机内降噪、锐化、自动对比度后重测拟合斜率偏小增益异常大平场光源不均或曝光时间不准光源预热改用积分球检查曝光同步暗场方差过大镜头盖漏光或热像素干扰优化遮光剔除热像素后统计曲线饱和后方差仍然上升光源强度漂移缩短总测量时间用帧间统计量核对光源稳定性5.2 张正友标定常见问题问题可能原因排查与解决findChessboardCorners 找不到角点棋盘格太小或反光换大棋盘格提高对比度打开ADAPTIVE_THRESH某张图角点顺序错乱棋盘格边缘被遮挡删掉该图保证整个棋盘格完整入镜重投影误差集中在边缘畸变模型不足或标定板不平打开CALIB_RATIONAL_MODEL换硬质标定板标定结果漂移图像姿态覆盖不够增加倾斜和旋转角度不要只平行移动不同时间标定结果不一致镜头焦距或光圈变化用锁定螺丝固定镜头标定期间不要碰变焦环这里说一个我踩过的坑有一次标定结果一直不好重投影误差超过0.5像素角点检测也间歇性失败。排查很久之后发现不是算法问题而是标定板表面印刷的黑色格子过于反光在灯光下形成了局部高光导致角点附近出现伪边缘。后来把光源改成漫射型调整角度再拍摄就一切正常。所以遇到标定问题先检查图像本身不要一上来就怀疑算法参数。另一个经验是标定板别买太薄的亚克力或纸质板人手按压时中心会轻微拱起。如果是普通纸张打印的棋盘格贴在一块平整的玻璃板或铝合金板上再使用能明显减少重投影误差。我的经验是散热效果好的金属板本身平整度就够配合漫射光源后标定精度能稳定进入0.1像素以内。关于PTC和张正友标定的执行顺序我在实际项目中习惯固定为先做PTC确认相机增益和噪声水平再做张正友标定。原因很简单PTC测量过程中需要大量均匀光照和暗场采集这些操作会揭示出传感器的固定图案噪声分布、坏点位置和响应不均匀性。提前掌握这些信息后续标定图像的角点检测和边缘质量都有预判不至于被某张异常图像带偏。如果你手里的相机支持无损格式输出建议全程使用原始格式数据关闭一切自动增强和色彩处理管道。标定算法本身对色彩不敏感但机内锐化和降噪会显著影响角点亚像素精度。我在某款工业相机上对比过打开降噪后重投影误差从0.08像素放大到0.2像素差距非常明显。最后补充一个可以继续深入的方向把PTC测得的噪声参数直接嵌入标定算法中在角点亚像素定位时按噪声强度加权理论上能把弱纹理区域的角点权重降下来、强纹理区域的角点权重提上去。这个思路目前在工程里还不常见但值得一试。我自己在模拟数据集上验证过读取噪声较大的低照度场景下加权方法比普通标定提升约两成精度。如果你手头的项目经常在暗光环境工作可以顺着这个方向继续优化。