☰
张正友标定法全解析:相机内参标定原理与OpenCV实操避坑指南
2026/9/29 1:47:32 网站建设 项目流程

相机内参标定,是所有视觉项目的“第一道门槛”。做SLAM、做双目测距、做工业视觉定位,早晚都要过这一关。而提到相机内参标定,绕不开的名字就是张正友标定法。这个1998年发表的方法,硬是用一块平面棋盘格,解决了过去需要高精度三维标定块才能完成的内参标定问题,直到今天,OpenCV里的calibrateCamera、Matlab里的Camera Calibrator,底层走的都是这套理论。

很多朋友拿着棋盘格对着摄像头咔咔拍20张,跑完OpenCV代码发现重投影误差0.2px,就觉得万事大吉。但一旦换场景、换相机,标定结果忽好忽坏,就不知道怎么排查了。这篇文章我就从理论上把张正友标定法彻底拆开,讲清楚每一步在解什么方程、每个约束从哪来、参数怎么求出来,再把实操中容易踩的坑一并总结。适合刚入门视觉的初学者,也适合已经会用OpenCV但还没搞懂原理的工程师。

1. 相机内参标定到底在解什么题

1.1 从三维世界到像素坐标的三次变换

相机的工作本质上是把一个三维空间点投影到二维像素平面上。这个过程可以拆成三步。

第一步,世界坐标系到相机坐标系的刚体变换。相机在世界中有一个位置和姿态,对应一个旋转矩阵R和平移向量t。世界坐标系下的点$P_w=[X,Y,Z]^T$,经过这个变换得到相机坐标系下的坐标$P_c=R P_w + t$。这一步携带的是相机的外参,也就是相机在世界中的位姿。

第二步,相机坐标系到归一化平面坐标的透视投影。把$P_c=[X_c,Y_c,Z_c]^T$除以深度$Z_c$,得到归一化坐标$(x,y)=(X_c/Z_c, Y_c/Z_c)$。这一步把人眼看到的“近大远小”效果体现出来了,本质是小孔成像模型。

第三步,归一化坐标到像素坐标的仿射变换。归一化坐标是物理单位(毫米),像素坐标是离散的像素,两者之间相差一个缩放和偏移:$u=f_x x + c_x$,$v=f_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^2=x^2+y^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]ᵀ

令$H=K[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,不是随便什么向量都能当的。旋转矩阵本身有严格的性质——列向量之间正交,且模长为1:

r1ᵀ·r2 = 0 r1ᵀ·r1 = r2ᵀ·r2 = 1

这两个性质是所有旋转矩阵与生俱来的,不随拍摄角度改变。从$H=K[r1\ r2\ t]$可以反推:$r1=K^{-1}h1/λ$,$r2=K^{-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}$,我们干脆令$B=K^{-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·b=0。V是一个2n×6的矩阵。b有6个未知数(不考虑尺度),所以理论上至少需要3张照片才能构成一个齐次线性系统并求解。这就是为什么大家都说张正友标定至少要拍3张以上的图,实操中则建议拍到10到20张。

V·b=0是齐次线性方程组,b的尺度是任意的。用SVD对V进行分解,最小奇异值对应的右奇异向量就是b的解。到这里,内参矩阵元素组成的中间变量B已经拿到手了。

3.3 从B到内参的封闭解

现在要做的是从B里把fx、fy、cx、cy提取出来。这步是纯代数运算。已知$B=K^{-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就是cx,u0就是cy。看着公式多,本质上就是从六元方程里一个一个消元。如果你推导的时候假设skew参数gamma=0,公式会简化不少,市面上绝大部分标定库也都是这么做的。

有兴趣的话,你可以验证一个特例:当不存在噪声、且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为0,CALIB_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个像素,说明采图或者检测环节还有问题,我会回去检查采图质量,而不是信任其中任意一组结果。这个方法虽然笨,但在实际项目里帮我拦下了不少隐患。

还有一个小技巧:保存标定结果时,把采图的时间、相机型号、分辨率、棋盘格尺寸这些元信息一起存到配置里。过几个月再标定时,如果内参变化超出了预期,你可以快速判断是镜头松动、温度漂移还是流程错误。这些经验都是踩坑踩出来的,希望能帮大家少走一些弯路。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询