张正友标定法全解析:相机内参标定原理与OpenCV实操避坑指南
发布时间:2026/9/29 1:47:35来源:尧图网络
相机内参标定是所有视觉项目的“第一道门槛”。做SLAM、做双目测距、做工业视觉定位早晚都要过这一关。而提到相机内参标定绕不开的名字就是张正友标定法。这个1998年发表的方法硬是用一块平面棋盘格解决了过去需要高精度三维标定块才能完成的内参标定问题直到今天OpenCV里的calibrateCamera、Matlab里的Camera Calibrator底层走的都是这套理论。很多朋友拿着棋盘格对着摄像头咔咔拍20张跑完OpenCV代码发现重投影误差0.2px就觉得万事大吉。但一旦换场景、换相机标定结果忽好忽坏就不知道怎么排查了。这篇文章我就从理论上把张正友标定法彻底拆开讲清楚每一步在解什么方程、每个约束从哪来、参数怎么求出来再把实操中容易踩的坑一并总结。适合刚入门视觉的初学者也适合已经会用OpenCV但还没搞懂原理的工程师。1. 相机内参标定到底在解什么题1.1 从三维世界到像素坐标的三次变换相机的工作本质上是把一个三维空间点投影到二维像素平面上。这个过程可以拆成三步。第一步世界坐标系到相机坐标系的刚体变换。相机在世界中有一个位置和姿态对应一个旋转矩阵R和平移向量t。世界坐标系下的点$P_w[X,Y,Z]^T$经过这个变换得到相机坐标系下的坐标$P_cR P_w t$。这一步携带的是相机的外参也就是相机在世界中的位姿。第二步相机坐标系到归一化平面坐标的透视投影。把$P_c[X_c,Y_c,Z_c]^T$除以深度$Z_c$得到归一化坐标$(x,y)(X_c/Z_c, Y_c/Z_c)$。这一步把人眼看到的“近大远小”效果体现出来了本质是小孔成像模型。第三步归一化坐标到像素坐标的仿射变换。归一化坐标是物理单位毫米像素坐标是离散的像素两者之间相差一个缩放和偏移$uf_x x c_x$$vf_y y c_y$。这里的$f_x,f_y$就是焦距的像素表示$c_x,c_y$是主点坐标。把三步合起来写成一个齐次坐标的矩阵方程s·[u v 1]ᵀ K·[R|t]·[X Y Z 1]ᵀ其中K就是内参矩阵。这个方程是整套标定理论的起点你要是能把这个式子的每一项都说清楚标定对你来说就成功了一半。1.2 内参矩阵K的物理含义内参矩阵K长这样K [fx s cx] [0 fy cy] [0 0 1 ]fx、fy是焦距单位是像素而不是毫米。为什么因为图像传感器上每个像素是一个小的感光单元物理尺寸是微米级的焦距除以像素物理尺寸就得到了以像素为单位的焦距。所以同样一个镜头装在像素尺寸不同的传感器上fx也不一样。这也是为什么相机说明书上写的焦距是毫米比如8mm、12mm定焦镜头但标定出来的fx是几千像素两者之间差了个像素密度。cx、cy是主点也就是光轴与成像平面的交点。理想情况下主点就在图像正中心比如1280x720分辨率的图像主点应该是(640, 360)。但实际生产装配中镜头和传感器不可能完美居中主点会有几个甚至几十个像素的偏移。注意主点偏移不是“坏了”是正常的物理现象不能想当然地拿图像中心去替代。还有一个参数s叫skew表示像素的x轴和y轴不完全垂直导致的倾斜。现代相机制造工艺好s通常接近0所以OpenCV默认把它设为0参与估计。但理论上它是存在的张正友原论文里也把它包含在推导中。1.3 畸变理想模型之外的现实针孔模型是理想情况但真实镜头是玻璃透镜光线经过透镜会发生折射导致实际成像和理想模型之间有偏差这就是畸变。畸变主要分两类。第一类是径向畸变由镜头形状引起包括桶形畸变和枕形畸变。光线离光轴越远折射越明显所以畸变在图像边缘最严重。径向畸变通常用三个系数描述$k_1,k_2,k_3$畸变后的坐标与原坐标满足x_distorted x·(1 k1·r² k2·r⁴ k3·r⁶) y_distorted y·(1 k1·r² k2·r⁴ k3·r⁶)这里的$r^2x^2y^2$是归一化平面坐标到光轴的距离。注意k1和k2对畸变的贡献最大k3只在畸变很大的镜头比如广角镜头中才需要。第二类是切向畸变源于镜头和传感器装配时不完全平行。切向畸变用两个系数$p_1,p_2$描述x_distorted x [2p1·xy p2·(r² 2x²)] y_distorted y [p1·(r² 2y²) 2p2·xy]标定的任务就明确了求出内参矩阵K里的fx、fy、cx、cy以及畸变系数k1、k2、p1、p2、k3。把这几个数搞准相机的“视觉模型”就完整了。2. 张正友标定法的破局思路2.1 为什么是棋盘格在张正友之前相机标定用的都是三维标定块两块或者三块相互垂直的平面上面画着精确的图案。这种标定块加工精度要求极高——你没法用相机标定来验证标定块本身的加工精度标定结果上限被标定块精度锁死了。而且标定块又贵又笨重。张正友的思路是我拿一块平面棋盘格怎么着都行只要图案打印得够清晰、贴得够平整就够了。这是降维打击。三维问题变成二维问题标定块从“精密加工件”变成“一张打印纸”。但平面棋盘格怎么提供三维信息呢玄机在于多拍照片。棋盘格不动相机动或者棋盘格动相机不动。每一张照片对应一个不同的位姿每张图的外参R和t都不一样。把多张图的信息综合起来就等效于拥有了三维空间中的约束。这就像你用一只眼睛看一个物体看不出深度但换个角度再看一次大脑就能重建出立体感。棋盘格还有一个优势角点检测。黑白格交界处的角点在图像上是一个稳定的特征点可以通过亚像素算法精确定位到0.1像素甚至更高的精度。标定算法对输入点坐标的精度极其敏感输入噪声稍微大一点解出来的参数就偏了。棋盘格的角点恰恰是自然界里最容易精确检测的图案特征。2.2 单应矩阵一举打通内外参关键的一步来了。我们让世界坐标系的XY平面贴在棋盘格平面上也就是棋盘格上所有点的Z坐标都为0。这样外参中的旋转矩阵R[r1 r2 r3]里r3那一列就被“吃掉”了因为Z方向的坐标恒为0。代入成像方程s·[u v 1]ᵀ K·[r1 r2 r3 t]·[X Y 0 1]ᵀ K·[r1 r2 t]·[X Y 1]ᵀ令$HK[r1\ r2\ t]$H是一个3x3的矩阵把棋盘格平面的点直接映射到像素平面s·[u v 1]ᵀ H·[X Y 1]ᵀ这个H就是单应矩阵。它的意义在于棋盘格平面上任意一个点只要知道它在棋盘格上的坐标(X,Y)就能通过H算出对应的像素坐标(u,v)。反过来给出一组一一对应的点对也能把H解出来。每张棋盘格照片都能解出一个H。而H里面同时包含了内参K和外参这张照片对应的r1、r2、t。内参是固定的外参每张图各不相同。现在的问题是从H里怎么把K单独剥出来2.3 旋转矩阵的正交约束是解题钥匙核心破局点在于旋转矩阵的两个列向量r1和r2不是随便什么向量都能当的。旋转矩阵本身有严格的性质——列向量之间正交且模长为1r1ᵀ·r2 0 r1ᵀ·r1 r2ᵀ·r2 1这两个性质是所有旋转矩阵与生俱来的不随拍摄角度改变。从$HK[r1\ r2\ t]$可以反推$r1K^{-1}h1/λ$$r2K^{-1}h2/λ$其中h1、h2是H的前两列λ是齐次因子。把r1、r2的表达式代入正交约束得到h1ᵀ·K⁻ᵀ·K⁻¹·h2 0 h1ᵀ·K⁻ᵀ·K⁻¹·h1 h2ᵀ·K⁻ᵀ·K⁻¹·h2这两个方程里只剩下内参K和单应矩阵H外参被消掉了。每张照片提供一个H就提供两个关于K的约束方程。这就是张正友标定法的精髓利用旋转矩阵的正交性把外参这个“无关变量”干净利落地消除。3. 核心推导从单应矩阵到内参封闭解3.1 单应矩阵H怎么估计第一步是解H。已知棋盘格上角点的世界坐标(X,Y)和对应的像素坐标(u,v)一组对应点可以提供两个方程。H是一个3x3齐次矩阵有8个自由度整体缩放不影响所以理论上4组点对就能解。实际操作中一副棋盘格上有几十个角点方程数远超未知数用最小二乘求解。具体做法是把H按行展开把点对的约束拼成一个大的线性方程组[u v 1] H·[X Y 1]ᵀ展开后是个欠约束齐次线性系统用SVD求最小奇异值对应的右奇异向量就得到了H的估计。这个流程叫DLT直接线性变换。OpenCV里求解单应矩阵还会用RANSAC剔除角点误匹配保证解出来的H对噪声不是太敏感。这里要注意H的解是带尺度因子λ的。也就是说$H$和$λH$描述的是同一个映射。这个λ不参与后续求解吗参与但会在推导中被巧妙消掉所以不必提前纠结。3.2 内参约束方程与对称矩阵B既然每个约束里都出现$K^{-T}K^{-1}$我们干脆令$BK^{-T}K^{-1}$。把B展开它是一个对称3x3矩阵B [B11 B12 B13] [B12 B22 B23] [B13 B23 B33]对称矩阵只有6个独立元素于是用一个小技巧把方程改写。定义向量$b[B11, B12, B22, B13, B23, B33]^T$那么二次型$h_i^T B h_j$可以改写成h_iᵀ·B·h_j v_ijᵀ·b其中v_ij是一个由h_i和h_j的元素组合成的6维向量v_ij [h1i·h1j, h1i·h2j h2i·h1j, h2i·h2j, h3i·h1j h1i·h3j, h3i·h2j h2i·h3j, h3i·h3j]看不懂这个向量没关系你只要明白原来的矩阵二次型被改成了6维向量b的线性方程。这一步是数值计算的关键因为线性方程比你想象中好解得多。两张正交约束方程改写为v12ᵀ·b 0 (v11 - v22)ᵀ·b 0每张棋盘格照片贡献两个方程。如果有n张照片就得到2n个方程拼成矩阵形式V·b0。V是一个2n×6的矩阵。b有6个未知数不考虑尺度所以理论上至少需要3张照片才能构成一个齐次线性系统并求解。这就是为什么大家都说张正友标定至少要拍3张以上的图实操中则建议拍到10到20张。V·b0是齐次线性方程组b的尺度是任意的。用SVD对V进行分解最小奇异值对应的右奇异向量就是b的解。到这里内参矩阵元素组成的中间变量B已经拿到手了。3.3 从B到内参的封闭解现在要做的是从B里把fx、fy、cx、cy提取出来。这步是纯代数运算。已知$BK^{-T}K^{-1}$把K的表达式代进去反解出v0 (B12·B13 - B11·B23) / (B11·B22 - B12²) λ B33 - [B13² v0·(B12·B13 - B11·B23)] / B11 fx sqrt(λ / B11) fy sqrt(λ·B11 / (B11·B22 - B12²)) gamma -B12·fx²·fy / λ u0 gamma·v0 / fy - B13·fx² / λ这里v0就是cxu0就是cy。看着公式多本质上就是从六元方程里一个一个消元。如果你推导的时候假设skew参数gamma0公式会简化不少市面上绝大部分标定库也都是这么做的。有兴趣的话你可以验证一个特例当不存在噪声、且B表达精确时这个公式能完美还原你预设的内参值。我在学这个方法的时候就是先造了一组理想数据测试公式发现结果分毫不差才真正相信这套推导是对的。3.4 畸变系数估计与最大似然优化到此为止求出来的内参是在不考虑畸变的前提下得到的封闭解。但真实相机有畸变所以接下来两步走。第一步估计畸变系数。在不考虑畸变的情况下我们已经拿到了内参K和外参每张图的R和t。把棋盘格角点的世界坐标通过内外参投影到像素平面得到无畸变的投影点。再把畸变模型代入让无畸变投影点和真实像素点之间的残差最小解出畸变系数k1、k2、p1、p2的初始值。这一步依然是线性最小二乘因为畸变模型对畸变系数是线性的。第二步联合非线性优化。之前所有步骤的误差是累积的单应矩阵估计有噪声B的求解是线性近似畸变系数是单独估计的。要得到高精度结果必须把所有参数放在一起优化。优化目标是最小化所有角点的重投影误差总和min Σ || u_observed - u_project(K, k1, k2, p1, p2, R_i, t_i, X_j) ||²这是一个非线性最小二乘问题用Levenberg-Marquardt算法求解。LM算法是高斯牛顿法和梯度下降法的结合对初始值有一定要求而我们的封闭解恰好提供了足够好的初始值。这也是张正友标定法的完整闭环先线性求初始值再非线性精化。少了封闭解直接上非线性优化大概率会收敛到错误的局部极小值。关于畸变系数OpenCV的默认实现支持5个畸变参数[k1, k2, p1, p2, k3]。在标定时可以通过flags参数选择是否估计某些系数比如CALIB_FIX_K3就是固定k3为0CALIB_ZERO_TANGENT_DIST就是固定切向畸变为0。对于畸变很小的工业镜头适当固定高阶畸变参数反而能提高稳定性。4. 理论落地标定实操的细节与避坑指南4.1 采图规范角度、数量、覆盖范围理论和实操的差距往往比看起来大得多。我见过不少同学算法流程写得完美但标定结果翻车原因几乎出在采图上。第一图片数量。理论最低3张但那是理想无噪声的情况。实际有角点检测噪声、棋盘格不平整误差建议拍15到20张。数量太少约束不足内参结果会随着你换一组照片大幅跳动。第二角度多样性。你光对着棋盘格正面拍棋盘格平面和成像平面近似平行单应矩阵约束退化内参求解病态。一定要倾斜让棋盘格平面和成像平面有30度以上的夹角。而且倾斜方向要多样化左倾、右倾、上仰、下俯让标定板在图像各个角落都出现。原因是畸变在图像边缘最明显如果棋盘格只在图像中心附近活动畸变系数根本约束不出来解出来的k1、k2可能是个不靠谱的数。第三视场覆盖。棋盘格在画面中不要太小建议占画面面积的三分之一以上。伽马射线也好、工业镜头也好画面的边缘区域承载着最多的畸变信息多让棋盘格出现在边缘区域。第四对焦和清晰度。这一点我在工业现场见过太多翻车案例标定板离相机太近导致虚焦棋盘格格子边缘模糊亚像素角点检测出来全是偏的。拍之前一定要确认棋盘格图案锐利清晰。4.2 棋盘格制作与角点检测棋盘格的规格选择注意两点。第一内角点数量。以8x6的黑白格为例OpenCV中cv2.findChessboardCorners传入的patternSize是(7, 6)也就是内部角点的行列数。这里的换算关系很多人搞混patternSize是角点数而不是格子数棋盘格有8列6行格子的内角点就是7列5行。这个参数设置错了角点检测直接失败。第二方格尺寸。标定板每个方格的实际物理尺寸要量准。理论上用张正友法标定单目相机内参时fx、fy的绝对数值不受方格尺寸真实值的影响——因为单目标定解出来的fx其实是一个“像素/单位”的比例关系如果你把方格尺寸标成两倍fx解出来也是两倍。但如果你后续要做双目测距、三维重建或者要把像素坐标和物理尺寸关联方格尺寸就至关重要。我的建议是用游标卡尺量取多组格子的平均值不要信卖家标称值。角点检测阶段的常见问题是反光。当标定板表面是覆膜或玻璃材质时灯光一打角点处会出现高光二值化后黑白格边界完全乱掉检测出的角点位置偏移。解决方案用哑光材料打印标定板或者在采图时调整灯光角度避免直接反射。4.3 常见标定问题排查速查表把我在实际项目里遇到过的典型问题整理成一张表遇到类似情况可以直接对照排查。现象可能原因解决办法重投影误差大于0.5px棋盘格不平整、角点检测偏差、采图模糊换硬质平面贴合标定板灯光调亮检查聚焦fx与fy比例怪异比如相差10%以上方格尺寸测量错误、传感器像素非正方形重新量方格尺寸检查patternSize设置主点cx、cy偏离图像中心太多图片太少或拍摄角度太单一增加图片数量覆盖更多角度和边缘区域k1、k2结果很大且不稳定标定板只出现在画面中心边缘畸变未约束让棋盘格出现在画面四角和边缘多倾斜换一组照片内参变化大采图质量差、数量不足、有部分模糊图像混入严格筛选照片剔除模糊和不清晰的图findChessboardCorners返回失败patternSize错误、格子尺寸过小、画面过暗核对角点数让格子占足够像素改善光照还有一个我特别想强调的坑不要盲目追求重投影误差小。误差在0.1px以下当然理想但如果你通过删图把误差“优化”到0.05px而删掉的都是边缘姿态的图那你可能消除了误差却牺牲了畸变参数的可靠性。重投影误差只是个综合指标更重要的是解出来的内参物理上合理主点接近图像中心、fx和fy比例接近传感器物理尺寸比例、畸变系数量级正常。参数合理比数字好看重要得多。4.4 我的实操体会最后分享几个我自己的习惯。标定相机之后我会固定摄像头和镜头的位置拧紧镜头上的固定螺丝然后重新标定一次。镜头上的调焦环和光圈环在运输或安装过程中可能被碰动你标定的是“当前状态”的相机安装完毕之后重新标定才能锁定这个状态。另外标定结果不要只看一次。我一般会连续标定两遍中间重新摆拍一组照片对比两遍的fx、fy、cx、cy。如果两组结果的fx差超过5个像素说明采图或者检测环节还有问题我会回去检查采图质量而不是信任其中任意一组结果。这个方法虽然笨但在实际项目里帮我拦下了不少隐患。还有一个小技巧保存标定结果时把采图的时间、相机型号、分辨率、棋盘格尺寸这些元信息一起存到配置里。过几个月再标定时如果内参变化超出了预期你可以快速判断是镜头松动、温度漂移还是流程错误。这些经验都是踩坑踩出来的希望能帮大家少走一些弯路。
网站建设高端定制企业官网