ARTICLE DETAIL

资讯详情

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

MATLAB相机标定实战:从棋盘格采集到内参导出全流程解析

MATLAB相机标定实战:从棋盘格采集到内参导出全流程解析 最近项目上要做视觉定位手里正好有一台D435i和一块自制的棋盘格标定板前后折腾了快一周总算把MATLAB相机标定这条路走通了。这篇东西不是什么教程翻译纯粹是实战记录把原理、操作、踩坑、参数调整这几块揉在一起写希望能帮到正在走同一条路的人。先说结论MATLAB自带的Camera Calibrator App配合OpenCV采集图像是个人做相机内参标定最稳、最快、最不容易翻车的一套组合。整个过程大概分三步采集图像、跑标定、验证结果。听起来简单但里面每一个环节都有不少细节下面一个一个拆开说。1. 相机标定到底在标什么从针孔模型到畸变参数很多刚接触标定的人第一个问题是标定到底在干嘛我觉得最直观的理解方式是相机就是把三维世界拍成二维图片这个过程可以用一个数学模型来描述标定就是把这个模型里的未知参数求出来。MATLAB里做的相机标定核心就是求解两个东西相机内参Intrinsics和畸变系数Distortion Coefficients。1.1 针孔成像模型与内参矩阵理想情况下相机成像遵循针孔模型。空间里一个三维点 (P(X, Y, Z))经过相机光心投影到成像平面上变成二维像素点 (p(u, v))。这个过程用矩阵写出来就是[ s \begin{bmatrix} u \ v \ 1 \end{bmatrix} \begin{bmatrix} f_x 0 c_x \ 0 f_y c_y \ 0 0 1 \end{bmatrix} \begin{bmatrix} X / Z \ Y / Z \ 1 \end{bmatrix} ]中间这个3x3矩阵就是内参矩阵K里面四项最关键(f_x)和(f_y)是焦距以像素为单位(c_x)和(c_y)是主点坐标光轴与成像平面的交点。这四个数决定了相机的基本成像属性也是后面所有视觉测量的基础。这里我多说一句(f_x)和(f_y)实际上等于物理焦距除以像元尺寸所以它们是像素单位的焦距。不同相机的(f_x)和(f_y)可能有细微差异如果CMOS传感器像素是正方形的两者基本相等。D435i的RGB传感器实测下来(f_x)和(f_y)差得不多但有些工业相机的像素不是正方形这个差异会直接影响测量精度。1.2 畸变模型径向畸变与切向畸变真实镜头不是完美的针孔光线穿过镜片会发生偏折导致图像变形。畸变主要分两类径向畸变和切向畸变。径向畸变是光线离光轴越远弯曲越厉害表现就是画面边缘的直线变弯常见的有枕形畸变和桶形畸变。切向畸变是镜片安装时与成像平面不平行导致的表现是画面整体倾斜。MATLAB的标定工具箱默认使用五参数畸变模型包含三个径向畸变系数(k_1, k_2, k_3)和两个切向畸变系数(p_1, p_2)。这里有个经验(k_3)只在畸变比较大的镜头比如广角镜头、廉价镜头上才需要估计如果用的是标准镜头(k_3)往往趋近于零。MATLAB里默认估计全部五个系数如果你的镜头畸变很小第三个径向畸变系数(k_3)可能会被算出来一个很大的值但实际校正效果没有明显区别。1.3 为什么用棋盘格张正友标定法的核心逻辑MATLAB标定工具箱用的算法本质上是张正友标定法Zhangs Method。这个方法的核心思路是拍摄不同姿态下的平面棋盘格利用棋盘格角点的精确位置建立单应性矩阵Homography再通过多个单应性矩阵求解内参。为什么非要棋盘格因为棋盘格的角点检测极其稳定且精度高——角点周围的黑白区域在图像梯度上有明确的响应可以做到亚像素级定位。相比圆形标定板或者随机点阵棋盘格对光照变化和视角变化的鲁棒性最好。而且棋盘格是平面靶标不需要精密的立体加工一张用激光打印机打出来的纸贴在平板上就能用。2. 干活前的准备图像采集与标定板制作这块是从实际操作中总结出来的。标定精度很大程度上取决于采集的图像质量这个地方图省事后面会很难办。2.1 标定板制作与尺寸选择标定板可以用打印店的高精度打印也可以买现成的玻璃/陶瓷标定板。我这次用的是A4纸打印的贴在硬纸板上尺寸是10x7的黑白棋盘格内部角点为9x6每个格子边长25mm。这里有个被忽略的要点格子的实际边长必须量准。不要相信打印稿上标注的尺寸打印机的缩放和纸张湿度都可能造成误差量的时候要用卡尺量多个格子的总宽度然后除以格子数得出平均值。标签大小整个棋盘格在图像中占画面高度的1/3以上太小会损失角点定位精度。格子数量我用的是9x6内部角点这个数量比较均衡。格子太多会导致每个格子太小角点检测不稳定太少则标定方程约束不够。表面平整度标定板必须绝对平整贴纸版最好的做法是裱在铝板或玻璃板上。软趴趴的纸板在倾斜拍摄时会发生微小弯曲直接引入系统误差。我自己第一次标定没有关注到这一点用的是贴了打印纸的快递纸板结果重投影误差一直在0.8像素左右降不下来后来换成了在办公用品店买的一块塑料垫板上贴纸误差直接降到0.3像素以内。2.2 图像采集的姿势要求采集图像是整个标定流程中最关键的一步没有之一。这个阶段做得好不好直接决定标定结果的精度。我总结下来的要求是覆盖整个视野标定板上的棋盘格必须出现在画面的各个区域——左上、右上、中央、左下、右下尤其是画面边缘。畸变在边缘最明显如果只把棋盘格放在画面中间边缘的畸变参数就没法被正确估计。多种倾斜角度这是张正友标定法的数学前提。如果所有图像棋盘格都正对着相机那算法在数学上就是病态的求得的焦距会非常不稳定。实际操作时让标定板相对于相机光轴倾斜20度到45度每次倾斜的角度都不一样上下左右前后都要斜着拍。尽量远的距离标定板距离相机要覆盖从近到远的范围。近的时候让棋盘格充满画面远的时候至少占画面高度的1/3左右大致在20cm到1m的距离范围内采集。距离太近会导致标定板平面度误差放大太远则会降低角点在图像中的感知尺寸。固定焦距这个很容易被忽略。用自动对焦镜头的话必须在拍摄前锁定焦距或者手动对焦并在整个采集过程中保持不变。因为标定求解的是一个固定的内参矩阵如果焦距变了内参矩阵也跟着变标定结果肯定不对。充足且均匀的光照避免反光和过曝。棋盘格上的白格一旦过曝变成纯白角点检测虽然还能工作但精度会打折扣黑格反光则会在图像中出现高光斑。最好在自然光或者散射光下拍摄避免直射光。我这次采集了30张图像耗时大概二十分钟。数量上经验值是15到30张太少不够太多没有意义——关键是姿态多样性不是数量多。2.3 用MATLAB还是用Python采集图像这里说一下我的做法我是用OpenCVPython写了一个小脚本采集图像存成jpg后交给MATLAB处理。代码很简单就是调用摄像头然后在画面上绘制当前棋盘格角点检测结果按空格键保存当前帧按q键退出。import cv2 import numpy as np # 打开摄像头这里是D435i的RGB流 cap cv2.VideoCapture(4) # 根据实际摄像头索引调整 cap.set(cv2.CAP_PROP_FRAME_WIDTH, 1280) cap.set(cv2.CAP_PROP_FRAME_HEIGHT, 720) chessboard_size (9, 6) # 内部角点数 square_size 0.025 # 格子边长单位米用于后续3D点生成 # 生成棋盘格的3D坐标 objp np.zeros((chessboard_size[0] * chessboard_size[1], 3), np.float32) objp[:, :2] np.mgrid[0:chessboard_size[0], 0:chessboard_size[1]].T.reshape(-1, 2) objp * square_size saved_count 0 while True: ret, frame cap.read() if not ret: break gray cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY) ret, corners cv2.findChessboardCorners(gray, chessboard_size, None) display frame.copy() if ret: # 亚像素精细化角点位置 criteria (cv2.TERM_CRITERIA_EPS cv2.TERM_CRITERIA_MAX_ITER, 30, 0.001) corners_refined cv2.cornerSubPix(gray, corners, (11, 11), (-1, -1), criteria) cv2.drawChessboardCorners(display, chessboard_size, corners_refined, ret) cv2.imshow(Capture, display) key cv2.waitKey(1) 0xFF if key ord( ) and ret: filename fcalib_images/image_{saved_count:02d}.png cv2.imwrite(filename, frame) print(fSaved: {filename}) saved_count 1 elif key ord(q): break cap.release() cv2.destroyAllWindows()这段代码里有个值得注意的细节保存图像时保存的是原始帧frame不是绘制了角点的显示帧display。因为drawChessboardCorners会在图像上画彩色圆点和连线这些绘制标记会干扰后续MATLAB的角点检测。这个坑我踩过最开始偷懒保存了display结果MATLAB那边角点检测一塌糊涂。3. MATLAB标定工具箱实操从打开App到导出参数3.1 打开Camera CalibratorMATLAB的标定工具箱叫Camera Calibrator在App标签页可以直接搜到或者在命令行窗口输入cameraCalibrator回车。打开后点击“Add Images”选中刚才保存的图像文件夹。这里会弹出两个关键设置棋盘格尺寸和格子边长单位。必须正确填写——MATLAB校准界面默认单位是毫米实测我的格子边长是25毫米所以填25。如果填错标定结果的焦距等参数虽然不会变因为它们是像素单位但后面做三维重建时外参的平移向量单位就全错了。如果只是做内参标定不涉及三维重建单位错误的影响不大但养成好习惯什么参数都填对后面不吃亏。3.2 棋盘格尺寸边长设置最容易被忽略的坑在这里特别说一下这个设置。MATLAB里填的棋盘格尺寸指的是单个格子的边长。例如我的是每个格子25毫米就填25。很多人会把整个棋盘格的长边当成格子尺寸填进去导致标定结果里的平移向量全部错乱。如果你用的是9x6内部角点的棋盘格整个棋盘格的长边其实是10个格子乘以25毫米等于250毫米但你在设置面板里填的还是25毫米这个信息一定要确认好。3.3 角点检测与剔除导入图像后MATLAB会自动检测棋盘格角点并在每张图上叠加显示检测结果。这里要逐张过一遍重点观察两个问题角点定位是否正确尤其是光照不均匀的边缘区域角点可能被错误的偏移了几个像素。MATLAB里可以在某张图上右键选择极坐标图查看棋盘格局部区域的放大效果。更快的办法是把鼠标放在角点位置上看是否精准落在黑白交界处。图像质量是否合格画面模糊、过曝、标定板弯曲的图像直接右键删除。这几张图像放进去只会拉低整体标定精度。我之前碰到过一个特殊情况有一张图标定板背面透光严重角点检测位置出现系统性偏移。虽然MATLAB没报错但重投影误差每张图只有它特别高。后来回看原始图像才发现问题所以图像筛选环节一定要逐张看图不要偷懒直奔标定按钮。3.4 标定参数设置与计算点击“Calibrate”按钮后MATLAB会弹出参数设置选项建议配置如下径向畸变系数2个或3个均可。对于D435i这类镜头选2个就够如果用的是特别广角的镜头选3个。切向畸变系数勾选上。即使摄像头出厂时声称没有切向畸变实际装夹过程中难免有微小倾斜估计出来会有一个很小的值。偏斜因子Skew默认不勾选。现在的CMOS工艺已经很成熟传感器行列之间的非正交偏差可以忽略。强行估计这个参数反而会增加不确定性。设置完成后点击“Compute”开始计算。这个过程很快一般几秒钟就出结果。3.5 重投影误差分析标定完成后MATLAB会展示每张图像的平均重投影误差Mean Reprojection Error。这个值反映的是将检测到的角点根据标定结果重新投影回去与检测到的实际像素位置之间的平均距离。误差单位是像素经验值建议控制在0.5像素以内重投影误差标定质量判断建议动作小于0.2像素优秀可以直接使用0.2到0.5像素良好可以接受0.5到1.0像素一般建议核查图像质量与数量大于1.0像素不合格重新采集图像我第一次标定结果重投影误差是0.84像素排查后发现问题出在三张倾斜过大且棋盘格边缘模糊的图像上。删掉这三张重新计算误差降到0.26像素。所以看到误差大先别急着重拍先看每张图的误差分布用柱状图确认是哪几张图拉高了平均值通常是棋盘格过小或过曝的图。3.6 导出标定结果标定完成后点击“Export Camera Parameters”MATLAB会生成一个包含相机参数结构体的脚本文件。核心字段如下cameraParams.CameraParameters % 包含以下关键字段: % IntrinsicMatrix: 3x3内参矩阵注意是转置形式 % RadialDistortion: 径向畸变系数 [k1, k2, k3] % TangentialDistortion: 切向畸变系数 [p1, p2] % MeanReprojectionError: 平均重投影误差 % WorldPoints: 标定板3D点坐标 % WorldUnits: 世界坐标系单位 cameraParams.IntrinsicMatrix % 例如: % 1.3825e03 0 0 % 0 1.3838e03 0 % 6.4412e02 3.8457e02 1MATLAB导出的内参矩阵是转置形式即(K^T)和OpenCV中的Camera Matrix是转置关系。很多人习惯直接把MATLAB的结果复制进OpenCV代码中结果发现图像去畸变后是花的。正确做法是如果用OpenCV需要将矩阵转置后再使用。3.7 导出单目相机参数除了完整的相机参数结构体MATLAB还支持导出OpenCV格式的XML/YAML文件或者ROS格式的YAML文件。这两个格式在实际项目中很常用省去手动转换的麻烦。具体操作在Camera Calibrator界面点击“Export Camera Parameters”选择“Export to OpenCV”或者“Export to ROS”。导出ROS格式时需要填写相机命名空间和图像话题名生成的文件可以直接被ROS的camera_info_manager加载。4. 标定了但没用对内参使用中的常见错误标定完不等于结束把标定参数用到实际项目里这中间还有一堆坑。下面这几个问题是我在多次实战中碰到过的值得提前说清楚。4.1 内参矩阵形式搞混MATLAB和OpenCV表示内参矩阵的方式完全不一样。标准针孔模型的内参矩阵OpenCV形式是[ K_{OpenCV} \begin{bmatrix} f_x 0 c_x \ 0 f_y c_y \ 0 0 1 \end{bmatrix} ]MATLAB导出的形式是这个矩阵的转置[ K_{MATLAB} \begin{bmatrix} f_x 0 0 \ 0 f_y 0 \ c_x c_y 1 \end{bmatrix} ]因为MATLAB在所有几何计算中约定点是行向量投影公式是[x y z] * K所以矩阵要转置。这个区别是使用相机标定参数时最常见的一个bug。以后在多个工具箱之间交换参数时要习惯性检查矩阵是行主序还是列主序别偷懒直接复制。4.2 畸变系数顺序问题MATLAB的径向畸变系数是[k1, k2, k3]切向畸变是[p1, p2]这和OpenCV的畸变系数顺序完全相同OpenCV是k1, k2, p1, p2, k3。但如果用其他库比如Halcon畸变系数的定义顺序完全不一样必须转成对应的畸变模型。具体到某个库先查文档确认系数排列和正负号定义再套用。4.3 只做内参标定够不够内参标定解决了“相机本身是什么”的问题但实际项目中还经常需要外参——标定板相对于相机的位置和姿态。MATLAB的Camera Calibrator只输出内参不直接输出外参。如果你需要外参有两种办法使用extrinsics函数MATLAB的extrinsics函数可以基于一张棋盘格图像计算该图像对应的外参旋转向量和平移向量。这个函数在Computer Vision Toolbox里用法是传入标定参数对象和单张图像检测到的角点。% 假设cameraParams已经在工作区 [imagePoints, boardSize] detectCheckerboardPoints(calib_image_01.png); [rotationMatrix, translationVector] extrinsics(imagePoints, cameraParams.WorldPoints, cameraParams); % 或者更直观地 worldPoints generateCheckerboardPoints(boardSize, squareSize); [R, t] extrinsics(imagePoints, worldPoints, cameraParams);使用estimateWorldCameraPose函数这个函数接受一组2D-3D点对直接求解世界坐标系到相机坐标系的变换输出是一个rigidtform3d对象2022a之后或一个包含旋转和平移的结构体。它内部使用了PnP算法对噪声的鲁棒性比直接线性方法更好。[worldOrientation, worldLocation] estimateWorldCameraPose(imagePoints, worldPoints, cameraParams);4.4 去畸变后的图像裁剪问题用标定参数去畸变后图像的边缘部分可能被拉伸或裁剪掉。MATLAB的undistortImage函数默认的输出尺寸和输入相同但实际有效区域可能缩小。如果只是做视觉算法预处理去畸变后加一步裁剪即可[J, newOrigin] undistortImage(I, cameraParams); % 如果出现黑色边缘, 可以直接裁剪 J_cropped imcrop(J, [newOrigin(1) newOrigin(2) size(I, 2)-newOrigin(1) size(I, 1)-newOrigin(2)]);Python/OpenCV中对应的是cv2.undistort加上新的相机矩阵newCameraMatrix通常用cv2.getOptimalNewCameraMatrix来获取可以指定alpha参数控制输出中保留的有效像素比例。5. 实战中的高频问题与排查技巧5.1 重投影误差居高不下怎么办这个问题的排查路径基本固定按顺序检查先看柱状图找出拉高平均值的那几张图删除。检查标定板表面是否平整。如果标定板有弯曲误差会集中在特定区域——比如所有图像中某条对角线附近的角点误差都偏高。检查是否有运动模糊。快门速度太慢导致图像轻微模糊角点定位精度会下降。检查棋盘格角点检测是否跳格或漏格。尤其图像边缘棋盘格如果拍摄不全MATLAB的自动检测很可能把内部点当成外部点。检查采集图像时焦距是否被改变。如果用了自动对焦可能拍摄到一半焦距变了后面的图像全部作废。以上五步走完90%的问题都能解决。5.2 标定板边缘角点误差特别大边缘角点误差大最常见的原因是镜头畸变在画面边缘表现最明显而你提供的标定图像中棋盘格出现的位置可能没有覆盖到画面边缘。解决方法是拍更多棋盘格出现在画面边缘的图像让算法在边缘区域也有足够的约束。另一个原因可能是切向畸变没有被正确估计。切向畸变在大畸变镜头上表现明显如果你把所有边缘误差偏高的图像都删了算法可能根本学不到边缘的畸变模式。5.3 图像数量太多反而不好有一个很有迷惑性的情况图像数量越多标定结果不一定越好。我遇到过连续拍了60张图重投影误差反而从0.3像素升到0.6像素。原因很简单——其中近40张图像的姿态高度相似没有提供新的约束信息反而引入了重复的系统误差。好的做法是采集过程中就保持姿态多样性拍15到25张足够。面试的时候面试官问类似问题这个回答也会显得比较专业。5.4 MATLAB角点检测偶发失败如果图像有明显的运动模糊、反光、或者棋盘格被遮挡MATLAB会检测失败。这时候可以在导入图像前先用detectCheckerboardPoints函数尝试检测提前过滤掉不合格的图像imageFiles dir(calib_images/*.png); validIdx []; for i 1:length(imageFiles) I imread(fullfile(imageFiles(i).folder, imageFiles(i).name)); try [imagePoints, boardSize] detectCheckerboardPoints(I); if ~isempty(imagePoints) validIdx(end1) i; %#okSAGROW end catch % 检测失败跳过 end end % 只使用validIdx中的图像进行标定这里有个小经验detectCheckerboardPoints如果检测到部分棋盘格会返回这些点但棋盘格必须完整可见才能正确推断整个棋盘格的角点位置。如果棋盘格有遮挡检测结果可能错误这种图删掉就好。5.5 不同光线下标定结果差异大标定参数的物理意义是相机本身的属性理论上不受光照影响。但实际中光照变化会影响角点检测的像素精度从而影响标定结果。如果两次标定结果差异较大大概率是光照不均匀导致角点定位出现系统偏差。解决办法就是之前提到的在均匀光线下采集避免强光直射和反光。如果必须在多种光照下使用相机也不用重新标定——只要镜头不变标定一次就够了光照变化由后续算法的曝光和增益去适应。6. 从内参到应用标定结果的典型用途标定只是手段应用才是目的。标定结果最典型的几个用途这里简单整理一下。6.1 图像去畸变与可视化这是最直接的应用。畸变校正后图像中直线的曲率消失在视觉SLAM、三维重建、测量等场景中畸变校正都是前置步骤。6.2 三维坐标测量已知内参后单目相机配合已知尺寸的标定板或物体可以恢复物体相对于相机的位置和姿态。具体来说提取图像中的特征点像素坐标结合相机内参和已知的世界坐标点集利用PnP算法求解旋转矩阵和平移向量从而得到物体相对相机的三维位姿。这个方法在机器人抓取、AR叠加、无人机降落引导等场景中应用广泛。6.3 双目立体视觉基础双目相机标定是在两个单目标定的基础上外加立体标定求解两个相机之间的旋转和平移关系。MATLAB也提供了stereoCameraCalibratorApp输入左右相机拍摄的同步图像对输出双目内外参和重投影矩阵可以直接计算深度图。另外说一下热搜词里提到的Autoware相机雷达联合标定。这种标定需要相机内参作为输入然后用联合标定工具求解相机与雷达之间的外参。内参不准后面的联合标定结果也不会好。所以内参标定是整个感知系统标定的地基值得花时间做扎实。6.4 标定参数在深度学习视觉任务中的作用有一个常见的误区深度学习模型似乎“不需要标定”。但当任务涉及把预测结果映射到真实世界的物理坐标系时比如目标检测输出像素框、姿态估计输出关键点像素坐标内参就是连接像素与物理世界的关键桥梁。在做这类项目时提前做一次标定把相机参数和图像一起作为输入或者在后处理阶段将像素坐标投影成物理坐标都是很实用的操作。7. 最后分享两个经验细节第一点是关于标定板的。不要只买或只打印一个尺寸的标定板。手边最好留两个不同尺寸的标定板——例如一个是A4大小的10x7棋盘格25mm格子另一个是更小的7x5棋盘格12mm格子。大标定板用于近距离标定小标定板用于远距离或者小视野的场景。有些场景下大标定板在近距离拍摄时会超出画面范围小标定板就派上用场了。第二点是关于标定频率的。定焦镜头标定一次基本可以用很久变焦镜头或者自动对焦镜头则需要频繁标定。如果项目精度要求高建议定期检查标定结果——方法是拍一张已知尺寸的棋盘格利用标定参数计算它的外参然后观察重投影误差是否仍然在可接受范围内。如果误差明显增大该重新标定了。用MATLAB做相机标定的路径从原理到实操大概就是这些。整个过程只要细心不贪图省事做出来的标定结果完全能支撑起高精度的视觉测量任务。
返回列表