ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

张正友标定法详解:MATLAB相机标定实操与精度评估

张正友标定法详解:MATLAB相机标定实操与精度评估 1. 项目背景与核心原理1.1 什么时候需要相机标定做机器视觉、三维重建、视觉SLAM、AR增强现实或者工业检测类项目的人迟早都要面对同一个问题相机拍摄到的二维图像怎么换算成三维空间中的真实位置我最早接触这个需求是在一个尺寸测量项目上当时想用单目相机测工件长度拍出来的图边缘检测做得再好换算到毫米级尺寸依然偏差很大。后来才意识到问题根本不在边缘检测而在相机本身的成像模型没标定。相机镜头有焦距、有光心偏移镜片又有畸变这些参数不搞清楚图像坐标和物理坐标之间的换算就是一笔糊涂账。张正友标定法就是解决这个问题的经典方案配合MATLAB实验来做更是几乎所有视觉方向学生和工程师入门相机标定的第一课。这个方法的核心价值在于不需要昂贵的3D标定设备只用一台普通相机加一张打印出来的棋盘格拍十几张照片就能把相机的内参、外参、畸变系数全部算出来。1.2 为什么选张正友标定法而不是别的方法相机标定这个领域其实有好几条路线放在一起对比才能理解张正友标定法为什么能成为事实标准。传统标定方法需要高精度的三维标定块比如两个互相垂直的平面棋盘格这种标定物本身加工精度要求高价格也贵一般实验室未必有而且摆放三维标定块的过程挺麻烦。自标定方法则完全不依赖标定物直接根据多张图像之间的对应关系求解灵活是灵活但精度比较差适合那些对精度要求不高的场景比如三维重建初值估计。张正友标定法恰好踩在中间用二维平面棋盘格作为标定物比自标定精度高出不少又比三维标定块便宜、方便得多。你只需要把棋盘格打印出来贴在平整的板子上从不同角度拍照片就能完成标定。这套方法在1998年由张正友提出论文标题是Flexible Camera Calibration By Viewing a Plane From Unknown Orientations到今天二十多年过去依然是工业界和学术界最主流的标定方案。围棋效应背后还有一个关键原因这个方法在数学上有优雅的闭式解再配合极大似然估计做优化标定结果稳定、可复现不像很多深度学习方法那样需要大量数据还要看运气。1.3 原理层面的数学流程拆解张正友标定法的数学原理是理解整个实验的灵魂。刚开始看公式会觉得头大但拆成几步其实很清晰。第一步建立针孔成像模型。三维空间中的点通过外参旋转矩阵R和平移向量t变换到相机坐标系再通过内参矩阵K投影到图像平面。整个过程可以用一个公式概括s * [u, v, 1]^T K * [R | t] * [X, Y, Z, 1]^T其中s是尺度因子u、v是图像像素坐标X、Y、Z是空间点坐标。内参矩阵K长这样K [fx, 0, cx; 0, fy, cy; 0, 0, 1]fx和fy是焦距相关的尺度因子cx和cy是光心在像素坐标系中的位置。第二步利用棋盘格在一个平面上的特性简化问题。因为棋盘格所有角点都在Z0平面上在获取单应性矩阵H K * [r1 r2 t]时外参从6个自由度降为5个自由度但H有8个自由度这多出来的约束就是求解内参的关键。这也是张正友标定法的核心智慧所在一张平面棋盘格图像提供8个约束但外参占掉6个每幅图只能给内参带来2个约束。所以要拍多张不同角度的图才能积累足够多的约束把内参解出来。第三步通过至少3张棋盘格图像求解内参初值。因为每个视图提供2个约束内参有5个未知数fx、fy、cx、cy以及一个畸变相关的参数理论上3张图就能解出代数解。实际中我们会拍10到20张一方面为了精度另一方面也为后续优化提供冗余。第四步加入畸变模型。真实镜头不是完美针孔存在径向畸变和切向畸变。径向畸变用三阶多项式描述k1、k2、k3切向畸变用p1和p2描述。畸变导致的实际像素位置和理想针孔投影位置之间存在偏移标定就是要同时估计这些畸变系数。第五步极大似然估计做全局优化。有了初值以后用Levenberg-Marquardt算法迭代优化所有参数让所有图像上的重投影误差总和最小化。重投影误差就是把已知角点的三维坐标通过内外参重新投影回图像平面与检测到的角点像素坐标比较二者的欧氏距离就是误差。这个值越小说明标定结果越准。MATLAB实验里这些步骤并不需要全部手敲代码。Computer Vision Toolbox里的cameraCalibrator App和estimateCameraParameters函数底层已经实现了上述完整流程你只需要准备好图像设置好参数就能一键跑通。但懂原理很重要因为后面分析结果、排查问题全靠对原理的理解。2. 实验环境与数据准备2.1 MATLAB环境与工具箱选择做张正友标定法的MATLAB实验首先需要准备好带Computer Vision Toolbox的MATLAB环境。版本方面没有硬性要求只要是2014年以后的主流版本基本都自带单相机标定相关的函数和App。我用过R2018b、R2021b和R2023b标定这块的接口差异不大。需要提醒的是MATLAB正版授权可以找学校实验室申请校园版或者用官方试用版。安装时记得要把Computer Vision Toolbox勾上否则调用estimateCheckerboardPoints会直接报类似Undefined function或toolbox缺失的错误。另外如果电脑性能一般也不用担心标定棋盘格图像整个计算量很小一台普通笔记本完全跑得动。真正要花心思的是在前期的图像采集阶段数据质量决定了标定结果的极限。2.2 标定板的规格与制作技巧标定板是整个实验中最容易被忽视、却又最关键的道具。我见过不少人在电脑屏幕上显示一张棋盘格照片对着屏幕拍结果标定结果一塌糊涂。这里面的门道不少。首先是棋盘格规格。标准的做法是需要知道黑白格的物理尺寸也就是每个正方形方格的边长。MATLAB的标定工具支持方形棋盘格Checkerboard和圆形点阵Circle Grid最常用的是棋盘格。我自己常用的规格是9x13格的棋盘格也就是在x方向有13个格y方向有9个格对应角点数是12x8。这里要注意MATLAB的角点检测函数会自动识别棋盘格但你需要告诉它每个方格的边长是多少毫米。其次要考虑方格尺寸和打印尺寸。方格边长一方面要足够精确另一方面要保证在拍摄距离下每个方格在图像里至少占20到30个像素。如果方格太小角点检测会不稳定反而降低标定精度。所以打印之前先用尺子量一下打印出来的实际方格尺寸不要直接相信打印机设置里的数字因为打印过程可能有缩放。打印之后要把棋盘格贴在完全平整的硬板上。我试过用硬纸板、亚克力板、铝板亚克力板效果最好因为它平整度好不容易弯曲。如果用纸板要注意别沾水、别弯折否则棋盘格平面本身不平会直接毁掉标定结果。还有一个小技巧棋盘格最外圈最好留一圈白色边距这样角点检测算法更容易识别边界减少检测失败的概率同时也可以避免边缘被截断导致某一行或某一列角点缺失。2.3 拍摄图像时的关键注意事项采集棋盘格图像这一步决定了整个标定实验的上限。这是最费时间、也最能体现经验差距的环节。第一拍摄张数要够。经验上至少拍10到15张推荐20张左右。不是越多越好但太少肯定不行。每张图要保证棋盘格以不同姿态出现在画面里覆盖相机的整个视野范围。第二姿态要有足够的变化。你不能十几张图都是棋盘格正对着相机、只是位置左右移一移。那样的话虽然平面单应性约束丰富但对焦距和主点的约束不够内参会不稳。要拍出棋盘格倾斜、旋转、在不同深度位置的照片。我这里说个具体的分布建议5张左右棋盘格中间位置正对相机但距离不同5张左右绕x轴倾斜5张左右绕y轴倾斜3到4张放在画面边缘四角区域。这样姿态覆盖全标定出来的内参才可靠。第三光照要均匀避免反光。棋盘格黑白格子对比度越高角点检测越稳。反光会让白色格子过曝、黑色格子亮度上升导致角点位置偏移。拍摄时尽量用漫射光源避免强点光源直射。如果用的是手机或工业相机固定好焦距和光圈不要自动对焦、自动变焦全程用同一个焦距。第四注意景深问题。棋盘格在镜头前面要有一定的深度范围但整块棋盘格要在景深范围内保持清晰。大光圈会导致棋盘格边缘模糊角点检测精度下降。我一般用f/8左右的光圈保证清晰度。第五画面里尽量不要有棋盘格以外的物体尤其是不能被手、其他物体遮挡住棋盘格的一部分。遮挡会导致角点数量不够检测失败或者精度降低。这些细节看起来琐碎但每一条都是我实际踩过坑总结出来的。有一次我为了图快用手机随手拍了8张图结果标定出来的重投影误差有2.8像素怎么调都不好后来规规矩矩重新拍了18张误差直接降到0.15像素。3. 基于MATLAB的标定实操全流程3.1 第一条路线Camera Calibrator图形界面MATLAB的Camera Calibrator App是最适合新手入门的方式全程图形化操作不用写一行代码还能直观看到每张图像的重投影误差。入口很简单在MATLAB命令行输入cameraCalibrator回车后会弹出App窗口。操作流程分五步。第一步点击Add Images按钮选择你拍好的所有棋盘格照片。MATLAB会自动检测每张照片里的棋盘格角点检测成功的图像会显示绿色角点标记检测失败的会报错或者显示跳过。这时你可以先把所有图导进来。第二步填写棋盘格参数。在标定设置里选择Camera Model为StandardDistortion选3 coefficients或2 coefficients取决于你的镜头畸变程度。然后填写Checkerboard的方格边长单位是毫米。这里必须填对否则所有尺度相关的参数都会错。第三步剔除不合格图像。这是App模式最实用的功能之一。导入图像后会发生检测失败的情况或者虽然没有检测失败但角点检测结果明显不对。我建议按两个标准筛选一是角点有没有全部覆盖二是重投影误差特别大的图先记下来后面优化阶段再删。第四步点击Calibrate按钮执行标定。App界面上会显示所有图像的重投影误差柱状图横轴是图像编号纵轴是平均重投影误差单位是像素。这个图非常直观误差明显偏高比如超过0.5像素的图像可以在图里选中它然后右键删除再重新标定。第五步导出结果。点击Export Camera ParametersMATLAB会在命令行窗口生成一个cameraParameters类型的变量比如cameraParams里面包含了内参矩阵、畸变系数、每张图的外参等信息。之后在脚本里就能直接调用它来做去畸变、测距、三维重建等后续工作。3.2 第二条路线命令行脚本批量自动化App适合单次交互式标定但如果要做批量测试、自动标定或者想对MATLAB的执行细节有更精细控制脚本方式更好。核心函数有三个% 读取所有图像文件 imageFiles dir(calibration_images/*.jpg); imagePaths fullfile({imageFiles.folder}, {imageFiles.name}); % 通过图像文件名列表或者图像数据本身检测角点 [imagePoints, boardSize] estimateCheckerboardPoints(imagePaths); % 使用相机参数估计完成标定 worldPoints generateCheckerboardPoints(boardSize, squareSize); cameraParams estimateCameraParameters(imagePoints, worldPoints);这里有一点坑就是estimateCheckerboardPoints如果直接传图像路径它会自己读取图像并检测角点如果传的是变量那传的就得是图像数据。我在实际使用中习惯直接传路径省内存。squareSize就是棋盘格方格边长单位毫米。boardSize是角点数比如方格是9x13那么角点就是8x12。注意顺序x方向的角点数和y方向的角点数不要搞反。estimateCameraParameters的第三个参数可以用名称-值对来设置比如cameraParams estimateCameraParameters(imagePoints, worldPoints, ... EstimateSkew, false, ... EstimateTangentialDistortion, true, ... NumRadialDistortionCoefficients, 2);EstimateSkew默认false也就是假设像素的x轴和y轴是垂直的绝大多数工业相机和普通相机都满足这个假设。EstimatesTangentialDistortion控制是否估计切向畸变默认true。NumRadialDistortionCoefficients径向畸变系数个数设2还是3要看镜头类型。标定完成之后可以进行误差可视化figure; showReprojectionErrors(cameraParams);出来的柱状图每条柱代表一幅图像的重投影误差看起来就很直观。另外还可以看每幅图的外参位置figure; showExtrinsics(cameraParams);这个会显示相机坐标系和棋盘格坐标系之间的相对位姿关系能帮我们确认采集图像的时候姿态分布是否合理。3.3 参数计算过程与核心函数解读单看函数调用很简单但作为博主还是想带大家过一遍里面的关键参数免得只知其然不知其所以然。imagePoints是一个Nx2xM的数组N表示每幅检测到的角点数2是行列坐标M是有效图像数。注意每幅图的角点数必须一致如果你某张图棋盘格被遮挡了一部分导致检测到的角点数比别的图少那这张图就不能要否则estimateCameraParameters会直接报错。worldPoints是根据generateCheckerboardPoints生成的三维点坐标固定在一个虚拟平面上。这个平面就是标定板所在的平面三维坐标中Z分量恒为0X和Y分量则是按方格边长递增排列。generateCheckerboardPoints返回值顺序要与imagePoints一致MATLAB内部已经帮你处理了对应关系所以不用担心点对不上的问题。cameraParams对象里面几个核心字段Intrinsics一个cameraIntrinsics对象包含FocalLength焦距、PrincipalPoint主点、ImageSize图像尺寸等。RadialDistortion径向畸变参数1x2或1x3的向量对应k1、k2、k3。TangentialDistortion切向畸变参数对应p1、p2。RotationMatrices每个视图的旋转矩阵是一个3x3xM的数组。TranslationVectors每个视图的平移向量是一个Mx3的数组。MeanReprojectionError全部图像的平均重投影误差单位像素。这些参数后续的用法很广。比如要做去畸变直接调用undistortedImage undistortImage(originalImage, cameraParams);要做像素坐标和相机坐标之间的换算用camIntrinsics cameraParams.Intrinsics; [x, y] imageToWorld(camIntrinsics, [u, v]);这里面imageToWorld需要指定一个参考平面Z0输出的x和y是相机坐标系下的平面坐标。在做单目测距、视觉测量时这非常有用。还有一个点值得提如果标定过程中出现warning提示某个参数估计不确定性较大查看cameraParams的EstimationErrors这组数据里面给了每个参数的标准差估计。标准差越大说明对应参数越不可靠。4. 标定结果分析与精度评估4.1 重投影误差衡量标定精度的核心指标标定完成之后最需要看的指标就是重投影误差Reprojection Error。这个概念核心是我们已知棋盘格角点的三维坐标又通过标定得到了内外参数就可以把这个三维点重新投影到图像平面上得到一个理论上的像素坐标把这个理论坐标与真实检测到的像素坐标相减就是重投影误差。理论上如果标定完美重投影误差应该等于0。但实际因为镜头噪声、角点检测精度限制、标定板不平整等因素误差不可能为0。那么多少算合格呢我自己的经验标准是平均重投影误差小于0.1像素非常优秀说明图像质量好、标定板平整、参数估计稳定。平均重投影误差在0.1到0.3像素之间良好完全可以用于一般的测量和视觉任务。平均重投影误差在0.3到0.5像素之间及格如果只是做视觉应用勉强能用但做高精度测量就要小心了。平均重投影误差超过0.5像素不合格需要检查数据质量和流程。很多同学第一次标定出来误差达到1像素以上就开始怀疑MATLAB是不是有问题。其实大多数原因是图像采集环节出了问题我后面会展开说怎么排查。用showReprojectionErrors画出来的柱状图也要学会看趋势。如果某张图误差明显高于别的图说明这张图可能拍摄时有些晃动或者棋盘格部分反光导致角点检测偏移建议删除重跑。如果所有图像误差普遍偏高但分布均匀那可能是棋盘格本身不平整或者标定板方格的尺寸标错了。4.2 内参矩阵和畸变系数的解读标定结束翻看cameraParams的结果内参矩阵和畸变系数是重点。先看内参矩阵核心是fx和fy以及主点cx和cy。对于理想的相机fx和fy应该接近差值在1%以内如果差太多可能说明图像不是正方形像素或者传感器存在缩放问题。主点cx和cy理论上应该接近图像中心比如1920x1080的图像中心是960和540实际会有几十个像素的偏差这是正常的镜片安装不可能完美居中。然后是畸变系数。对普通工业镜头k1通常远大于k2k3常常可以忽略不计。如果你发现估计出来的k2反而很大或者k3非常大这往往是过拟合的信号说明数据不足以支撑三阶畸变模型应该把NumRadialDistortionCoefficients从3改成2或者增加图像数量。切向畸变p1和p2的值一般都很小。如果发现p1、p2特别大绝对值超过0.1就要检查镜头是不是安装歪了或者是不是用了劣质镜头。用MATLAB里的相机标定App标定结束后通常可以在View菜单里打开Undistort Image预览直接看到去畸变前后的对比效果。去畸变后画面的直线应该变直尤其是画面边缘的直线这个视觉检查比任何指标都直观。4.3 提高标定精度的迭代策略一次标定很少能直接达到理想精度通常需要迭代改进。我的标准流程是这样。第一轮先按照前面说的采集要求拍20张图标定之后看平均重投影误差。如果超过0.3像素打开每个图像的误差柱状图删掉误差明显大的那几张。我先删误差最高的图每次删1到2张重新标定观察变化。要注意的是一次不能删太多因为图像数量太少误差反而会变大。第二轮检查姿态覆盖。如果showExtrinsics显示所有棋盘格都集中在某个平面附近姿态分布不够丰富那就要补拍一些不同角度的图。姿态丰富度不够即便重投影误差看起来不错内参的可靠性也存疑。第三轮检查棋盘格本身。如果不断更换图像组合误差依然降不下来请用手按压标定板看它是不是已经弯曲了。弯曲的标定板会引入系统性误差再怎么换图都没用。还有一个小技巧如果条件允许把同一个标定流程用两套不同的棋盘格比如格数相同、边长不同的各标一次比较结果中的fx和fy是否一致。如果差异很大说明标定流程某处有系统误差比如方格尺寸填错了。做完这些步骤好一点的标定结果可以达到0.05到0.1像素的平均重投影误差这个精度已经足够应对绝大多数实际场景。5. 常见问题与排查技巧实录5.1 角点检测失败或检测数量不一致我在实验过程中遇到最多的问题是estimateCheckerboardPoints检测不到棋盘格或者检测到的角点数比别人少。检测不到先考虑几个因素图像分辨率是否太低如果棋盘格在图像里占的面积太小角点检测不出来的概率会很高尽量让棋盘格占画面三分之一以上。光照是否均匀白色格子过曝、黑色格子反射光斑都会导致检测失败。图像有没有严重模糊拍摄过程中手抖、对焦不准都会让角点位置模糊算法自然找不到。角点数不一致多半是棋盘格边缘在画面外被截断。一张图的棋盘格如果最左侧的一列角点出了画面那检测到的角点数就会少一列这张图就没法用。解决办法很简单拍摄的时候让棋盘格四周留出足够的空白边距确保整块棋盘格完整出现在画面里。5.2 重投影误差居高不下的系统性问题重投影误差普遍偏高而且删图像也不管用就要从系统层面找原因了。第一个嫌疑是boardSize或者squareSize填错了。squareSize偏大或偏小虽然不影响重投影误差的计算但会导致fx、fy等参数整体缩放。而boardSize填错会导致对应点坐标错乱误差会非常大。我建议拍摄之前就把标定板格子和方格边长记录好不要等到标定完了再回忆。第二个嫌疑是棋盘格不平整。贴在纸板上的棋盘格容易弯曲尤其是对角区域。一张弯曲的标定板相当于把世界坐标系的平面扭曲了但模型依然假设它是平面误差自然降不下来。我后来换成亚克力板误差从0.5像素直接降到0.12像素。第三个嫌疑是镜头自动对焦。有些相机在拍摄不同距离的图像时会自动调节焦距这导致不同图像对应不同的内参fx、fy标定算法却假设所有图像共享一组内参结果就会一团糟。所以说拍摄时一定要锁定焦距。第四个嫌疑是标定板太脏或印刷模糊。打印机的墨量不同黑色格子的边缘可能模糊不清导致角点检测的亚像素精度下降。5.3 MATLAB报错和环境问题速查有些同学问为什么我调用estimateCheckerboardPoints时报错说没有这个函数十有八九是Computer Vision Toolbox没装。检查方法是在命令行输入ver看列表里有没有Computer Vision Toolbox这一项。没有就要重新安装工具箱或者换一个完整版安装。还有一个常见报错Error using estimateCheckerboardPoints图像中包含的棋盘格角点无法正确检测。这个报错信息比较含糊实际就是算法没有找到合格的棋盘格。按照前面说的光照、清晰度、占比这几个方向去调整拍摄方式就行。另外不同MATLAB版本中cameraCalibrator App的界面略有差异但功能基本一致。如果界面打不开确认一下当前路径下是否有同名文件夹和App产生冲突。5.4 值得反复强调的独家经验最后分享几条我做了无数遍实验之后才沉淀下来的经验。一是保留原始图像不要对图像做任何预处理。我见过有人为了“让角点更好检测”先对图像做了锐化、对比度增强再做标定。这非常不建议因为任何图像处理都会改变角点的亚像素位置引入额外的误差。标定图像要直接用raw格式的原始图像最多做一下gamma校正但要保证不改变几何位置。二是标定板的有效区域不要太小。拍照时让棋盘格占据画面的30%到60%这样角点分布均匀、数量也够。小于20%时角点检测不稳定大于80%时容易出现边缘畸变的影响过大导致误差集中在画面边缘。三是多次标定看稳定性。同一批图像换个初始化条件或者使用不同子集重新标定结果中的fx、fy偏差应在0.5%以内。如果多次标定结果漂移很大说明数据集合里某些图像的一致性差回去查图像质量。四是标定结果要配合实际验证。标定完之后拍一张棋盘格在画面中间位置的照片用undistortImage去畸变观察画面边缘的直线是否变直。再放一个已知尺寸的物体在平面场景里用标定参数去做一个简单的长度测量看看标定出来的参数在真实任务中是否靠谱。这个验证环节比任何虚拟指标都更有说服力。五是对最终参数要保存好。建议把cameraParams对象保存为mat文件存档save(cameraParams.mat, cameraParams);后续所有图像处理项目直接用load加载这个文件不需要每次重新标定。当然如果换镜头、换了相机分辨率、或者调节了焦距就要重新标定参数不能跨场景通用。张正友标定法的MATLAB实验说难不难说简单也不简单。核心并不是MATLAB代码本身而是理解原理之后对数据采集质量的控制。我个人的体会是这个实验值得花几个小时反复做几遍每一次都能发现新的细节。第一次跑通流程第二次关注误差来源第三次尝试不同参数设定和不同质量的数据做完这三轮你对相机模型的直觉就有了质的提升。
返回列表