我要提问
ARTICLE DETAIL

资讯详情

前沿编程新知与开发实战干货的深度解读。

双目视觉相机标定实战:从棋盘格到立体校正的完整指南

双目视觉相机标定实战:从棋盘格到立体校正的完整指南 1. 深度感知的毫米级误差是怎么被一张棋盘格消灭的先聊一个很多刚接触双目视觉的人都会问的问题为什么两个相机摆在那儿就能算出物体的距离本质上双目相机模拟的是人眼的工作方式。人的两只眼睛从略微不同的角度观察同一个物体大脑根据左右眼图像的视差disparity判断远近双目相机也是这个逻辑——左相机和右相机同时拍摄找到空间中同一个点在左右图像中的像素坐标差再结合相机本身的几何参数就能把深度信息反算出来。但这里有一个关键前提你必须在像素坐标和真实世界三维坐标之间建立精确的数学关系。这个建立过程就是相机标定。在标定之前左右相机拍出来的两张图里面的同一点对应的像素位置是乱的。镜头有畸变两个相机的位置安装不可能绝对平行光心距离基线也未知。如果不把这些误差全部算清楚直接拿原始图像去做立体匹配生成的深度图和3D点云会出现严重的变形距离测量误差可能达到分米级甚至更大。而经过良好的标定之后精度可以控制在毫米级甚至亚毫米级具体取决于基线长度、相机分辨率和标定质量。所以标定的本质就一句话求出一组内外参数把图像上的二维像素点和空间中的三维点严格对应起来。这篇内容我结合自己实际跑过的流程从相机成像原理、标定参数的含义、棋盘格图像采集的坑到Python代码实现、角点剔除、误差评估完整地梳理一遍。整个过程最快5分钟能跑通但真正把精度调好需要反复迭代几次。文章里的代码都是可以直接复制运行的适合刚入门双目视觉的同学作为第一个能出结果的项目。2. 先把相机成像的数学关系捋清楚像素坐标怎么映射到三维空间2.1 四个坐标系一张关系网标定不是玄学它背后是一套非常成熟的坐标转换模型。理解这层映射关系后面看代码才不会一头雾水。相机成像涉及四个坐标系世界坐标系World Coordinate System我们生活的三维空间绝对参考系。棋盘格标定板所在的位置就是一个世界坐标系。相机坐标系Camera Coordinate System以相机光心为原点Z轴指向相机前方光轴方向的三维坐标系。图像坐标系Image Coordinate System在相机内部以光轴与成像平面的交点为原点单位是毫米或微米的二维坐标系。像素坐标系Pixel Coordinate System以图像左上角为原点单位是像素的二维坐标系。我们平时说的某个点在图像上的坐标就是指这个。一个三维空间点从世界坐标转换到像素坐标完整过程是世界坐标通过外参旋转矩阵R和平移向量T变换到相机坐标。相机坐标通过内参焦距f、主点坐标cx、cy透视投影到图像坐标。图像坐标通过像素尺寸dx、dy即每个像素的物理大小换算成像素坐标。用矩阵形式写出来投影过程就是s * [u, v, 1]^T K * [R | T] * [Xw, Yw, Zw, 1]^T其中 K 是内参矩阵K [[fx, 0, cx], [0, fy, cy], [0, 0, 1]]fx f / dxfy f / dy也就是用像素单位表示的焦距。dx 和 dy 是单个像素在 x、y 方向的物理尺寸。对于双目相机来说左右相机各有自己的一套内参和畸变系数同时两个相机之间还有一个相对位姿关系右相机相对于左相机的旋转矩阵和平移向量这个相对位姿就是双目标定的核心输出之一它决定了立体校正stereo rectification的效果。2.2 畸变是怎么来的为什么必须矫正理想情况下光线穿过镜头中心直线投影到成像平面。但实际镜头是透镜组光线经过折射后不可能完美汇聚于是就有了畸变。畸变主要分两类径向畸变Radial Distortion由镜头形状引起光线越靠近边缘弯曲越厉害。表现为直线的边缘向外凸桶形畸变或向内凹枕形畸变。用 k1、k2、k3 三个系数描述其中 k3 在高畸变镜头如广角、鱼眼中需要考虑。切向畸变Tangential Distortion由镜头和成像平面不平行引起。镜头组装时的微小倾斜都会造成这种畸变用 p1、p2 两个系数描述。所以OpenCV里相机的畸变系数向量通常有5个元素[k1, k2, p1, p2, k3]。畸变不矫正的后果是什么左右图像的极线epipolar line不再是水平的立体匹配时无法在一维搜索空间里找对应点只能做二维搜索计算量爆炸且误匹配率剧增。只有做完了畸变矫正和立体校正左右图像才能变成理想的共面行对准状态也就是空间同一点在左右图像上的投影位于同一水平线上这时候用块匹配BM或半全局匹配SGBM算法才能高效准确地算出视差图。3. 双目标定到底标定了什么内参、外参、畸变和相对位姿OpenCV里单目标定用cv2.calibrateCamera()双目标定用cv2.stereoCalibrate()。两者的关系是先各自单目标定再联合双目标定。单目标定输出的参数参数含义维度内参矩阵 K焦距、主点3×3畸变系数 distk1, k2, p1, p2, k35维旋转向量 rvecs每张标定板位姿的旋转每张图3维平移向量 tvecs每张标定板位姿的平移每张图3维单目标定解决的是每个相机自身怎么看世界的问题。双目标定stereoCalibrate则是在左右相机各自内参已知的基础上求解两个相机坐标系之间的刚体变换R右相机相对于左相机的旋转矩阵T右相机相对于左相机的平移向量有了 R 和 T再加上左右相机的内参和畸变系数cv2.stereoRectify()就能计算出用于立体校正的映射矩阵把左右图像重投影到同一个理想平面上。最后用cv2.initUndistortRectifyMap()生成校正查找表map交给cv2.remap()执行像素重映射。这部分流程初学者最容易混淆的点是立体校正之后的图像是虚拟相机拍摄的虚拟相机的光心位置和朝向已经和真实物理相机不同了。校正后的图像更适合做立体匹配但不能再拿原始相机参数去解释它。换句话说校正图和原始图之间必须严格通过map来对应不能混用。4. 棋盘格图像采集标定精度的一半是由拍照决定的很多人标定结果不理想第一反应是代码写得不对其实绝大多数问题出在图像采集环节。棋盘格图像的质量和覆盖率直接决定了参数求解的稳定性。这里我把自己踩过的坑和总结的采集规范列一下。4.1 棋盘格的选择与制作棋盘格标定板的核心参数是角点数量和方格尺寸。OpenCV的findChessboardCorners()需要你传入(cols, rows)这里的 cols 和 rows 是内部角点的数量不是格子的数量。比如你用一张 9×6 的格子图案内部角点就是 8×5。一个新人经常犯的错是把格子和角点搞混导致findChessboardCorners永远找不到角点。另外方向也很重要——(8, 5)和(5, 8)是不同的输入函数要求的是宽方向的角点数和高方向的角点数通过图片的形状可以判断。方格尺寸square size在单目标定里其实不重要因为标定求解的是内参尺度本身会被吸收但在双目标定里特别是你后续要做深度计算、三维重建就必须以真实物理单位毫米告诉程序每个方格的实际边长否则计算出来的平移向量 T 只是一个无量纲的相对值无法换算成真实距离。打印棋盘格时推荐用哑光纸避免反光打印完用尺子量一下方格实际尺寸打印缩放会导致真实尺寸和设计尺寸有偏差这在追求毫米级精度时必须修正。4.2 采集规范姿态比数量重要很多教材会说采集20对图像就够了但实际经验是20对是底线姿态分布是否均匀才是决定精度的关键。合格的双目标定图像集应该满足覆盖整个视场标定板要出现在画面的左上、右上、中央、左下、右下等区域让所有像素区域都有特征点经过。多个倾斜角度标定板相对于相机平面要有明显的倾角绕X轴和Y轴旋转最好在±30度左右。全部正对着相机的图像对标定内参几乎没有约束力。多个距离从最近工作距离到最远工作距离都要有覆盖实际应用中的深度范围。左右图同时拍摄双目采集中左右相机必须严格同步采集同一时刻的图像标定板位置不能移动。如果相机支持硬件触发就优先用硬件触发不支持的话也要保证两幅图像捕获时间差在毫秒级内。标定板尽量占画面面积的1/4到1/2太小会导致角点检测精度下降太大又容易出视野。我自己常用的模式是远近各一组配合绕X轴旋转、绕Y轴旋转、平移扫视每组5-6张总共25-30对单目和双目结果都能比较稳定。4.3 角点检测失败时的处理策略findChessboardCorners返回 False 的情况太常见了主要原因有棋盘格反光导致局部灰度对比度不足运动模糊或对焦不准棋盘格部分被遮挡棋盘格太小角点特征不明显图像分辨率太高默认参数下角点提取精度下降针对这些情况OpenCV 提供了cv2.findChessboardCornersSB()。这是新一代的角点检测函数基于Blob检测对模糊和光照变化的鲁棒性明显更好在分辨率和畸变较大的场景下成功率高出不少。代价是速度慢一些但对标定这种离线任务完全可接受。另外还有一个技巧降低图像分辨率再检测恢复原分辨率后再精细化。先用原图的1/2或1/4尺寸跑findChessboardCorners检测成功后再在原图上用cornerSubPix()做亚像素精细化。这样可以兼顾检测成功率和亚像素精度。findChessboardCornersSB自带亚像素精度输出不需要额外调 cornerSubPix这是它的另一个优势。5. Python代码实战从单目标定到双目标定、立体校正的完整实现下面这段代码是我在项目里实际用过的流程裁剪了和业务相关的部分保留完整标定链路。环境要求OpenCVopencv-contrib-python4.5以上、NumPy、glob。5.1 整体流程框架先把流程理清楚代码是跟着这个框架走的读取左右图像对检测棋盘格角点并亚像素精细化生成世界坐标点棋盘格角点的三维坐标左右相机各自单目标定双目标定传入单目结果作为初始值立体校正并计算校正映射表用remap生成校正后的左右图验证极线是否水平对齐5.2 完整代码import cv2 import numpy as np import glob # 参数配置 CHESSBOARD_SIZE (8, 5) # 内部角点数量 (cols, rows) SQUARE_SIZE 30.0 # 方格边长单位mm IMAGE_PATH_LEFT ./images/left/*.jpg IMAGE_PATH_RIGHT ./images/right/*.jpg CALIB_RESULT_PATH ./calib_result.npz # 1. 准备世界坐标点 # 以棋盘格平面为z0平面生成每个角点的三维坐标 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 # 2. 检测所有图像中的角点 obj_points [] # 世界坐标点 img_points_l [] # 左相机像素坐标 img_points_r [] # 右相机像素坐标 left_images sorted(glob.glob(IMAGE_PATH_LEFT)) right_images sorted(glob.glob(IMAGE_PATH_RIGHT)) assert len(left_images) len(right_images), 左右图像数量不一致 valid_pairs 0 for left_path, right_path in zip(left_images, right_images): img_l cv2.imread(left_path) img_r cv2.imread(right_path) gray_l cv2.cvtColor(img_l, cv2.COLOR_BGR2GRAY) gray_r cv2.cvtColor(img_r, cv2.COLOR_BGR2GRAY) # 推荐使用 findChessboardCornersSB鲁棒性更好 found_l, corners_l cv2.findChessboardCornersSB(gray_l, CHESSBOARD_SIZE, None) found_r, corners_r cv2.findChessboardCornersSB(gray_r, CHESSBOARD_SIZE, None) if found_l and found_r: # 亚像素精细化SB版本自带亚像素精度这里再加一步更稳 criteria (cv2.TERM_CRITERIA_EPS cv2.TERM_CRITERIA_MAX_ITER, 30, 1e-6) corners_l cv2.cornerSubPix(gray_l, corners_l, (11, 11), (-1, -1), criteria) corners_r cv2.cornerSubPix(gray_r, corners_r, (11, 11), (-1, -1), criteria) obj_points.append(objp) img_points_l.append(corners_l) img_points_r.append(corners_r) valid_pairs 1 print(f有效图像对数量: {valid_pairs} / {len(left_images)}) # 3. 单目标定 ret_l, K_l, dist_l, rvecs_l, tvecs_l cv2.calibrateCamera( obj_points, img_points_l, gray_l.shape[::-1], None, None) ret_r, K_r, dist_r, rvecs_r, tvecs_r cv2.calibrateCamera( obj_points, img_points_r, gray_r.shape[::-1], None, None) print(左相机内参:\n, K_l) print(左相机畸变:\n, dist_l) print(右相机内参:\n, K_r) print(右相机畸变:\n, dist_r) # 4. 双目标定 flags 0 # 常用flags组合按需选择 # flags | cv2.CALIB_FIX_INTRINSIC # 固定内参只优化外参 # flags | cv2.CALIB_USE_INTRINSIC_GUESS # 用单目结果作为初值 # flags | cv2.CALIB_SAME_FOCAL_LENGTH # 强制左右焦距一致 ret_stereo, K_l, dist_l, K_r, dist_r, R, T, E, F cv2.stereoCalibrate( obj_points, img_points_l, img_points_r, K_l, dist_l, K_r, dist_r, gray_l.shape[::-1], criteria(cv2.TERM_CRITERIA_MAX_ITER cv2.TERM_CRITERIA_EPS, 100, 1e-6), flagsflags ) print(重投影误差:, ret_stereo) print(旋转矩阵 R:\n, R) print(平移向量 T:\n, T) # 5. 立体校正 # alpha0 裁剪掉所有黑色区域alpha1 保留全部像素会有黑色边框 alpha 0.0 R_l, R_r, P_l, P_r, Q, roi_l, roi_r cv2.stereoRectify( K_l, dist_l, K_r, dist_r, gray_l.shape[::-1], R, T, alphaalpha) # 计算校正映射表 map_l_x, map_l_y cv2.initUndistortRectifyMap( K_l, dist_l, R_l, P_l, gray_l.shape[::-1], cv2.CV_32FC1) map_r_x, map_r_y cv2.initUndistortRectifyMap( K_r, dist_r, R_r, P_r, gray_r.shape[::-1], cv2.CV_32FC1) # 6. 可视化验证 # 取一对图像做校正验证极线是否水平 test_l_path, test_r_path left_images[0], right_images[0] test_l cv2.imread(test_l_path) test_r cv2.imread(test_r_path) rect_l cv2.remap(test_l, map_l_x, map_l_y, cv2.INTER_LINEAR) rect_r cv2.remap(test_r, map_r_x, map_r_y, cv2.INTER_LINEAR) # 把左右校正图水平拼接画横线辅助检查 vis np.hstack((rect_l, rect_r)) height, width vis.shape[:2] for y in range(0, height, height // 10): cv2.line(vis, (0, y), (width, y), (0, 255, 0), 1) cv2.imwrite(./rectify_visualization.png, vis) print(校正可视化图像已保存: ./rectify_visualization.png) # 7. 保存标定结果 np.savez(CALIB_RESULT_PATH, K_lK_l, dist_ldist_l, K_rK_r, dist_rdist_r, RR, TT, R_lR_l, R_rR_r, P_lP_l, P_rP_r, QQ, roi_lroi_l, roi_rroi_r) print(标定结果已保存:, CALIB_RESULT_PATH)5.3 代码里的几个关键选择为什么用findChessboardCornersSB而不是老的findChessboardCorners老函数在光照不均、轻微模糊、图像分辨率高的情况下返回False的概率不低。SB版本内部用的是基于Blob的检测算法对上述问题鲁棒性更强。我实际对比过同一组120张图老函数成功检测82张SB版本成功检测114张差距非常明显。当然SB版本也不是万能的棋盘格反光过强或者格子太小仍然会失败。为什么在SB之后又调用cornerSubPix理论上SB返回的角点已经是亚像素级别但OpenCV文档里对SB精度并没有严格要求。实测下来额外做一次 cornerSubPix 可以让角点位置在前后两次检测之间更稳定特别是对于畸变较大的边缘区域。这个操作的额外耗时很小但能让重投影误差降低0.02像素左右值得做。双目标定里的flags参数怎么选这里有个非常实际的选择题如果左右相机是同一型号理论上 fx 和 fy 应该非常接近。可以用CALIB_SAME_FOCAL_LENGTH让双目标定强制左右焦距一致这种约束会提升标定的稳定性。如果相机有出厂标定参数可以用CALIB_USE_INTRINSIC_GUESS传入初值帮助优化器更快收敛。如果单目标定重投影误差已经在0.1像素以内可以直接CALIB_FIX_INTRINSIC只看双眼外参这样能防止双目优化过程中内参跑偏。我个人的做法是单目标定质量好的情况下用CALIB_FIX_INTRINSIC CALIB_SAME_FOCAL_LENGTH组合效果最稳定。但如果单目标定本身有偏差还是让双目重新优化一遍内参更稳妥。Q矩阵有什么用stereoRectify输出的 Qdisparity-to-depth mapping matrix是 4×4 的投影矩阵作用是把视差图disparity map转换为三维点云。有了 Q配合cv2.reprojectImageTo3D()输入一张视差图和对应的 Q就能直接输出三维坐标。这一步是后续生成3D点云的关键。5.4 生成3D点云的代码片段标定完成后如果你已经用SGBM算出了视差图生成点云就非常简单import open3d as o3d # disparity是双通道float32视差图invalid_mask用于过滤无效视差 def disparity_to_pointcloud(disparity, Q, rgb_imageNone, max_depth3000.0): # 如果视差图是uint8类型需要转成float32并做缩放处理 disparity disparity.astype(np.float32) # 3D重投影 points_3d cv2.reprojectImageTo3D(disparity, Q) # 过滤无效点深度过大、视差为0或无穷 mask (points_3d[:, :, 2] 0) (points_3d[:, :, 2] max_depth) points points_3d[mask] colors rgb_image[mask] if rgb_image is not None else None # 转成Open3D点云 pcd o3d.geometry.PointCloud() pcd.points o3d.utility.Vector3dVector(points.reshape(-1, 3)) if colors is not None: pcd.colors o3d.utility.Vector3dVector(colors.reshape(-1, 3) / 255.0) return pcdreprojectImageTo3D算出的是左相机坐标系下的三维坐标单位由Q矩阵决定。由于我们在标定时SQUARE_SIZE用的是毫米Q矩阵里的尺寸系数也会以毫米为单位所以输出点云的坐标单位是毫米。这块有一个隐藏的坑reprojectImageTo3D返回的 Z 值通常是深度但 X、Y 值的大小取决于图像分辨率。如果你的分辨率很高点云里的点会非常多直接可视化会很卡。建议先用pcd.voxel_down_sample(voxel_size0.5)降采样再显示交互会流畅很多。6. 标定结果的验证与误差排查怎么看标定得好不好6.1 重投影误差的合理范围stereoCalibrate返回的 ret_stereo 是整体重投影误差单位是像素。合理的范围重投影误差 0.5像素很好0.5 ~ 1.0像素可以用但要检查是否有异常1.0像素明显有问题需要排查需要注意的是重投影误差只是拟合优度不是实际测量精度。它衡量的是这些标定板角点的二维投影位置和检测位置的差距而实际应用里的深度精度还受基线、分辨率、视差匹配质量的影响。很多论文里看到0.1像素标定精度是在理想实验室条件下得到的实际工程中0.3~0.5像素就已经算不错了。6.2 立体校正结果的检查方法前面代码里已经生成了校正可视化图。判断校正好坏的标准是拼接后的图上画横线左右图中同一个特征点应该落在同一条水平线上。如果同一个点在左右图上的高度差超过1-2个像素说明标定或校正参数有问题。更精确的做法是检测校正后图像的角点计算对应角点的 y 坐标差。我自己写过一个简单脚本检测同一对校正图里棋盘格角点坐标然后输出最大和平均 y 方向偏差。通常要求平均偏差小于0.5像素最大偏差小于1像素。6.3 常见标定失败原因与解决方案现象可能原因解决方案重投影误差很大1.0角点检测误差大由图像模糊、反光造成重新采集更清晰的图像使用SB检测校正后横线不对齐图像对不同步标定板动了重新采集保证同步触发某些图像明显拉低整体精度该图像角点检测有系统偏差比如部分遮挡剔除该图像对或者用更严格的角点筛选棋盘格方位重复度高、覆盖不足图像姿态太单一重新采集增加倾斜、远近变化局部区域边缘畸变矫正后拉伸严重alpha参数太小增大alpha值保留更多原始像素6.4 剔除不合格角点的进阶操作针对某些图像拉低整体精度的情况比较稳妥的自动化做法是逐对图像计算单目标定的重投影误差按误差排序剔除误差明显偏大的那几对。代码逻辑不复杂核心是在calibrateCamera之后用projectPoints把世界坐标重新投影回去和检测到的角点坐标算欧式距离def compute_reprojection_error(obj_points, img_points, K, dist, rvecs, tvecs): total_error 0 total_points 0 errors_per_image [] for i in range(len(obj_points)): projected_points, _ cv2.projectPoints( obj_points[i], rvecs[i], tvecs[i], K, dist) error cv2.norm(img_points[i], projected_points, cv2.NORM_L2) / len(projected_points) errors_per_image.append(error) total_error error * len(projected_points) total_points len(projected_points) return total_error / total_points, errors_per_image # 对左相机做误差统计 _, errors_l compute_reprojection_error( obj_points, img_points_l, K_l, dist_l, rvecs_l, tvecs_l) # 按误差排序打印前5张误差最大的图 import heapq top5 heapq.nlargest(5, range(len(errors_l)), keylambda i: errors_l[i]) for idx in top5: print(f图像 {idx} 重投影误差: {errors_l[idx]:.4f} px)把这部分放到双目标定之前先把误差大于某个阈值比如0.8像素的图像对剔除再重新跑标定整体误差通常能下降30%以上。我自己的一个项目里第一次全量标定重投影误差1.2像素剔除3对异常图之后降到了0.35像素差距非常大。7. 从标定到3D点云一条链路里的关键坑位总结标定只是双目视觉的第一步但它决定了后续所有环节的上限。如果标定粗糙后面的立体匹配、深度滤波、点云拼接都会持续放大误差。这里把从标定到3D点云全链路里最常踩的几个坑位集中说一下。7.1 视差图到点云的单位一致性reprojectImageTo3D输出的单位由 Q 矩阵决定但 Q 矩阵里的参数依赖stereoRectify输入的棋盘格尺寸。如果标定时 SQUARE_SIZE 填的是30毫米后续所有涉及距离的阈值都要按毫米来设置。很多人拿视差图去生成点云发现深度数值大得离谱多半是视差图的数值范围没处理好。SGBM输出的视差图默认是 int16 类型实际的视差值 像素值 / 16。所以用到视差图时一定要除以16.0再传给reprojectImageTo3D。否则深度就是实际值的16倍。这个坑在初学者里极其普遍而且报错不明显出来的点云就是虚胖的。7.2 有效视差范围的裁剪不是整张图每个像素都有有效视差。遮挡区域、无纹理区域、超出相机工作距离的区域SGBM都会给出错误值。在生成点云之前务必做两件事设置最小视差和最大视差过滤明显错误的匹配点用cv2.medianBlur或WLS滤波加权最小二乘对视差图做平滑和空洞填充SGBM自带的uniquenessRatio和speckleWindowSize参数也建议调一下能显著减少错误匹配的噪声。参数调优没有一个通用放之四海皆准的配方但可以遵循大方向numDisparities越大能匹配的范围越大但计算量也越大blockSize越大视差图越平滑但边缘细节越差。7.3 基线越长近距精度越高但视野越小双目系统的精度和基线T的模长直接相关。同样的相机分辨率下基线越长近处物体的深度分辨力越高但公共视野越小最近可测距离也越大。所以选相机基线时要想清楚工作距离范围近距离精细测量用短基线远距离大场景用长基线。这个关系可以从三角测量公式推出来。深度 Z 与视差 d 的关系是 Z f * B / d两边对 d 求导得到深度误差ΔZ -Z^2 / (f * B) * Δd可以看到误差和 Z 的平方成正比和基线 B、焦距 f 成反比。这也解释了为什么双目系统的精度会随着距离增加急剧下降。10米处可能还有厘米级精度但50米外的深度误差就会达到米级。7.4 别忘了 ROIstereoRectify输出的roi_l和roi_r分别标识了校正后图像的有效矩形区域。因为校正后图像边缘会有黑色区域alpha 值越小裁剪越狠如果直接整图处理边缘区域的黑边会影响后续的立体匹配。建议根据 ROI 把图像裁剪到有效区域再做点云生成能省掉不少无效计算。8. 实操中的最后几点心得标定这种事理论看十遍不如动手做一遍。第一次跑通整个流程你会发现代码本身并不难难的是采集出合格的标定图像、判断结果是否可靠、以及根据误差反推哪里出了问题。我的建议是不要追求一次性标定完美而是把它当成一个迭代过程。每次标定完一定看一眼立体校正图上的横线是否对齐再算一下重投影误差如果有异常就剔除对应图像对重新来一次。多跑几轮之后你会对什么样的图像对标定有帮助什么样的图像是废图形成直觉这个直觉比任何参数调优技巧都值钱。对于刚入门的朋友先不用急着上高精度工业相机拿两个普通USB相机搭一个简易双目架子跑通整个标定和点云生成的流程再考虑硬件升级。算法的坑和硬件的坑是两回事先把算法链路吃透硬件带来的限制反而更容易定位。这套流程我前后跑过不下百次踩过的坑基本都写在上面了。如果你照着代码走一遍发现重投影误差还是降不下来大概率问题出在图像采集环节——回去重新拍一套覆盖姿态更多、清晰度更高的图片效果立竿见影。
返回列表