简介:相机标定工具库面向计算机视觉与机器人视觉开发者,提供完整的单目内参标定与双目立体标定方案,覆盖针孔(Pinhole)、Kannala-Brandt、MEI、Scaramuzza四种主流相机模型,适用于普通、广角及鱼眼镜头;同时内置棋盘格、圆形网格、非对称圆形网格、ArUco四类标定板检测与采样功能,用户可针对不同镜头畸变程度自由组合比对。压缩包共225个文件,以jpg标定图像样本、h/cc工程源码、yaml/yml配置文件、pdf/md说明文档为主,另含py辅助脚本、svg示意图与docx/json附加材料,整体约24.14MB。源码对各相机模型均有对应实现,并提供单目与双目标定的可执行示例,帮助读者理解畸变校正、内参矩阵估计和双目外参解算的完整流程,降低从零搭建标定环境与调试算法的成本。目前已有102人学习下载,适合相关研究方向的学生、工程师与研究团队直接参考复用。
1. 相机内参标定工具库:为什么需要四种模型和四种标定板
把镜头对准棋盘格拍几十张照片,跑一遍标定,内参就出来了。这套流程对针孔相机和普通镜头够用,但一旦遇到视场角超过 120° 的鱼眼镜头,或者折反射全景相机,标准棋盘格标定就开始翻车——角点检测大面积失败,重投影误差虚高,最后解出来的畸变系数完全是玄学。换到户外强光场景,棋盘格反光,圆形网格被阴影切断,连板都认不出来。这也是相机内参标定和双目标定工具库这类项目存在的意义:把针孔、KannalaBrandt、MEI、Scaramuzza 四种相机模型,以及棋盘格、圆形网格、非对称圆形网格、ArUco 四种标定板的检测与求解逻辑统一封装,让单目内参、双目外参、立体校正参数一次给全。适合正在做视觉检测、机器人抓取、三维重建或双目测距的从业者,尤其是那些已经发现 OpenCV 自带例程压不住鱼眼和户外场景的人。
2. 四种相机模型的选型与参数:针孔、KannalaBrandt、MEI、Scaramuzza 怎么接入
模型选错,后面所有标定参数都是白算。工具库把四种模型统一成同一个标定入口,本质上是把投影函数、畸变模型、去畸变映射三块分开实现。选型时先看镜头类型和视场角,再看需要输出什么参数。
2.1 针孔模型:fx、fy、cx、cy 和畸变系数怎么用
针孔模型适合视场角小于 60° 到 90° 的普通工业镜头和短焦镜头。投影关系就是 u = fx * X/Z + cx,v = fy * Y/Z + cy,畸变用 k1、k2、p1、p2、k3 这五个系数描述。OpenCV 的 cv2.calibrateCamera 默认就是这套模型,工具库通常也把它作为默认值。
用工具库时,针孔模型需要关注的是畸变系数的阶数选择。很多封装默认只解 k1、k2、p1、p2,如果镜头畸变比较大,比如广角工业镜头,就需要把 k3 也放开。
# 工具库中针孔模型的标定配置示例(伪代码) calib_config = { "camera_model": "pinhole", "image_size": [2448, 2048], "distortion": { "k1": 0.0, "k2": 0.0, "p1": 0.0, "p2": 0.0, "k3": 0.0 }, "fix_principal_point": False, "fix_aspect_ratio": False }上面的配置里,fix_principal_point 和 fix_aspect_ratio 是求解时的约束开关。如果相机 CMOS 安装偏差不大,可以保持 False 让求解器自由优化;如果图像是从视频流里裁剪出来的,主点 cx、cy 的初值就必须固定,否则标定结果会漂。这里建议第一次标定全部放开,看结果再决定锁哪些参数。
2.2 KannalaBrandt 模型:大视场角下的投影与畸变多项式
鱼眼镜头视场角在 100° 到 190° 之间时,针孔模型会把边缘像素点拉到无限远,畸变系数根本收敛不了。KannalaBrandt 模型是鱼眼相机的通用选择,它把畸变建模成入射角的多项式,投影公式变成 r = f * θ_d,其中 θ_d = θ * (1 + k1 * θ² + k2 * θ⁴ + k3 * θ⁶ + k4 * θ⁸)。
工具库接入 KannalaBrandt 时,要注意两个参数组:相机内参仍然是 fx、fy、cx、cy,畸变系数变成 k1、k2、k3、k4,而不是针孔模型的 k1、k2、p1、p2。有些封装会把 p1、p2 也带进来,那是兼容性做法,实际求解时应固定为 0,否则参数耦合严重。
# KannalaBrandt 模型配置 calib_config = { "camera_model": "kannala_brandt", "image_size": [1920, 1080], "distortion": { "k1": 0.0, "k2": 0.0, "k3": 0.0, "k4": 0.0 }, "fix_principal_point": False, "board_height_mm": 25.0 # 标定板格子尺寸,用于尺度统一 }KannalaBrandt 模型对标定板图像的覆盖角度要求很高。用棋盘格时,至少要保证标定板出现在画面边缘位置,否则 k3、k4 根本没有约束,解出来等于随机值。我一般会让标定板出现在图像的四个角落各拍 5 到 8 张,再加中心区域的 10 张左右。
2.3 MEI 与 Scaramuzza 模型:折反射相机和全向相机的接入差异
MEI 模型是中心折反射相机模型,典型硬件是一个摄像头对着曲面反射镜,比如俯视全景环视系统。它比针孔模型多一个 xi 参数,描述镜面偏心距,投影公式是先把三维点绕镜面反射,再投影到成像平面。Scaramuzza 模型则更直白,直接用多项式拟合入射光线到像素的映射关系,适合超广角全向相机,参数是多项式系数。
工具库处理这两种模型时,最大的坑在于初值。KannalaBrandt 可以从针孔模型的结果初始化,MEI 和 Scaramuzza 如果初值乱给,非线性优化很容易掉进局部极小值。我的一般做法是先用工具库自带的自动初始化跑一遍,观察输出的 xi 或多项式系数是否在合理范围,比如 MEI 的 xi 一般在 0.5 到 1.5 之间,超出这个区间基本是初值问题。
# MEI 模型配置 calib_config = { "camera_model": "mei", "image_size": [1280, 960], "xi": 1.0, # 镜面偏心距初始值,后续参与优化 "distortion": { "k1": 0.0, "k2": 0.0, "p1": 0.0, "p2": 0.0 }, "fix_xi": False }这里 fix_xi 建议保持 False,让 xi 参与优化。只有当镜头的反射镜是严格定制的工业件、偏心距已知时,才固定 xi。注意 MEI 模型对圆形网格的检测稳定性更好,因为折反射图像里棋盘格直线被严重弯曲,角点检测容易把亚像素位置算偏。
3. 四种标定板的检测差异:棋盘格、圆形网格、非对称圆形网格、ArUco 怎么选
标定板的选择直接影响检测鲁棒性和标定精度。工具库把这四种板统一封装成 board 配置项,内部对应不同的检测器。实际部署时,很多人只盯着模型选型,忽略了标定板类型对结果的影响,这是最容易翻车的地方。
3.1 棋盘格:亚像素角点检测与方向二义性
棋盘格是最常见的标定板,角点响应强,亚像素精度高。工具库内部一般用 cv2.findChessboardCorners 检测,再叠加 cornerSubPix 细化。棋盘格的问题在于对称性导致方向二义性——旋转 180° 后无法区分,输出角点顺序可能反转。这在单目标定里问题不大,因为单目只需要角点坐标;但双目标定时,左右图必须用同一角点索引,方向不一致会把外参解成镜像。
解决方式通常是让板的不对称特征不唯一。更稳妥的做法是使用 asymmetric circles grid 或 ArUco 板,它们自带方向信息。如果只能用棋盘格做双目,注意保证标定板在整个采集过程中始终以同一个方向出现在画面里,不要出现倒置。
3.2 圆形网格与非对称圆形网格:blob 检测的参数差异
圆形网格用圆斑中心作为特征点,对模糊和离焦比棋盘格更鲁棒,但中心定位精度受 blob 检测参数影响大。cv2.findCirclesGrid 默认用 SimpleBlobDetector,需要调节 blob 的面积阈值和圆度阈值,不然反光或阴影会把一个圆拆成两个。
非对称圆形网格每组圆之间行数不一样,feature 点坐标不具备对称性,标定求解时能提供更强的几何约束。工具库里的非对称圆网格检测,一般也是走 findCirclesGrid,只改 patternSize 参数。
# 非对称圆形网格检测配置 grid_config = { "board_type": "asymmetric_circles", "pattern_size": [4, 11], # 内交点行列数,注意行列顺序 "circle_diameter_mm": 12.0, "spacing_mm": 20.0, "blob_params": { "min_area": 50, # 过小会丢失暗部圆点 "max_area": 500, "min_circularity": 0.7 } }patternSize 的行列顺序是实际检测时最容易错的参数。非对称圆形网格标定板厂商一般标成 rows × cols,但 OpenCV 要求 patternSize 传内交点数量,而且行列必须与实际图像中的检测顺序一致。建议一开始打印板时就把这个参数记录在板背面,别靠记忆。
3.3 ArUco 板:遮拦下的检测鲁棒性与字典选择
ArUco 板由单个 ArUco 码组成网格,每个码自带 ID 和方向信息,即使部分码被遮挡或反光丢失,剩余码也能提供足够的对应点。工具库支持 ArUco 板时,一般用的是 GridBoard 结构,需要指定字典、码的数量、码的大小和间距。
ArUco 板标定的精度上限略低于棋盘格,因为码角点是通过码的四个角定位的,亚像素精度不如棋盘格角点。但它有两个无可替代的优势:一是自带方向,双目标定不用操心左右图顺序;二是在遮挡场景下能拿部分点继续标定,适合现场环境杂乱的生产线。
# ArUco 板配置 aruco_config = { "board_type": "aruco_grid", "dict_id": 6, # DICT_6X6_250 "marker_size_mm": 30.0, "marker_spacing_mm": 5.0, "grid_size": [5, 7], # 横向 5 个码,纵向 7 个码 "detector_params": { "adaptiveThreshWinSizeMin": 3, "adaptiveThreshWinSizeMax": 23, "adaptiveThreshWinSizeStep": 5 } }aruco_dict 的选择要注意生成板和检测时保持一致。DICT_4X4 码小、密度高,但误检率稍高;DICT_6X6 码面积大,远距离和低光照下更容易检测。如果标定距离变化大,建议选 DICT_6X6 或 DICT_5X5,并把 adaptiveThreshWinSize 的窗口范围放大,避免强反光把白边扫成黑的。
4. 双目标定流程:从数据采集到立体校正的完整参数设置
单目标定只解决每个相机自己的内参,双目系统还要知道两个相机之间的旋转和平移。工具库通常把内参标定、双目标定、立体校正三段流程串成一个管线。工具库部署时,最常被问的问题就是“拍了 100 张图,为什么外参还是不对”。
4.1 双目采集规范:覆盖范围、姿态变化与最少图像对数
双目标定对数据采集的要求远高于单目。左右相机必须同步采集同一块板,不能分时拍。覆盖范围要包括近处、远处、左边缘、右边缘、上下边缘,姿态要有倾斜、旋转变化。最少 20 对有效图像,实际建议 30 到 40 对。
一条常见的反面经验:把标定板对着相机正着拍 30 张,结果外参精度稀烂。棋盘格在正对视角下提供的几何约束高度相似,旋转分量根本没被激励出来。正确做法是每拍 5 张就换一个姿态,保证标定板平面与相机光轴夹角在 30° 到 60° 之间交替变化。
4.2 stereoCalibrate 的参数组合:flags 怎么选、RMS 怎么判
工具库内部走 cv2.stereoCalibrate 时,flags 的设置取决于单目内参是否已经单独标定。如果两个相机的内参已经在同一工具库里标定过,就用 CALIB_FIX_INTRINSIC,只优化外参;如果内参不算数,需要联合优化,用 CALIB_USE_INTRINSIC_GUESS。
RMS 值是最容易误判的指标。很多人看到 RMS 小于 0.3 就觉得标定成功,实际上 RMS 只反映标定板角点的重投影误差。如果标定板本身不平整,比如打印在相纸上没粘到玻璃板上,角点位置存在系统误差,RMS 可能很低但真实精度很差。
# 双目标定核心调用 rms, K1, D1, K2, D2, R, T, E, F = cv2.stereoCalibrate( object_points, # 世界坐标点列表 image_points_l, # 左相机角点 image_points_r, # 右相机角点 camera_matrix_1, # 左相机内参初值 dist_coeffs_1, camera_matrix_2, # 右相机内参初值 dist_coeffs_2, image_size, criteria=(cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 100, 1e-5), flags=cv2.CALIB_FIX_INTRINSIC )这里 R 是右相机相对左相机的旋转矩阵,T 是平移向量。使用 CALIB_FIX_INTRINSIC 时,K1、D1、K2、D2 不会被修改,只优化 R、T、E、F。如果两目内参来自不同分辨率或不同镜头,不能直接混用。
4.3 立体校正与重投影误差:从 stereoRectify 到 Q 矩阵
双目标定完成后,工具库会生成 stereoRectify 所需的映射参数。stereoRectify 输出左右相机的行对准旋转矩阵 R1、R2,以及投影矩阵 P1、P2。随后用 initUndistortRectifyMap 生成查找表,交给 remap 执行校正。
判断双目标定是否成功,核心指标是极线误差。校正后同一特征点在左右图中的行坐标差应小于 0.5 像素。如果行差达到 2 像素以上,先检查采集阶段左右图是否同步,再检查是否使用了不同的标定板方向。
R1, R2, P1, P2, Q, roi1, roi2 = cv2.stereoRectify( K1, D1, K2, D2, image_size, R, T, flags=cv2.CALIB_ZERO_DISPARITY, alpha=0.0 # 0 表示裁剪掉校正后无内容的黑边,1 表示保留全部像素 )alpha 参数直接影响校正后图像的裁剪范围。alpha = 0.0 时,校正图边缘会出现大量黑色区域,但像素利用率高;alpha = 1.0 时保留所有原始像素,但有效视差范围变小。实际项目中我一般先用 0.0 确认极线对准,再根据具体算力调整 alpha。注意校正后的内参是 P1 和 P2,不要再使用原始 K1、K2 做测距。
5. 标定避坑:三个高频翻车现场与排查路径
这一章写工具库落地时最容易踩的坑。每一条都是现场真实遇到过、且反复出现的问题,按现象、原因、解决三节记录。
5.1 图像采集类问题:光照过曝、运动模糊、板不在焦平面
现象一:棋盘格标定板在户外阳光直射下,白色格子过曝,角点检测数量不足,findChessboardCorners 返回失败。
原因是标定板表面反射率过高,相机自动曝光把白格拉到饱和,黑白边界被白场吞掉。解决方式是切换到手动曝光,把曝光时间调低到白格不过曝、黑格仍然可分辨的程度;如果是在生产线上固定安装,直接用偏振片贴在镜头前,消除镜面反光。
现象二:手持标定板采集时偶发运动模糊,角点出现重影,但检测器仍然返回成功。
原因是检测器对轻微模糊不敏感,亚像素细化后角点位置被拉偏。解决方式是在工具库的数据采集环节加入清晰度评分,用拉普拉斯方差过滤低于阈值的图像。
import cv2 import numpy as np def check_sharpness(image, threshold=60.0): gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY) laplacian_var = cv2.Laplacian(gray, cv2.CV_64F).var() return laplacian_var > threshold这个函数放在采集循环里,评分不达标的帧直接丢弃。Laplacian 方差阈值 60 是个经验值,分辨率越高阈值可以适当放大。不要用自动对焦镜头做标定,多次测量中焦距稍微变化,标定结果就全废了。
现象三:标定板平面与相机光轴夹角太大,超过 70°,板边缘的圆形网格被压成椭圆,blob 检测把边缘圆点漏掉。
原因是 findCirclesGrid 的 blob 检测基于圆度阈值,大角度下圆点投影椭圆度超标。解决方式是控制标定板倾角在 50° 以内,超大角度图像单独用 ArUco 板。
5.2 求解类问题:RMS 虚低、畸变参数不收敛、模型与镜头不匹配
现象一:重投影 RMS 只有 0.2 像素,但去畸变后的图像边缘直线仍然弯曲。
原因是 RMS 只统计了标定板覆盖区域的角点误差,边缘区域没有角点参与优化,畸变系数外推时失真。解决方式是采集时让标定板尽可能接近图像边缘,尤其是四个角落,否则边缘畸变根本没有约束。工具库支持的话,画出去畸变后的直线叠加图,肉眼判断边缘弯曲是否消失。
现象二:KannalaBrandt 模型标定结果每次都不一样,k3、k4 跳动幅度大。
原因是参与优化的图像中,标定板没有覆盖大视角区域,鱼眼镜头边缘的畸变信息缺失。解决方式是增加板贴近图像边缘的帧数,至少保证 40% 的图像包含边缘特征点。还有一个隐藏原因:初始畸变系数全为零时,优化会陷在局部极小。先把 k1 设成 -0.05、k2 设成 0.05 再跑,能加速收敛。
现象三:折反射镜头用针孔模型标定,结果 RMS 看着不高,但三维重建完全不对。
原因是模型与镜头物理结构不匹配。MEI 模型里 xi 描述的是镜面反射关系,用针孔模型硬拟合相当于用一个错误投影函数去逼近,内参和畸变系数互相补偿,数值自洽但物理意义失真。解决方式是回到镜头参数表确认视场角,超过 100° 直接选 KannalaBrandt,折反射镜选 MEI 或 Scaramuzza。
5.3 工程类问题:分辨率变更、自动曝光、不同板混用
现象一:标定完成后变更图像分辨率,内参没有按比例缩放,去畸变图变形。
原因是一些相机通过裁剪 ROI 实现不同分辨率输出,分辨率变化后 cx、cy 不再居中,fx、fy 也不成比例。解决方式是工具库输出内参时附带 image_size 字段,每次切换分辨率都重新标定,或者只使用相机原生分辨率。
现象二:相机开启自动增益和自动曝光,亮度波动导致同一块 ArUco 板有时检测到全部码、有时只检测到一半。
原因是增益变化改变了二值化阈值分布,ArUco 检测器的自适应阈值窗口没跟上。解决方式是标定期间固定曝光、固定增益,生产环境无法固定时,把 detector_params 里的 adaptiveThreshWinSizeMin、Max、Step 设成更宽的区间。
现象三:一张采集数据里混用了两种标定板,部分帧用棋盘格,部分帧用圆形网格,工具库报错或者外参跳变。
原因是不同标定板的特征点数量、世界坐标定义完全不同,混合输入等于把不同约束体系的数据丢进同一个非线性优化。解决方式是一次采集只允许一种板,板类型由配置锁定,不运行时切换。
6. 用 ArUco 板验证双目标定:一条可复现的极线校验套路
标定结束不等于工作结束。我习惯在每次双目标定后,用一块 ArUco 板做一次独立的极线校验。ArUco 板自带 ID 和方向,不需要依赖左右图角点顺序一致,验证过程天然适合自动化。
校验方法不复杂:把 ArUco 板放在视野中心、左边缘、右边缘、远处、近处各采集几帧,对每帧检测左右图中所有码的角点,然后根据校正后的 P1、P2 计算重投影行坐标差。
# 极线校验脚本骨架 left = cv2.imread("left.png") right = cv2.imread("right.png") left_rect = cv2.remap(left, map1_l, map2_l, cv2.INTER_LINEAR) right_rect = cv2.remap(right, map1_r, map2_r, cv2.INTER_LINEAR) # 检测左右图中的 ArUco 角点 corners_l, ids_l, _ = aruco_detector.detectMarkers(left_rect) corners_r, ids_r, _ = aruco_detector.detectMarkers(right_rect) # 按 ID 匹配角点,计算行差 valid = 0 max_row_diff = 0.0 for id_ in set(ids_l.flatten()) & set(ids_r.flatten()): idx_l = ids_l.flatten().tolist().index(id_) idx_r = ids_r.flatten().tolist().index(id_) pts_l = corners_l[idx_l].reshape(-1, 2) pts_r = corners_r[idx_r].reshape(-1, 2) row_diff = np.abs(pts_l[:, 1] - pts_r[:, 1]) max_row_diff = max(max_row_diff, row_diff.max()) valid += len(pts_l)校验的通过标准是最大行差小于 1 像素,平均行差小于 0.5 像素。如果 ArUco 板的码数足够多,这个结果基本能代表整个画面的极线对齐水平。不建议用棋盘格做这个验证,因为棋盘格对角点顺序依赖强,稍有不慎会把顺序错误当成极线误差,反而误判。
关于行差偏大的处理,先确认 remap 用的映射表是基于哪一组内参生成的。如果 remap 用了原始 K1,而 P1 已经变化,那行差偏大是必然的。这个错误我在现场帮人排查过很多次,常见于代码里混用了两套内参变量。把 mapping 的生成和内参加载封装在同一个函数里,能避免这类手误。
标定结果的记录也值得养成习惯。我会把每次标定的配置、RMS、极线误差、图像分辨率、镜头型号写进一个 JSON 文件,下次换镜头、换分辨率时直接对比,几分钟就能判断新标定是否异常。这个习惯帮我避免了很多重复返工,也更容易说服产线同事接受标定结果。
以上是这套工具库落地的主要路径。每个模型的参数含义、每种板的检测限制、双目流程的校验方式,都值得在项目里单独验证一遍。希望帮到你。
本文还有配套的精品资源,点击获取