资讯动态

OpenCV双目视觉立体标定与校正:从棋盘格到深度图实战

发布时间:2026/10/11 18:20:39 来源:尧图企业网站定制
简介这份资源面向计算机视觉初学者与双目视觉项目开发者提供基于OpenCV的双目立体标定与校正完整代码帮助解决双目标定参数估计、图像去畸变与立体校正等实际问题。压缩包内共1个doc文件约294KB以文档形式整理了标定与校正的源码及分析涵盖cv::stereoCalibrate、cv::undistort、cv::stereoRectify等关键函数的调用流程并给出StereoCalib函数的实现细节包括棋盘格角点读取、内参外参估计、标定结果保存以及校正后图像的显示方式。读者可据此理解双目视觉系统的标定原理掌握从棋盘格图像到校正图像的完整处理链路并直接参考代码结构应用于三维重构、深度计算等场景。目前已有2591人学习下载适合需要快速上手OpenCV双目标定与校正的开发者查阅。1. 双目视觉立体标定与校正从两张棋盘格到可测距的深度图很多人第一次跑双目卡在不是算法而是标定完发现极线对不齐、视差图全是麻点。双目视觉立体标定与校正代码opencv 这套东西本质就干两件事先用棋盘格把左右相机的内参、畸变、以及两者之间的旋转平移关系解出来再用立体校正把两个视角“掰”成理想平行光轴让同名点只差水平方向一个像素偏移。做完这一步视差转深度才有意义测距、三维重建、避障才有地基。适合谁手里有双目模组、想自己标一遍而不是直接抄厂家参数的嵌入式视觉工程师以及做机器人、AGV、体积测量的开发者。下面按我实际调通的顺序讲参数和坑都摊开。2. 标定前必须想清楚的选型与采集规范2.1 为什么标定板、镜头、基线要一起定双目标定的精度上限在采集图像那一刻就锁死了。常见做法是先用张正友标定法分别标左右相机再做双目标定。但很多人忽略标定板方格尺寸必须和实际打印尺寸一致打印后要用卡尺量别信PDF标注。镜头选型上广角畸变大的镜头虽然视野宽但畸变模型要选对OpenCV里对应CALIB_RATIONAL_MODEL或CALIB_THIN_PRISM_MODEL普通镜头用默认5参数就够。基线方面基线越长测距越远但盲区越大室内1米内测距60mm到120mm基线比较稳。标定板建议用不小于A3的棋盘格格数9x6或11x8太小会导致角点检测抖动。2.2 采集图像的硬性要求采集时左右相机必须同步触发哪怕差几毫秒运动物体就会让标定外参漂移。静态标定也要保证标定板在左右图中都完整、清晰、不切边。我一般会拍20到30组覆盖画面四个角、中心、远近不同距离、不同倾斜角。每组左右图命名要能配对比如left_01.png和right_01.png。光照要均匀避免反光棋盘格黑白边界模糊会直接让findChessboardCorners失败。下面这段是采集后检查角点是否全部找到的脚本跑一遍能省很多返工。import cv2 import glob import numpy as np # 棋盘格内角点数量注意是内角点不是方格数 pattern_size (9, 6) criteria (cv2.TERM_CRITERIA_EPS cv2.TERM_CRITERIA_MAX_ITER, 30, 0.001) left_images sorted(glob.glob(calib/left_*.png)) right_images sorted(glob.glob(calib/right_*.png)) for lpath, rpath in zip(left_images, right_images): limg cv2.imread(lpath) rimg cv2.imread(rpath) lgray cv2.cvtColor(limg, cv2.COLOR_BGR2GRAY) rgray cv2.cvtColor(rimg, cv2.COLOR_BGR2GRAY) lret, lcorners cv2.findChessboardCorners(lgray, pattern_size, None) rret, rcorners cv2.findChessboardCorners(rgray, pattern_size, None) if not (lret and rret): print(f角点缺失: {lpath} left{lret} right{rret}) continue # 亚像素精化窗口11x11迭代30次或精度0.001 lcorners cv2.cornerSubPix(lgray, lcorners, (11, 11), (-1, -1), criteria) rcorners cv2.cornerSubPix(rgray, rcorners, (11, 11), (-1, -1), criteria) print(fOK: {lpath} 角点数{len(lcorners)})逻辑说明findChessboardCorners只做粗定位cornerSubPix才把角点压到亚像素级窗口(11,11)是搜索半径太大容易跨到相邻角点太小收敛慢。pattern_size写错是最常见的翻车点9x6的板子内角点是9列6行不是方格数。如果某组图一直检测不到先看是不是标定板反光或者倾斜超过60度。2.3 单目标定内参与畸变怎么解单目标定用calibrateCamera输入是角点的世界坐标和图像坐标。世界坐标按方格实际尺寸生成比如方格25mm那坐标就是(i*25, j*25, 0)。标定完看两个指标重投影误差和畸变系数。重投影误差一般要小于0.3像素超过0.5说明采集有问题。畸变系数k1、k2如果绝对值大于0.5说明镜头畸变很大要考虑换模型。# 生成世界坐标方格实际尺寸25mm square_size 25.0 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 objpoints [] # 3D点 imgpoints_left [] # 左图2D点 imgpoints_right [] # 右图2D点 # 假设前面已经收集好角点这里直接调用标定 ret_l, mtx_l, dist_l, rvecs_l, tvecs_l cv2.calibrateCamera( objpoints, imgpoints_left, lgray.shape[::-1], None, None) ret_r, mtx_r, dist_r, rvecs_r, tvecs_r cv2.calibrateCamera( objpoints, imgpoints_right, rgray.shape[::-1], None, None) print(左相机重投影误差:, ret_l) print(右相机重投影误差:, ret_r) print(左内参:\n, mtx_l) print(左畸变:, dist_l.ravel())参数说明calibrateCamera返回的ret是平均重投影误差单位像素。mtx是3x3内参矩阵dist是畸变向量顺序是k1,k2,p1,p2,k3。如果误差大优先检查角点顺序是否一致左右图角点起点必须对应同一个物理角点否则双目标定必崩。3. 双目标定与立体校正的完整落地3.1 stereoCalibrate外参和本征矩阵一次解出双目标定用stereoCalibrate它同时优化左右内参、畸变、旋转矩阵R、平移向量T、本征矩阵E和基础矩阵F。实际工程里我一般固定单目标定得到的内参只优化外参这样更稳。标志位用CALIB_FIX_INTRINSIC。如果两个相机不是同一型号内参差异大那就放开一起优化但要多拍图。# 双目标定固定内参 flags cv2.CALIB_FIX_INTRINSIC criteria_stereo (cv2.TERM_CRITERIA_EPS cv2.TERM_CRITERIA_MAX_ITER, 100, 1e-5) ret_stereo, mtx_l, dist_l, mtx_r, dist_r, R, T, E, F cv2.stereoCalibrate( objpoints, imgpoints_left, imgpoints_right, mtx_l, dist_l, mtx_r, dist_r, lgray.shape[::-1], criteriacriteria_stereo, flagsflags) print(双目标定重投影误差:, ret_stereo) print(旋转矩阵R:\n, R) print(平移向量T:\n, T) print(基线长度(mm):, np.linalg.norm(T))逻辑说明T的模就是基线长度单位跟世界坐标一致。R和T描述的是右相机相对左相机的位姿。ret_stereo一般要求小于0.5像素超过1.0说明外参没解好常见原因是左右图角点配对错位或者同步没做好。3.2 stereoRectify把两个视角掰成平行校正用stereoRectify计算校正变换矩阵再用initUndistortRectifyMap生成映射表最后remap得到校正图。校正后左右图的极线是水平对齐的同名点在同一行。这一步做完才能用StereoSGBM算视差。# 立体校正 img_size lgray.shape[::-1] R1, R2, P1, P2, Q, roi1, roi2 cv2.stereoRectify( mtx_l, dist_l, mtx_r, dist_r, img_size, R, T, alpha0, # 0表示裁剪掉无效区域1保留全部 flagscv2.CALIB_ZERO_DISPARITY) # 生成映射表 map1_l, map2_l cv2.initUndistortRectifyMap( mtx_l, dist_l, R1, P1, img_size, cv2.CV_16SC2) map1_r, map2_r cv2.initUndistortRectifyMap( mtx_r, dist_r, R2, P2, img_size, cv2.CV_16SC2) # 应用校正 left_rect cv2.remap(limg, map1_l, map2_l, cv2.INTER_LINEAR) right_rect cv2.remap(rimg, map1_r, map2_r, cv2.INTER_LINEAR) cv2.imwrite(left_rect.png, left_rect) cv2.imwrite(right_rect.png, right_rect)参数说明alpha0会裁掉校正后黑边适合做视差alpha1保留全部像素但黑边多。CALIB_ZERO_DISPARITY让主点在校正后保持一致视差计算更准。Q是视差转3D的矩阵后面reprojectImageTo3D要用。校正完一定要肉眼检查在左右图上画同一行水平线看对应点是否在同一行差超过1像素就说明标定有问题。3.3 视差计算与深度验证校正完用StereoSGBM算视差再通过Q转成三维点。验证方法很简单放一个已知距离的物体看算出来的深度和实际差多少。室内1米距离误差控制在1%到2%算合格。# SGBM参数按图像分辨率和纹理调整 window_size 5 min_disp 0 num_disp 16 * 5 # 必须是16的倍数 stereo cv2.StereoSGBM_create( minDisparitymin_disp, numDisparitiesnum_disp, blockSizewindow_size, P18 * 3 * window_size ** 2, P232 * 3 * window_size ** 2, disp12MaxDiff1, uniquenessRatio10, speckleWindowSize100, speckleRange32, preFilterCap63, modecv2.STEREO_SGBM_MODE_SGBM_3WAY) disparity stereo.compute(left_rect, right_rect).astype(np.float32) / 16.0 # 转三维 points_3d cv2.reprojectImageTo3D(disparity, Q) # 取中心点深度 h, w disparity.shape center_depth points_3d[h//2, w//2, 2] print(中心点深度(mm):, center_depth)逻辑说明numDisparities决定最大测距范围16的倍数太小远处算不出太大耗时且噪点多。blockSize奇数5或7适合纹理丰富场景11以上适合弱纹理但边缘会糊。uniquenessRatio越大越严格10到15比较稳。speckleWindowSize过滤小噪点100是常用值。视差图里黑色区域是无效值通常因为遮挡或弱纹理。4. 避坑与排查标定校正里最容易翻车的五件事4.1 角点检测全过但重投影误差巨大现象findChessboardCorners每组都返回True但calibrateCamera误差超过2像素。原因角点顺序在左右图或不同组之间不一致比如某组图棋盘格旋转了180度角点起点跑到对角。解决统一标定板摆放方向或者用drawChessboardCorners把角点画出来逐组肉眼检查发现顺序反了就旋转图像重新检测。4.2 校正后极线还是歪的现象stereoRectify跑完画水平线看左右图对应点不在同一行。原因双目标定时左右角点没有严格配对或者采集时相机有运动导致外参不准。解决重新采集静态图确保每组左右图是同一时刻检查stereoCalibrate的ret_stereo大于1就重标校正后可以用cv2.computeCorrespondEpilines画极线验证。4.3 视差图大面积黑色现象StereoSGBM输出几乎全黑只有零星亮点。原因numDisparities设太小实际视差超出范围或者校正图本身没对齐。解决先看校正图是否水平对齐再逐步增大numDisparities到图像宽度的1/8到1/4弱纹理场景先做直方图均衡或加纹理投影。4.4 深度值跳变严重现象同一平面深度值忽大忽小噪点多。原因blockSize太小uniquenessRatio太低或者光照不均导致匹配歧义。解决增大blockSize到7或9提高uniquenessRatio到15加speckleWindowSize过滤硬件上改善光照避免高光。4.5 基线长度和实际不符现象T的模和卷尺量的基线差很多。原因世界坐标方格尺寸写错比如实际25mm写成20mm导致尺度整体缩放。解决用卡尺量打印后的方格实际尺寸重新生成objp标定板不要缩放打印必须1:1。5. 进阶技巧用重投影误差热力图定位问题组标定完别急着收工我习惯做一张重投影误差热力图把每组图的误差可视化一眼看出哪组拖后腿。方法是用projectPoints把世界坐标投到图像上和检测到的角点算欧氏距离再按组画柱状图。误差大的组直接剔除重标通常能降30%以上。# 计算每组重投影误差 errors [] for i in range(len(objpoints)): imgpoints_proj, _ cv2.projectPoints( objpoints[i], rvecs_l[i], tvecs_l[i], mtx_l, dist_l) error cv2.norm(imgpoints_left[i], imgpoints_proj, cv2.NORM_L2) / len(imgpoints_proj) errors.append(error) # 打印并标记超过阈值的组 threshold 0.5 for i, e in enumerate(errors): flag -- 剔除 if e threshold else print(f组{i}: 误差{e:.3f}{flag})逻辑说明projectPoints用标定得到的外参和内参把3D点投回图像cv2.norm算L2距离再除以角点数得到平均像素误差。阈值0.5是经验值超过就说明这组图角点或位姿有问题。剔除后重新标定通常ret会明显下降。另一个技巧是校正后做极线对齐检查在左图随机取点用Q和视差反算右图对应行看是否和实际匹配。如果偏差大说明stereoRectify的alpha或主点没对齐。我一般会把alpha设0然后检查roi1和roi2确保有效区域一致。最后说个血泪经验标定不是一次性的温度变化、镜头轻微松动都会让外参漂移。我习惯每三个月复标一次或者用在线自标定做微调。双目这套东西标定稳了后面视差和深度就是水到渠成。希望帮到你。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑