ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

张正友相机标定法MATLAB全流程:棋盘格采图、参数估计与避坑指南

张正友相机标定法MATLAB全流程:棋盘格采图、参数估计与避坑指南 去年做双目视觉测量项目标定结果卡了我整整两天。相机参数估出来的焦距和镜头标称差了快10%重投影误差始终在0.8像素以上更换图像数量、换光照、重拍都没有明显改善。后来换了个角度检查才发现问题出在棋盘格采集姿态覆盖不够跟张正友标定法和MATLAB实验流程本身没关系。这篇内容我打算把从原理到MATLAB实操的完整过程摊开讲一遍包括我怎么采集图像、怎么写脚本、怎么判断结果是不是靠谱以及那些报错和异常结果背后到底在说什么。适合刚接触相机标定的学生也适合做视觉测量、三维重建、机器人定位的工程师作为排查参考。1. 张正友标定法的核心逻辑用棋盘格把相机内外参“解”出来1.1 为什么偏偏是棋盘格相机标定的本质是求世界坐标系到像素坐标系的投影关系。这个关系由内参、外参和畸变参数组成。传统方法要用一个高精度的三维标定块采集多面图像制作麻烦维护也麻烦。自标定方法不需要靶标但鲁棒性差精度不够看。张正友法的思路很简单我只要一个平面棋盘格在不同姿态下拍若干张图就能同时求出内参和外参。棋盘格能成为标准靶标主要是两个原因。第一黑白格交界处是天然的角点特征抗干扰能力强可以做亚像素提取第二棋盘格平面本身提供了足够的几何约束每张图可以建立一个单应矩阵多个姿态下的单应矩阵组合起来正好能把内参的未知数解出来。还有一个隐性优势棋盘格不需要严格知道三维坐标只需要知道格子的物理边长。这一点对工程太友好了。普通低成本场景用A4纸打印一张棋盘格贴在平板上就能开工。当然精度要求高的项目还是建议买陶瓷或玻璃基底标定板后面我会讲到为什么。1.2 单应矩阵的内参约束看懂两条等式就够了张正友法推导过程中最核心的一步是建立棋盘格平面到图像平面的单应矩阵。设棋盘格平面为Z0靶标上的一点用齐次坐标[X, Y, 1]表示图像上对应点是[u, v, 1]投影关系可以写成s * [u, v, 1]^T K * [r1, r2, r3, t] * [X, Y, 0, 1]^T因为Z0r3那一列其实不参与运算可以化简成s * [u, v, 1]^T K * [r1, r2, t] * [X, Y, 1]^T这里的3×3矩阵H K * [r1, r2, t]就是单应矩阵。每张棋盘格图像只要检测到至少4个角点就能通过直接线性变换求出一个H。这个H里面混合了内参K、旋转向量r1/r2和平移向量t。接下来是利用旋转矩阵的性质。r1和r2来自同一个旋转矩阵所以它们互相正交并且长度相等。把H写成[h1, h2, h3]就能得到两个经典约束h1^T * (K^{-T} * K^{-1}) * h2 0h1^T * (K^{-T} * K^{-1}) * h1 h2^T * (K^{-T} * K^{-1}) * h2这两条等式是张正友法求解内参的钥匙。内参K有5个未知数fx, fy, u0, v0, skew每张图像能提供两个约束所以理论上至少需要3张不同姿态的棋盘格图像才能线性解出内参。拿到K之后外参也能逐一恢复r1 λ * K^{-1} * h1r2 λ * K^{-1} * h2r3 r1 × r2t λ * K^{-1} * h3这里λ是归一化系数。最后所有参数会放到一个非线性优化目标里最小化实际检测角点和模型投影角点之间的重投影误差同时把畸变系数也一起优化进去。1.3 为什么这个实验最适合在MATLAB里跑张正友法并不是只能写在MATLAB里OpenCV、Python都可以做。但作为实验和教学MATLAB的优势非常明显Computer Vision Toolbox里的Camera Calibrator App和底层函数estimateCameraParameters把整个流程封装得很干净同时又提供了角点检测可视化、重投影误差棒图、三维外参图这些交互式工具。你不必自己写一堆绘图代码就能很快判断标定结果是否合理。更重要的是MATLAB里的函数命名和参数设计比较直白适合用来对照算法原理验证每一步。你可以在命令行窗口单独跑detectCheckerboardPoints看角点提取得对不对也可以把estimateCameraParameters的参数换来换去观察重投影误差的变化。这种可交互性对理解张正友算法帮助很大。下面我从图像采集开始按我自己做实验的顺序完整走一遍。2. 图像采集的“门道”什么样的棋盘格图才算合格2.1 数量、姿态和占幅张正友论文里说至少3幅图但工程上3幅只是数学上的下限。我自己的习惯是采集12到20幅但重点从来不是数量而是姿态覆盖。棋盘格最好出现在图像中心、左上、右上、左下、右下同时要有左右倾斜、上下俯仰、平面内旋转等变化。简单说让棋盘格以各种角度和位置出现在画面里并且尽量靠近画面边缘。因为边缘是畸变最明显的地方如果所有图像里棋盘格都只在画面中心畸变参数基本是拍脑袋估出来的。另一个容易被忽视的点是棋盘格在每张图像里的占幅。太小了角点提取的亚像素精度会下降太大了又会把画面中心区域反复覆盖边缘信息缺失。我一般让棋盘格占整张图像面积的1/4到1/3。对普通镜头来说这个范围能兼顾角点清晰度和畸变信息覆盖。2.2 采集环境与相机状态采集时相机必须固定焦距也必须固定。如果你中途调焦、缩放或者后期把图像裁剪了、用软件缩放了那内参就变了标定结果会非常奇怪。光照要均匀尽量避开锐利反光。尤其是打印棋盘格一旦黑白格边界过曝角点检测位置会发生系统性偏移。棋盘格本身要平。A4纸打印出来直接拿在手里很容易有轻微弯曲和褶皱。这个弯曲在标定算法眼里不是小问题而是把“平面靶标”变成“曲面靶标”重投影误差能高出好几倍。最省钱的办法是把打印纸用双面胶贴在亚克力板或玻璃板上确保表面平整。更高精度的场景直接买玻璃基底的陶瓷棋盘格漫反射好也不容易翘。2.3 我的采图经历以为拍够了结果一半不能用我第一次做张正友标定实验时随手从打印机抽了一张A4棋盘格贴在纸箱上手持相机拍了15张。当时觉得拍得挺多结果MATLAB里detectCheckerboardPoints只成功检测出8张而且这8张里还有几张角点错位。排查下来问题出在光照桌面上有台灯棋盘格局部反光黑白格边界高光过曝角点的对比度完全被破坏了。后来把纸板换成了亚克力夹板用无影光源从侧面补光检测率马上上来了。所以我现在对每个做标定实验的人都说一句话图像采集质量决定了重投影误差的上限后面所有算法优化都是在不超过这个上限的前提下操作。别急着跑代码先花半小时把棋盘格拍好。3. MATLAB标定实验完整流程从读图到出参数3.1 数据组织与读图在MATLAB里做标定我不建议把图像一股脑塞到工作区建议先建一个专门的文件夹比如calib_imgs把拍好的图像按顺序命名。然后通过imageDatastore批量读入imageDir fullfile(pwd, calib_imgs); imds imageDatastore(imageDir);这里有个小坑文件路径尽量不要包含中文字符MATLAB在某些版本下会因为编码问题报奇怪的错误。如果你已经放到中文路径下最省事的方式是先把整个文件夹复制到不含中文的路径。3.2 角点检测detectCheckerboardPoints的输入与输出核心函数是detectCheckerboardPoints。直接传入imageDatastore的Files属性它会把所有图像里的棋盘格角点坐标一次性检测出来[imagePoints, boardSize] detectCheckerboardPoints(imds.Files);这里imagePoints的尺寸是“图像数×角点数×2”boardSize返回的是棋盘格内角点的行列数比如[10, 7]表示每行10个、每列7个内角点。注意detectCheckerboardPoints检测的是黑白格内部角点不是棋子格子的总数。如果你的棋盘格是11×8格那内角点通常就是10×7。如果某张图检测失败MATLAB会直接报错或者在该图像的对应位置填充NaN。我习惯先单独跑几张图确认角点基本都检出之后再一次性处理全部图像。角点检测的参数里比较有用的是Upsample选项可以控制亚像素插值密度但对多数镜头默认值就够了。3.3 生成世界点并估计相机参数检测完角点后需要生成棋盘格在世界坐标系下的坐标。这里只需要保证棋盘格平面位于Z0平面并设定正方形边长即可。squareSize单位取决于你要表达的真实物理尺度毫米也好厘米也好只要后续使用一致就行squareSize 30; % 单位mm按实际棋盘格正方形边长设定 worldPoints generateCheckerboardPoints(boardSize, squareSize);然后调用estimateCameraParameters进行标定cameraParams estimateCameraParameters(imagePoints, worldPoints, ... NumRadialDistortionCoefficients, 2, ... EstimateTangentialDistortion, true, ... EstimateSkew, false);这里解释一下参数选择。NumRadialDistortionCoefficients控制径向畸变系数的个数普通镜头选2也就是k1和k2。高畸变广角镜头可以选3但选3时k2、k3容易互相耦合反而可能导致不稳定。EstimateTangentialDistortion决定要不要估计切向畸变p1、p2如果镜头和传感器装配不到位切向畸变会比较明显所以我一般习惯打开。EstimateSkew是像素坐标轴是否倾斜的估计工业相机传感器基本都做得比较正设false可以减少一个有待估计的参数提高其他参数稳定性。3.4 查看结果和导出cameraParams标定完成后不要急着保存先看可视化结果showExtrinsics(cameraParams); figure; showReprojectionErrors(cameraParams);showExtrinsics会把相机和棋盘格之间的位姿关系画成三维图showReprojectionErrors会画出每张图每个角点的重投影误差分布。然后再算一下平均误差meanError mean(cameraParams.ReprojectionErrors(:)); fprintf(Mean reprojection error: %.4f pixels\n, meanError);如果结果满意保存参数供后续去畸变或三维重建使用save(cameraParams.mat, cameraParams);之后对任意图片做去畸变只要一句话[J, newOrigin] undistortImage(I, cameraParams, OutputView, full);我在实际项目中会把这一步封装成一个函数输入任意图像路径输出去畸变后的图像和新的内参矩阵方便后面统一调用。4. 结果解读内参、外参和畸变不能只盯着误差4.1 重投影误差的合理阈值与陷阱很多人标定完只会看一个数平均重投影误差。如果小于0.5像素就觉得OK大于1像素就觉得失败。这个判断基本对但有几个陷阱。第一误差小不一定意味着参数准。如果你只用了3张图像优化自由度比约束少完全可能得到一个训练集上重投影误差极低、但实际使用很差的参数。我一般会保证至少10张有效图像并且要看到误差分布不是集中于某一两张图。第二误差棒图里经常会有一两个角点误差特别大这通常意味着那一张图的棋盘格局部有反光、污渍或者不平整可以考虑剔掉这张图重新标定。第三重投影误差是在图像平面计算的它不直接等于三维测量误差。如果后面要做三维重建光看像素误差远远不够。4.2 内参中的焦距和主点得到cameraParams后可以通过cameraParams.Intrinsics查看内参矩阵。重点关注几个值fx和fy、主点u0和v0、畸变系数。fx和fy在理想情况下应该非常接近因为普通相机像素几乎是正方形。如果两者差距超过5%你需要怀疑图像是否被非等比缩放处理过或者探测器像素本身不是正方形。主点应该接近图像中心比如1080p图像的主点大概在(960, 540)附近偏差几十个像素通常正常偏差几百像素就要检查初始内参和图像是否被裁剪过。我有个习惯会把标定出来的fx和镜头标称参数对比一下。fx的物理含义约等于焦距除以像元尺寸。如果镜头标称8mm像元尺寸2.2μm那么fx应该在3636像素左右。如果标定结果差太多要么是焦距没固定要么是图像在事前被缩放处理过。这个检查能帮我提前发现很多低级错误。4.3 畸变系数与去畸变验证畸变系数里径向畸变由k1、k2可能还有k3描述切向畸变由p1、p2描述。常见镜头是桶形畸变k1通常为负。如果标定出来系数符号、量级怪怪的不要急着接受先回去看图像。更直观的验证方法是直接做一次去畸变。我通常会用一张直线边缘明显的室内照片比如门框、墙角的图像调用undistortImage之后对比直线是否变直。如果画面边缘曲线已经消除说明畸变模型基本到位如果边缘还有明显的波浪形说明畸变阶数可能不够或者棋盘格没有覆盖到画面边缘。4.4 外参可视化看相机和棋盘格的相对位姿外参代表每张图像拍摄时相机相对于棋盘格世界坐标系的旋转和平移。MATLAB里的showExtrinsics会把相机画成三维锥体把棋盘格画成平面网格非常直观。我一般会观察相机位姿是否都落在合理的视野范围内。如果某个位姿突然“飞”到很远的地方那这一张图像大概率角点检测错位或者棋盘格方向判断反了。外参在视觉测量里的意义容易被忽略。单目标定得到的外参并不是固定不变的它描述的是每一次拍摄时靶标和相机的关系。只有双目立体标定或者手眼标定才关心多个相机之间、相机与机械臂之间的固定外参。张正友法在这里只是基础。5. 避坑实录MATLAB标定中我排查过的几个典型问题5.1 角点检测失败或误检如果detectCheckerboardPoints一直检测不到角点先别怀疑函数有问题。检查顺序是图像是否过曝或过暗棋盘格是否太小是否运动模糊棋盘格图案本身是否被裁剪到边缘我遇到最多的是局部反光导致黑白格分界线消失。可以先用imadjust增强对比度看一眼img imread(bad_image.jpg); img2 imadjust(img, stretchlim(img), [0 1]); imshowpair(img, img2, montage);但要注意标定最终用的还是原始图像增强图只是用来确认角点是否存在。如果增强后能检测出来说明原图对比度不够可以调整光源后重拍而不是用增强图硬标。5.2 报错“棋盘格方向不一致”怎么办这是用estimateCameraParameters时很常见的报错。原因是不同图像里检测出的角点顺序不一致可能是某张图旋转了90度、镜像翻转或者棋盘格朝向和第一张图差别过大。张正友法在求解时默认所有图像的世界坐标系定义一致也就是所有棋盘格都以相同规则定义原点顺序错乱就会导致约束方程乱套。最直接的解法是重拍保证每张图中棋盘格的第一行内角点都在图像左侧并且棋盘格没有翻转。也可以用flipud、fliplr调整图像方向后再检测但这样做容易引入坐标混乱。我在实际项目里一般不和它硬刚重新采一波图比改代码快得多。5.3 重投影误差高但角点检测看起来都对如果你检查过角点检测准确误差依然高优先怀疑三个地方。第一棋盘格表面不平。这一点用肉眼经常看不出来但标定算法对平面性很敏感。你可以在桌上垫一块平板用重物压住棋盘格边缘再拍。第二相机焦距在采集过程中可能被动过。某些变焦镜头即使轻微碰一下内参也会变。第三模型参数选择不合适。可以尝试把NumRadialDistortionCoefficients从2改成3或者打开EstimateTangentialDistortion。如果误差明显下降说明镜头畸变模型复杂原来的低阶模型不够用。还有一个容易被忽略的是imagePoints和worldPoints的对应关系。如果squareSize给错重投影误差本身不会变因为像素投影和世界坐标缩放会成比例抵消。但外参的平移量单位会错后续三维重建时尺度就全错了。所以标定完一定要用已知尺寸的物体验证尺度。5.4 去畸变后边缘仍有明显桶形或枕形畸变这种情况往往是畸变系数没有覆盖到画面边缘。畸变参数在画面中心附近很小到了边缘会急剧放大因此需要保证有足够多的棋盘格图像出现在图像边缘区域。你可以专门拍摄一些棋盘格贴近图像四角、边缘的姿态而不是让棋盘格永远在画面中央。如果已经这样做了还是有残留另一个办法是增加径向畸变系数到3阶。但是要注意高阶畸变系数不是越高越好高阶系数会放大噪声导致中心区域畸变被过度校正。实际项目中我一般先用2阶看边缘效果不行再上3阶同时观察内参是否发生明显跳变。6. 再说一句张正友标定法在实际项目中的使用习惯我在项目里不会把标定当成一次性工作。相机镜头只要受过磕碰、温度变化明显、或者调过焦点内参都可能有变化。所以每次实验前我会用固定焦距随手拍十来张棋盘格快速重标定一次再继续后面的测量任务。这套流程在MATLAB里跑一遍很快真正花时间的其实只是采图。另外张正友法不只适用于单目。双目立体标定通常是先分别标定左右相机再用stereoCameraCalibrator做整体优化得到两个相机之间的旋转矩阵和平移向量。MATLAB在这条链路里的交互体验很好但核心还是每一步都要确认误差和可视化结果不要盲目相信一个输出值。最后分享一个经验标定误差再好看也不如一次实测来得可靠。我之前遇到过一次重投影误差0.12像素看起来很漂亮但三维重建出来的物体尺寸偏差很大最后发现是棋盘格实际边长和代码里写的squareSize不一致。这类低级错误很难靠算法发现所以我后来养成了每次标定完成以后用卷尺量一个已知距离做尺度验证的习惯。张正友标定法和MATLAB实验流程本身并不复杂复杂的是你用什么样的细心程度去对待采集、参数设置和结果验证。
返回列表