
简介这份资源面向计算机、人工智能、通信、物联网等专业的在校学生与教师提供基于Python和OpenCV的单目与双目相机标定完整源码可用于课程设计、毕业设计、大作业或项目立项演示。压缩包共8个文件以4个py脚本为核心分别实现单目标定与双目立体标定流程另附2个txt与2个md说明文档交代运行环境、参数配置与使用注意事项整体约25KB轻量易部署。目前已有436人学习下载具备一定参考热度。读者可借此理解相机内参、畸变系数求解、棋盘格角点检测、双目标定与立体校正等关键环节并对照说明文档快速跑通程序、排查常见报错。代码结构清晰、功能验证稳定既适合小白入门进阶也便于有基础的开发者二次开发扩展测距、三维重建等应用。1. 相机标定程序到底在算什么从一张棋盘格到两组相机参数你拿到的这个「相机标定程序-基于Python和OpenCV实现单目相机标定和双目相机标定源码.zip」本质是一套用 Python 和 OpenCV 把相机内外参算出来的脚本集合。单目部分解决的是「一台相机自己的内参和畸变系数是多少」双目部分解决的是「左右两台相机之间的旋转和平移关系是什么」。这两件事听起来抽象但落到工程里非常具体你拍一组棋盘格照片程序检测角点解算出焦距、主点、畸变系数双目再多一步立体标定输出 R 和 T。适合谁做机器人视觉、三维重建、测距、SLAM 前端的从业者以及刚学 OpenCV 想跑通完整标定流程的新手。很多人第一次标定完发现重投影误差 0.5 像素以上或者双目测距飘得离谱问题往往不在算法而在拍摄和参数设置。这篇就按「单目怎么跑通、双目怎么对齐、坑在哪」的顺序讲清楚。2. 单目相机标定从拍棋盘格到拿到内参矩阵2.1 为什么单目标定必须先理解针孔模型和畸变模型单目标定的数学基础是针孔成像模型。三维点经过相机外参旋转 R、平移 t变到相机坐标系再经过内参矩阵 K 投影到像素平面。K 里有四个关键量fx、fy 是焦距在 x、y 方向的像素表示cx、cy 是主点通常接近图像中心但不完全等于中心。畸变分径向和切向OpenCV 用 k1、k2、p1、p2、k3 五个系数描述鱼眼或广角镜头 k3 不能省。为什么强调这个因为很多人标定完只看重投影误差不看内参是否合理。比如 fx 和 fy 差出 10% 以上或者 cx 偏离图像中心超过 50 像素基本可以判断拍摄或角点检测有问题。常见做法是先用 cv2.findChessboardCorners 粗定位再用 cv2.cornerSubPix 做亚像素细化这一步直接决定后面解算的精度上限。标定板的选择也有讲究。棋盘格最常见但 OpenCV 也支持圆点阵列和 ChArUco。棋盘格对光照和模糊更敏感圆点阵列在远距离更稳ChArUco 适合部分遮挡场景。如果你只是跑通这个源码包棋盘格足够如果做产品级标定建议至少准备两种标定板交叉验证。2.2 用 OpenCV 跑通单目标定的最小代码下面这段代码是单目标定的核心流程可以直接抄。注意棋盘格尺寸要和你实际打印的格子数一致内角点数是行数减一、列数减一。import cv2 import numpy as np import glob # 棋盘格内角点数量例如 9x6 表示每行9个内角点每列6个 CHECKERBOARD (9, 6) criteria (cv2.TERM_CRITERIA_EPS cv2.TERM_CRITERIA_MAX_ITER, 30, 0.001) objpoints [] # 三维世界坐标 imgpoints [] # 二维图像坐标 # 生成棋盘格世界坐标假设标定板在 Z0 平面 objp np.zeros((CHECKERBOARD[0] * CHECKERBOARD[1], 3), np.float32) objp[:, :2] np.mgrid[0:CHECKERBOARD[0], 0:CHECKERBOARD[1]].T.reshape(-1, 2) 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, CHECKERBOARD, None) if ret: corners2 cv2.cornerSubPix(gray, corners, (11, 11), (-1, -1), criteria) objpoints.append(objp) imgpoints.append(corners2) cv2.drawChessboardCorners(img, CHECKERBOARD, corners2, ret) cv2.imshow(corners, img) cv2.waitKey(100) ret, mtx, dist, rvecs, tvecs cv2.calibrateCamera( objpoints, imgpoints, gray.shape[::-1], None, None) print(内参矩阵:\n, mtx) print(畸变系数:, dist.ravel()) print(重投影误差:, ret)逻辑说明objp 是棋盘格在真实世界的坐标单位可以当成毫米或任意一致单位因为标定只关心比例。findChessboardCorners 返回角点粗略位置cornerSubPix 用迭代法把角点精度提到亚像素级窗口 11x11 是常见值太小容易受噪声影响太大在畸变严重时反而偏。calibrateCamera 一次性解出内参、畸变和每张图的外参返回值 ret 是平均重投影误差单位像素。参数说明CHECKERBOARD 必须和实际标定板一致写错会导致角点检测失败或错位。criteria 里 30 是最大迭代次数0.001 是精度阈值一般不用改。如果图像分辨率很高比如 4KcornerSubPix 窗口可以适当加大到 15x15。2.3 拍摄阶段决定标定上限12 张以上、多角度、覆盖全画面标定精度七成靠拍摄三成靠算法。血泪经验是很多人随手拍 5 张就跑去标定结果重投影误差 1.5 像素还以为是代码问题。正确做法是拍 12 到 20 张棋盘格在画面里要覆盖四个角和中心倾斜角度至少覆盖 ±30 度远近也要有变化。具体操作把棋盘格固定在硬板上不要用手拿着手抖会造成运动模糊。光照要均匀避免反光尤其是打印纸表面。每拍一张移动一次不要只平移要有旋转。如果相机视野很大建议把棋盘格放在画面边缘也拍几张因为畸变在边缘最明显边缘样本不足会导致畸变系数解算不准。一个容易忽略的点标定板不要完全平行于像平面。完全正对时外参退化焦距解算不稳定。至少要有几张明显倾斜的图。如果你做双目左右相机要同时拍同一块棋盘格且棋盘格要同时完整出现在左右画面里这是后面立体标定的前提。3. 双目相机标定把左右相机拧到同一个坐标系3.1 双目标定和单目标定的关系先各自单标再联合立体标定双目标定不是从零开始而是先分别对左右相机做单目标定拿到各自的内参和畸变再用 cv2.stereoCalibrate 解出右相机相对左相机的旋转 R 和平移 T。R 和 T 就是双目系统的外参后面做极线校正、视差计算、深度恢复都靠它们。为什么不能直接双目标定因为 stereoCalibrate 需要左右相机的内参作为初值如果内参不准R 和 T 也会飘。常见做法是左右各拍一组单目标定图分别标定检查各自重投影误差都小于 0.3 像素再用同一组双目同步图做立体标定。注意双目同步图必须是左右同时拍的不能拿单目图凑。立体标定的输出里R 是 3x3 旋转矩阵T 是 3x1 平移向量。T 的模长就是基线距离单位和你标定板世界坐标单位一致。如果你用毫米T 的模长就是毫米。基线测量可以用卷尺粗验如果差太多说明标定有问题。3.2 双目立体标定代码与极线校正下面代码假设你已经有了左右相机的内参和畸变以及同步拍摄的棋盘格图像对。import cv2 import numpy as np import glob CHECKERBOARD (9, 6) criteria (cv2.TERM_CRITERIA_EPS cv2.TERM_CRITERIA_MAX_ITER, 30, 0.001) objp np.zeros((CHECKERBOARD[0] * CHECKERBOARD[1], 3), np.float32) objp[:, :2] np.mgrid[0:CHECKERBOARD[0], 0:CHECKERBOARD[1]].T.reshape(-1, 2) objpoints [] imgpointsL [] imgpointsR [] left_images sorted(glob.glob(stereo/left/*.jpg)) right_images sorted(glob.glob(stereo/right/*.jpg)) for fL, fR in zip(left_images, right_images): imgL cv2.imread(fL) imgR cv2.imread(fR) grayL cv2.cvtColor(imgL, cv2.COLOR_BGR2GRAY) grayR cv2.cvtColor(imgR, cv2.COLOR_BGR2GRAY) retL, cornersL cv2.findChessboardCorners(grayL, CHECKERBOARD, None) retR, cornersR cv2.findChessboardCorners(grayR, CHECKERBOARD, None) if retL and retR: cornersL cv2.cornerSubPix(grayL, cornersL, (11,11), (-1,-1), criteria) cornersR cv2.cornerSubPix(grayR, cornersR, (11,11), (-1,-1), criteria) objpoints.append(objp) imgpointsL.append(cornersL) imgpointsR.append(cornersR) # 先分别单目标定 retL, mtxL, distL, _, _ cv2.calibrateCamera(objpoints, imgpointsL, grayL.shape[::-1], None, None) retR, mtxR, distR, _, _ cv2.calibrateCamera(objpoints, imgpointsR, grayR.shape[::-1], None, None) # 立体标定 ret, mtxL, distL, mtxR, distR, R, T, E, F cv2.stereoCalibrate( objpoints, imgpointsL, imgpointsR, mtxL, distL, mtxR, distR, grayL.shape[::-1], flagscv2.CALIB_FIX_INTRINSIC) print(基线长度:, np.linalg.norm(T)) print(旋转矩阵 R:\n, R) print(平移向量 T:\n, T) # 极线校正 R1, R2, P1, P2, Q, roi1, roi2 cv2.stereoRectify( mtxL, distL, mtxR, distR, grayL.shape[::-1], R, T, alpha0) print(Q 矩阵:\n, Q)逻辑说明先分别单目标定拿到左右内参再用 stereoCalibrate 联合优化。flags 设为 CALIB_FIX_INTRINSIC 表示固定内参只优化外参这是常见做法因为单目标定已经比较准联合优化内参容易过拟合。stereoRectify 输出 R1、R2 用于把左右图像校正到共面行对齐P1、P2 是新的投影矩阵Q 是视差到深度的重投影矩阵。参数说明alpha0 表示校正后图像只保留有效像素边缘会被裁掉alpha1 保留所有像素但会有黑边。实际工程里常用 alpha0 或 0.5。Q 矩阵后面用 cv2.reprojectImageTo3D 把视差图转成三维点云。基线长度 np.linalg.norm(T) 要和你实际测量的基线对比误差超过 5% 就要检查。3.3 双目同步拍摄的硬性要求和检查方法双目标定最怕左右不同步。如果左右相机是分开触发的运动场景下棋盘格位置不一致标定出来的 R、T 会包含时间差带来的误差。常见做法是用硬件同步触发或者用同一块标定板静止拍摄。如果你只能软触发至少保证拍摄时标定板完全静止。检查方法标定完后用 stereoRectify 校正左右图看同一行上左右角点是否水平对齐。如果对齐误差超过 1 像素说明 R、T 不准。另一个检查是看 Q 矩阵里的基线项Q[2,3] 和 Q[3,2] 应该和 -1/T[0] 相关如果数值异常说明标定有问题。还有一点双目相机的焦距最好一致如果不一致校正后图像缩放会不同影响视差匹配。如果左右相机型号不同建议在 stereoCalibrate 里不要固定内参让算法自己调但这样需要更多样本。4. 标定结果验证与常见翻车排查4.1 重投影误差不是唯一指标看内参合理性、基线一致性、校正对齐重投影误差小于 0.3 像素只能说明角点拟合好不代表标定参数物理正确。我一般会做三个额外检查。第一看 fx 和 fy 的比值正常相机像素是方形fx/fy 应该在 0.95 到 1.05 之间超出说明像元非方形或标定有问题。第二看主点 cx、cy 是否在图像中心附近偏离超过 10% 图像尺寸就要怀疑。第三双目看基线用卷尺量实际基线和 T 的模长对比误差超过 2% 就要重新标。极线校正后的对齐检查最直观。把校正后的左右图叠加看同一物体是否在同一水平线上。如果上下错位明显说明 R 没解准。可以用 OpenCV 的 cv2.computeCorrespondEpilines 画极线正常应该水平且平行。4.2 避坑标定过程中最容易翻车的 5 个点现象一findChessboardCorners 返回 False角点检测失败。原因棋盘格尺寸写错、光照不均、棋盘格太模糊、或者棋盘格周围留白不够。解决先确认内角点数打印时四周留至少一个格子宽的白边拍摄时避免过曝和欠曝模糊就重拍。现象二重投影误差很大超过 1 像素。原因角点检测不准、拍摄样本太少、棋盘格倾斜角度不够、或者有运动模糊。解决增加样本到 15 张以上覆盖画面四角和中心用 cornerSubPix 细化剔除模糊图。现象三双目基线长度和实际差很多。原因左右相机没有同步拍摄或者标定板在左右图中角点顺序不一致。解决用硬件同步或静止拍摄检查左右角点检测结果是否都正确必要时手动翻转角点顺序。现象四校正后图像上下错位。原因R、T 不准或者单目内参误差大。解决先确保单目标定重投影误差小于 0.3 像素再重新立体标定不要固定内参试试。现象五畸变校正后边缘拉伸严重。原因畸变系数解算不准尤其是 k3 被过度拟合。解决如果镜头不是鱼眼可以在 calibrateCamera 里设置 flags 不估 k3或者增加边缘样本。5. 进阶技巧用标定结果做测距和点云以及我的固定习惯标定完不是终点真正要用起来得把参数落到测距和三维重建。单目测距需要已知物体实际尺寸用相似三角形算距离精度受限于像素分辨率和物体尺寸测量误差。双目测距更直接校正后算视差用公式 Z f * B / df 是校正后焦距B 是基线d 是视差。视差精度直接决定测距精度亚像素视差匹配能把误差压到 1% 以内。下面这段代码用校正后的双目图算视差并转点云是常见的落地路径。import cv2 import numpy as np # 假设已经拿到 stereoRectify 的 P1, P2, Q # 读取校正后的左右图 imgL cv2.imread(rectified_left.png, 0) imgR cv2.imread(rectified_right.png, 0) # SGBM 视差匹配 window_size 5 min_disp 0 num_disp 16 * 5 stereo cv2.StereoSGBM_create( minDisparitymin_disp, numDisparitiesnum_disp, blockSizewindow_size, P18 * 3 * window_size ** 2, P232 * 3 * window_size ** 2, disp12MaxDiff1, uniquenessRatio10, speckleWindowSize100, speckleRange32 ) disparity stereo.compute(imgL, imgR).astype(np.float32) / 16.0 points_3d cv2.reprojectImageTo3D(disparity, Q) # 过滤无效视差 mask disparity min_disp points points_3d[mask] print(点云数量:, points.shape[0])逻辑说明SGBM 是半全局匹配参数里 numDisparities 必须是 16 的倍数blockSize 取奇数。P1、P2 控制视差平滑度经验值是 P1 8 * 通道数 * blockSize^2P2 32 * 通道数 * blockSize^2。uniquenessRatio 越大越严格能过滤误匹配。reprojectImageTo3D 用 Q 矩阵把视差转成三维坐标单位和你标定时的世界单位一致。参数说明window_size 小则细节好但噪声大大则平滑但边缘糊。一般 3 到 11 之间调。num_disp 要覆盖最大视差如果场景最近距离 0.5 米基线 0.1 米焦距 500 像素最大视差约 100那 num_disp 至少 112。speckleWindowSize 和 speckleRange 用来去斑点视差图噪声大就调大。我自己的固定习惯是每次标定完先存一份原始参数和重投影误差隔一周再标一次对比两组参数。如果 fx 变化超过 1%说明拍摄或标定流程不稳定得回头查。标定板我一般打印在 5mm 厚的铝板上背面贴绒布避免反光。双目基线用游标卡尺量不靠卷尺。这些细节看起来琐碎但能让你少走很多弯路。希望帮到你。本文还有配套的精品资源点击获取