ARTICLE DETAIL

资讯详情

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

张正友标定法全解析:从单应性矩阵到OpenCV实践

张正友标定法全解析:从单应性矩阵到OpenCV实践 简介相机标定是计算机视觉中的基础环节旨在求解相机内参数与畸变参数。这份实验报告以张正友标定法为核心兼顾传统标定与自标定方法的优点适合学习视觉三维重建、视觉导航及相机标定应用的开发者参考。报告从实验原理入手完整梳理了打印标定模板、多角度拍摄、特征点检测、单应性矩阵求解、内参数与外参数估计、畸变系数计算及优化求精的流程并结合OpenCV给出初始化参数和迭代求解总体最小二乘问题的具体路径。实验部分展示了各幅图像的平均误差、相机内参矩阵和畸变系数便于读者对照复现与验证精度。资源包为一个Word文档大小约四百七十七千字节已有两千三百四十七人浏览学习适合需要系统掌握张正友标定法并撰写实验报告的人群。1. 相机标定为什么绕不开张正友方法做视觉测量、机器人抓取或者自动驾驶感知的人迟早都会遇到同一个问题图像里的像素坐标怎么换算成真实世界里的物理坐标答案的第一步就是相机标定。在办公、家庭这类桌面视觉系统DVS场景里张正友标定法是综合成本最低的选择——它不需要昂贵的精密三维标定块只需要一块打印出来的棋盘格从不同角度拍十几张照片就能得到内参、外参和畸变系数。实验数据里10幅图像标定后的总体平均重投影误差只有0.064像素这个精度已经能满足大部分机械臂抓取和视觉测量需求。这套方法1999年由微软研究院的张正友提出介于传统标定和自标定之间既不需要高精度设备又不像自标定那样精度不可控。本文从数学模型、OpenCV实现、实验结果到双目和IMU联合标定的衔接完整过一遍。2. 张正友标定的数学模型从单应性矩阵到内外参解耦2.1 坐标系定义与内参矩阵张正友方法的核心是建立标定板平面到像素平面的映射关系。标定板坐标系的原点选在棋盘格某个内角点上Z轴始终为0所以角点的空间坐标都是[X, Y, 0]的形式。在OpenCV 3.0及之后的版本里标定板的角点坐标写成[i * square_size, j * square_size, 0]其中square_size是棋盘格每个方格的实际物理尺寸单位通常是毫米。相机内参矩阵A的形式是A | fx s cx | | 0 fy cy | | 0 0 1 |fx和fy分别是x轴和y轴方向的焦距单位是像素cx和cy是光心在主点上的偏移s表示成像平面x轴和y轴的不正交性理想情况下为0。注意这里的fx不是物理焦距f而是f除以像素尺寸所以同一款相机在不同分辨率下fx会成比例变化。如果你换了一台d435i这样的深度相机标定得到的fx和fy通常会落在几百到几千像素的范围内具体数值取决于分辨率和镜头视场角。2.2 单应性矩阵的求解不同位置的棋盘格到相机成像可以用一个公式统一表达s * m A * [r1 r2 r3 t] * M其中m是像素坐标齐次形式[u, v, 1]^TM是世界坐标齐次形式[X, Y, 1]^Tr1、r2、r3是旋转矩阵R的列向量t是平移向量。因为Z0r3那一列乘完是0所以实际参与计算的是A和[r1 r2 t]的乘积。这个3x3矩阵H就是单应性矩阵H A * [r1 r2 t]把H写开每列记为h1、h2、h3就能把问题转化为线性方程组。对于每个角点可以得到两个约束方程n个角点组成2n个方程。用矩阵形式Lx0表示x是H按列展开的1x9向量L是2nx9的矩阵。x的解是L的最小奇异值对应的右奇异向量这一步用SVD就能算出来。这里有个容易忽略的点单应性矩阵H是齐次定义的乘以任意非零常数结果不变所以SVD解出来的结果需要归一化通常取最后一行为1。实际使用时OpenCV的findHomography函数还会用RANSAC剔除误匹配的角点但在标定场景里因为棋盘格角点是精确提取的直接使用DLT直接线性变换就够了不需要RANSAC。2.3 从单应性约束反解内参求出H之后要利用旋转矩阵的单位正交性来约束内参。r1和r2是单位向量且互相正交所以h1^T * A^(-T) * A^(-1) * h2 0 h1^T * A^(-T) * A^(-1) * h1 h2^T * A^(-T) * A^(-1) * h2令B A^(-T) * A^(-1)B是一个对称矩阵包含6个未知元素。每幅图像提供两个约束方程因此至少需要3幅图像才能解出完整的B矩阵。如果只有两幅图像就必须假设s0即成像平面两轴完全正交这样可以减少一个未知数。B矩阵的求解同样是SVD解出B之后通过Cholesky分解或者直接按公式反解内参。常用的一组公式是v0 (B12*B13 - B11*B23) / (B11*B22 - B12^2) lambda B33 - (B13^2 v0*(B12*B13 - B11*B23)) / B11 fx sqrt(lambda / B11) fy sqrt(lambda * B11 / (B11*B22 - B12^2))也就是说标定最少需要3幅不同姿态的棋盘格图像。但实际项目中我一般至少拍15到20幅覆盖画面中心和四角并且让棋盘格相对于相机有明显的俯仰、偏航和滚转变化。只拍三四幅图像求出的内参矩阵数值看起来可能正常但换一个角度验证时畸变校正就会暴露问题。2.4 外参与畸变参数的迭代求精内参有了之后外参可以直接从单应性矩阵拆出来r1 lambda * A^(-1) * h1 r2 lambda * A^(-1) * h2 r3 r1 x r2 t lambda * A^(-1) * h3这里的lambda是归一化系数。由于噪声影响拆出来的旋转矩阵并不严格满足正交性所以OpenCV里这个结果只作为初始值后续会用迭代优化把旋转矩阵R重新参数化为旋转向量r罗德里格斯公式。畸变参数方面张正友原始论文里只讨论径向畸变的前两项k1和k2但OpenCV实际使用的畸变模型包含5个参数[k1, k2, p1, p2, k3]其中k1、k2、k3是径向畸变系数p1、p2是切向畸变系数。给定n幅图像、每幅m个角点可以得到2mn个方程记为Dkd用最小二乘求解k (D^T * D)^(-1) * D^T * d畸变参数初始值全部设为0然后和内外参一起放到总体最小二乘问题里用Levenberg-Marquardt算法迭代求解。最终优化目标是最小化所有角点的重投影误差也就是图像上检测到的角点坐标和根据外参反投影回来的坐标之间的欧氏距离。3. OpenCV标定实现从角点检测到calibrateCamera3.1 数据准备与棋盘格图像采集标定质量一半取决于采集的数据而不是算法。打印棋盘格时要注意图案要平整不能贴在弯曲的纸板或布上方格边长要测量准确用游标卡尺量多组取平均。拍照时固定相机移动棋盘格比固定棋盘格、移动相机效果更稳定尤其在机械臂眼在手上eye-in-hand的场景里外参标定还需要另外处理。采集时遵循几个原则棋盘格在画面中占的面积从大到小都要覆盖每个图像里棋盘格不要过于倾斜倾斜角度控制在45度以内要让棋盘格出现在画面的四个角和中心位置。如果用的是鱼眼镜头或者广角镜头畸变比较严重图像数量可以增加到25到30幅。3.2 角点检测与亚像素优化OpenCV的findChessboardCorners函数可以自动检测棋盘格内角点。一个常见的问题是不同版本的OpenCV对棋盘格patternSize的定义不同——patternSize(7, 7)表示的是内角点数量而不是棋盘格的数量。如果是标准8x8棋盘格内角点数是7x7patternSize要填(7, 7)。写错了会直接报错说找不到角点。角点检测出来之后通常要用cornerSubPix做亚像素细化提高角点坐标精度。这一步很关键因为后面所有计算都建立在角点坐标的精度上。细化窗口一般取(5, 5)到(11, 11)迭代终止条件设为最大30次循环或0.001像素的epsilon。3.3 标定主流程代码下面是一段完整的OpenCV Python标定流程使用OpenCV 4.x环境import cv2 import numpy as np import glob # 棋盘格内角点数例如9x6表示水平9个内角点、垂直6个内角点 pattern_size (9, 6) square_size 25.0 # 方格边长单位mm # 生成标定板坐标系下的角点世界坐标 objp np.zeros((pattern_size[0] * pattern_size[1], 3), np.float32) objp[:, :2] np.mgrid[0:pattern_size[0], 0:pattern_size[1]].T.reshape(-1, 2) * square_size obj_points [] # 世界系角点 img_points [] # 像素系角点 images glob.glob(calib_images/*.jpg) for fname in images: img cv2.imread(fname) gray cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) # 检测棋盘格内角点返回是否成功和角点坐标 ret, corners cv2.findChessboardCorners(gray, pattern_size, None) if ret: # 亚像素细化窗口大小取5x5迭代30次或精度0.001 criteria (cv2.TERM_CRITERIA_EPS cv2.TERM_CRITERIA_MAX_ITER, 30, 0.001) corners_sub cv2.cornerSubPix(gray, corners, (5, 5), (-1, -1), criteria) obj_points.append(objp) img_points.append(corners_sub) # 标定主函数 ret, mtx, dist, rvecs, tvecs cv2.calibrateCamera( obj_points, img_points, gray.shape[::-1], None, None ) print(f重投影误差: {ret:.6f} 像素) print(f内参矩阵:\n{mtx}) print(f畸变系数: {dist.ravel()}) # 保存结果方便后续去畸变或联合标定使用 np.savez(calib_result.npz, mtxmtx, distdist, rvecsrvecs, tvecstvecs)calibrateCamera的返回参数里ret是所有角点的总体重投影误差单位是像素。mtx是3x3内参矩阵dist是1x5或1x8的畸变系数向量rvecs和tvecs分别是每幅图像的旋转向量和平移向量。注意这里gray.shape[::-1]传入的是图像宽高顺序是(width, height)不要写反。3.4 参数说明与常见报错calibrateCamera还有几个可以调的参数默认情况下够用但对特殊镜头需要调整参数作用推荐值flags控制畸变模型和优化约束默认0即可criteria迭代优化终止条件(EPSMAX_ITER, 30, 1e-6)rvecs/tvecs每幅图像的外参初始值不传则自动估计校准点数需要满足超定条件至少3幅建议15如果实际运行时报error: (-215:Assertion failed) nimages 0说明images列表为空检查glob路径是否正确。如果报Calibration failed通常是角点检测成功但优化不收敛原因多为图像数量太少、棋盘格姿态过于重复或者pattern_size填错。另外均匀亮度和避免反光也很重要反光会干扰亚像素角点的灰度梯度计算导致检测出的角点往边缘偏移。4. 实验结果解析内参矩阵、畸变系数与重投影误差4.1 实验数据概览实验中使用10幅不同姿态的棋盘格图像进行标定总体平均重投影误差为0.0642303像素。每幅图像的误差都在0.06像素附近波动最大的是第5幅0.0679925像素最小的是第10幅0.0597705像素波动范围不到0.008像素说明各幅图像的标定质量比较一致。标定得到的内参矩阵为| 4647.52 0 936.40 | | 0 4672.66 397.64 | | 0 0 1 |畸变系数为k1 -0.034905 k2 -3.960346 p1 -0.011076 p2 -0.006547 k3 -2.224948每幅图像的旋转向量和平移向量也输出在结果中。旋转向量的模长表示旋转角度单位是弧度三种分量表示旋转轴方向。例如第1幅图像的旋转向量是[-20.75; -4.48; 556.70]模长约为557这么大的数值是因为旋转向量没有归一化实际上角度很大说明这块棋盘格相对于相机有较大的姿态倾斜。4.2 如何判断标定结果是否可靠重投影误差是最直观的指标。低于0.1像素说明标定质量不错0.1到0.3像素属于可接受范围超过0.5像素就要检查数据。这个实验的0.064像素属于很理想的结果说明角点检测和优化迭代都收敛得很好。但重投影误差不是唯一指标。内参矩阵的合理性同样重要cx和cy应该在图像中心附近。如果图像分辨率是1920x1080主点cx936、cy397其中cy偏离中心540较多可能的原因是相机sensor装配或者镜头光轴不垂直于成像平面这个数值本身不一定是错误。fx和fy应该接近。如果两者差异超过5%说明像素不是正方形或者镜头存在较大的各向异性畸变。畸变系数量级要合理。k1通常是负的小数表示桶形畸变k2如果是负的且绝对值较大说明镜头边缘畸变明显p1、p2的量级一般在0.01以下。结合第1幅图像的数据旋转向量三个分量相差很大第三分量556远超前两个分量说明棋盘格主要绕相机z轴旋转了比较大的角度。这在采集图像时很常见因为让棋盘格正对相机但绕光轴转不同角度能有效约束内参矩阵中的s参数。4.3 从标定结果反推相机模型标定结果可以直接用来去畸变。OpenCV提供两种方式import cv2 import numpy as np # 加载标定结果 data np.load(calib_result.npz) mtx data[mtx] dist data[dist] img cv2.imread(test.jpg) h, w img.shape[:2] # 方法一先计算最优新内参矩阵再映射 newcameramtx, roi cv2.getOptimalNewCameraMatrix( mtx, dist, (w, h), alpha0, (w, h) ) dst cv2.undistort(img, mtx, dist, None, newcameramtx) x, y, w_, h_ roi dst_cropped dst[y:yh_, x:xw_] # 方法二使用initUndistortRectifyMap remap适合视频流 mapx, mapy cv2.initUndistortRectifyMap( mtx, dist, None, newcameramtx, (w, h), cv2.CV_32FC1 ) dst_remap cv2.remap(img, mapx, mapy, cv2.INTER_LINEAR)方法一中alpha0表示裁剪掉所有可能无效的黑色边缘alpha1表示保留所有原始像素输出图像会包含黑色区域。对于需要后续做立体匹配或光流计算的场景建议用initUndistortRectifyMap生成映射表因为提前算好映射表后处理视频流时每帧只需要一次remap性能开销远小于每次调用undistort。5. 进阶从单目标定到双目、IMU联合标定与工程落地5.1 提高标定精度的几个实操技巧单目标定结果不满意时先不要急着改算法按顺序排查这几件事。第一检查棋盘格打印质量。用激光打印机在哑光纸上打印不要用喷墨喷墨打印的墨点扩散会让角点检测产生系统性偏移。第二检测角点时把findChessboardCorners的flags参数设为CALIB_CB_ADAPTIVE_THRESH CALIB_CB_NORMALIZE_IMAGE对光照不均匀的图像效果更稳定。第三如果使用变焦镜头标定必须在每个要使用的焦距下分别标定内参不通用。第四对于d435i这类深度相机建议同时标定RGB相机和红外相机然后用双目标定结果进行深度对齐否则RGB图和深度图的像素对齐会有明显的边缘错位。另外一个容易被忽略的细节是棋盘格不要拿在手里拍摄。手持抖动会导致运动模糊角点亚像素细化时梯度方向被模糊抹平检测精度下降。把棋盘格贴在硬纸板上放在桌面上或者用支架固定移动的是棋盘格本身而不是相机这样拍出来的图像边缘更锐利。5.2 双目相机标定与剔除不合格角点双目相机标定在单目基础上增加了左右相机的外参关系。流程一般是先分别标定左右相机内参再用stereoCalibrate求右相机相对于左相机的旋转矩阵R和平移向量T最后用stereoRectify计算极线校正映射。值得注意的一点是双目标定对左右图像的角点对应关系要求很高如果某一幅图的棋盘格检测失败整对图像都应该丢弃而不是只丢失败的那一边。OpenCV里可以这样处理# 假定left_corners和right_corners分别是左右图像检测到的角点 # retL表示左图是否检测成功retR表示右图 if retL and retR: # 亚像素细化 pattern_size (9, 6) criteria (cv2.TERM_CRITERIA_EPS cv2.TERM_CRITERIA_MAX_ITER, 30, 0.001) cornersL cv2.cornerSubPix(grayL, cornersL, (5, 5), (-1, -1), criteria) cornersR cv2.cornerSubPix(grayR, cornersR, (5, 5), (-1, -1), criteria) obj_points.append(objp) img_pointsL.append(cornersL) img_pointsR.append(cornersR)如果做了亚像素细化重新计算重投影误差后误差突然增大那一定是cornerSubPix追踪到了棋盘格边缘的干扰点上。一个有效的做法是用所有点的中值误差做阈值把误差超过中值3倍的图像剔除后重新标定。对于双目相机还可以额外检查左相机和右相机的重投影误差是否都在同一个量级如果左图误差0.05像素而右图误差0.5像素说明右相机标定时有系统性问题比如右图过暗或者棋盘格在右图中太靠近画面边缘。5.3 相机与IMU、雷达联合标定的衔接单目和双目标定得到的内参是相机IMU联合标定和相机雷达联合标定的前置条件。在ubuntu18.04这类环境下做Autoware相机雷达联合标定时标定工具需要输入的相机内参矩阵和畸变系数正是calibrateCamera输出的mtx和dist。如果内参不准确后面的外参标定结果会整体偏移。相机与IMU联合标定的原理是利用相机标定得到的位姿变化序列和IMU输出的角速度、加速度积分结果做时间戳对齐和位姿轨迹匹配求解两传感器之间的旋转矩阵和平移向量。实际操作中最容易出问题的不是算法而是时间同步。相机帧和IMU数据如果不做时间戳插值直接丢给标定工具求出的外参旋转误差可能达到1到2度。从工程角度看建议把单目标定结果的保存格式统一。常见做法是保存为一个YAML文件字段包括image_width、image_height、camera_matrix、distortion_coefficients。这样无论后续接OpenCV、ROS camera_calibration包还是Autoware的标定接口都能直接用。5.4 常用标定工具与验证方法除了OpenCV手写标定流程实际项目中常用的还有ROS的camera_calibration包基于棋盘格流式标定适合在ROS环境下快速标定单目和双目相机输出camerainfo话题。Kalibr苏黎世联邦理工开源的相机-IMU联合标定工具支持多相机、鱼眼和IMU联合标定输出精度很高但配置复杂。OpenCV的sample代码在samples/cpp/tutorial_code/calib3d里有完整的标定例子包括交互式棋盘格采集和标定结果可视化。标定完成后建议做一次实际场景验证。找几个已知尺寸的物体比如A4纸的长边297mm放在画面不同位置用标定结果做去畸变后测量像素距离再根据内参和物体深度换算物理尺寸。如果换算结果和实测误差小于1%说明标定可以信任。如果误差偏大通常问题不在内参而在深度测量或者角点检测环节。对于手眼标定eye-in-hand或eye-to-hand单目标定得到的外参还需要和机械臂末端的位姿做AXXB求解这一步的精度上限直接取决于相机内参标定的质量。内参误差0.1像素在3米距离处可能造成几毫米的位置偏差而对于近距离的桌面视觉系统0.064像素的误差带来的位置偏差可以控制在亚毫米级这也是张正友方法在DVS场景里能够长期占据主流位置的原因。本文还有配套的精品资源点击获取
返回列表