☰
相机内参标定与双目标定:多模型多标定板工具库落地指南
2026/10/10 5:03:56 网站建设 项目流程

简介:一款面向计算机视觉与摄影测量开发者的相机标定工具库,覆盖单目内参标定与双目相对位姿标定,兼容针孔、Kannala-Brandt、MEI、Scaramuzza等常见相机模型,并集成棋盘格、圆形网格、非对称圆形网格及ArUco多种标定板,能适应广角、鱼眼等镜头场景。资源共225个文件,约24.14MB,包含170张JPG标定示例图,以及覆盖Chessboard、CataCamera、EquidistantCamera、PinholeCamera等典型实现的C++源码(.cc/.h),另有YAML配置文件、Python脚本和PDF/Markdown文档,方便参数配置、批量处理与原理学习,目录分类明确,易于按需查找。目前已有102人学习/下载。无论是科研验证还是工程落地,这套资料都能帮助用户快速选型、配置和调用标定算法,免去从零搭建的重复工作;对于三维重建、机器人视觉和自动驾驶等需要深度信息与高精度姿态的应用,双目标定部分尤其具有实用价值。

1. 相机内参标定与双目标定:多模型多标定板工具库到底解决了什么问题

重投影误差已经压到 0.05 像素,双目测距还是偏了 3 厘米——这种情况我见过不止一次。相机内参标定和双目标定从来不是“跑个脚本、出一个 yaml”那么简单:相机模型选错、标定板选错、采图不规范,任何一环出问题,后面所有工作都会跟着翻车。这个工具库把四种相机模型和四类标定板统一到同一套流程里,选鱼眼、选全向相机、或者换一套标定板,都不需要再重新搭建标定管线。

它的价值不只是“多一个标定工具”,而在于换镜头、换视场角、换深度场景时,标定流程本身不需要推翻重来。对双目相机标定、视觉引导抓取、三维重建这些方向,尤其是手上有多种镜头的团队,这类工具库能省下大量重复调参时间。接下来我从模型选型、标定板选型、单目与双目标定实操、常见翻车场景到验证手段,按落地顺序把关键点讲透。

2. 四种相机模型怎么选:针孔、KannalaBrandt、MEI、Scaramuzza 的适用边界

模型选错的代价很大,畸变模型直接决定了内参矩阵那一堆数字的真实含义。同一组标定图,用不同模型解出来的内参可能差出百分之几,而这个差异会在后续双目校正和测距中被放大。

2.1 针孔模型与 KannalaBrandt 模型:普通镜头和鱼眼的分界线

针孔模型用 4 个内参 fx fy cx cy 加畸变项 k1 k2 p1 p2 k3 描述。fx fy 的单位是像素,和传感器尺寸、镜头焦距耦合;cx cy 的理想值是图像中心,但实际镜头装配误差会让它漂移几十像素。针孔模型在 FOV 60 度以内的普通镜头上表现稳定,一旦超出,畸变系数会越来越大,内参矩阵也跟着失真。

KannalaBrandt 是鱼眼镜头的惯用模型,它用等距投影假设和 θ 的奇次多项式去拟合入射角到像平面半径的映射:

r = f * (θ + k1θ³ + k2θ⁵ + k3θ⁷ + k4θ⁹)

这个式子把畸变和投影放在同一个式子里拟合,鱼眼镜头边缘那些夸张拉伸能被多项式吃掉。实测中,FOV 超过 100 度时用针孔模型标定的可重复性很差,即使重投影误差显示 0.1 像素,去畸变后边缘直线还是会弯。KannalaBrandt 在 100 到 190 度范围内能保持稳定。如果你的镜头 FOV 在 90 度附近,两种模型都跑一遍,比较边缘直线的拟合程度再定。

这里有个容易踩的坑:KannalaBrandt 通常没有单独的切向畸变项,因为鱼眼的主点偏移和切向畸变会被多项式吸收。强行加上 p1 p2 只会让参数相互补偿,得到一组“能标定但换场景就飘”的参数。

2.2 MEI 与 Scaramuzza 模型:全向相机场景下的差异

MEI 模型是给全向相机准备的。它假设光线先投影到一个单位球面上,再经过一个偏移量 ξ 的透视投影映射到平面。这个 ξ 参数描述镜面或镜头的光学中心偏移,在很多工具库里叫 xi 或者 mirror parameter,典型值在 0 到 1 之间。MEI 适合折反射相机、超广角猫眼镜头这类视场角接近或超过 190 度的设备。

Scaramuzza 走了另一条路——不显式建模投影几何,直接用多项式近似入射光线方向与像点半径的关系。它比 MEI 更“黑匣子”,但对某些特殊折射设计的镜头反而拟合得更好。这两种都需要极广的视野,标定板要放到非常近的位置才能覆盖到边缘,采图难度比普通镜头高不少。全向相机标定时,标定板的姿态覆盖比数量更重要。

2.3 模型选型决策表与参数含义

相机模型适用镜头建议 FOV典型参数
针孔普通工业镜头、手机主摄< 60°fx fy cx cy + k1 k2 p1 p2 k3
KannalaBrandt鱼眼、广角100°~190°fx fy cx cy + k1 k2 k3 k4
MEI全向、折反射> 180°fx fy cx cy + xi + 畸变项
Scaramuzza极端全向、特殊折射> 190°中心坐标 c + 多项式系数

参数选择上,fx fy 只和分辨率及镜头光学有关,换标定板不影响;cx cy 不要手动设为 w/2 h/2,让优化器去拟合,装配误差会让它真实值偏离中心几十像素。如果两个模型标出的 fx 相差超过 5%,优先怀疑采图覆盖不全,而不是模型差异。另外,同一颗镜头在不同分辨率下需要重新标定,不能直接用上一位同事留下的参数。

3. 标定板选型与采集:棋盘格、圆形网格、非对称圆形网格、ArUco 怎么挑

模型决定上限,标定板决定实际能达到多少。很多团队固定用一种板子,换到鱼眼镜头就发现检测率骤降,这不是算法坏了,是板子类型和镜头不匹配。

3.1 棋盘格与非对称圆形网格:精度与鲁棒性的取舍

棋盘格的好处是角点检测极其稳定,亚像素优化能到 0.1 像素以内,是默认首选。坏处也明显:要求角点全部可见,部分遮挡直接检测失败。在视野边缘、广角镜头下,角点会被畸变拉伸,检测算法退化明显。OpenCV 的 findChessboardCorners 对强畸变确实会失效,所以鱼眼标定时经常改用圆形网格。

圆形网格检测的是圆心,拟合圆比拟合角点更抗噪。但离图像中心远的圆会被投影成椭圆,必须用椭圆拟合,这时候算法里的斑点阈值、面积范围参数非常敏感。对称圆形网格存在 180 度旋转歧义,标定时优化器会跑向局部极小值,所以正规工具库都推荐非对称圆形网格——它通过错位排列打破对称性,让棋盘方向唯一确定。如果你在工具库里看到 AsymmetricCircles 这类名字,指的就是它。

实际使用中,棋盘格在普通镜头上精度没有敌手;圆形网格在鱼眼和畸变严重的镜头上表现更好,代价是圆心检测必须稳定,否则一张图里漏掉一排,整组数据就废了。

3.2 ArUco 标定板:遮挡场景与在线生成的正确姿势

ArUco 标定板是把一堆带 ID 的二维码 marker 排成阵列。它和棋盘格是两种完全不同的思路:棋盘格靠拓扑结构确定角点,ArUco 靠 ID 编码确定 marker 角点。这带来一个巨大好处——标定板不需要完整出现在画面里,哪怕只拍到 5 个 marker 也能提供足够的对应点。这对工业现场的窄小工位、机械臂运动范围受限的场景非常有用。

aruco 在线生成时注意三件事。第一,marker 的 ID 字典要选和检测端一致,OpenCV 里用 DICT_4x4_50 还是 DICT_5x5_100,两端不一致直接检测不到。第二,在线生成器导出的 PNG 是像素尺寸,打印后要拿游标卡尺量实际黑边边长,把量到的真实尺寸填进标定配置,不能直接用纸面设计的尺寸。第三,marker 之间的间距要一致,间距不一致会让板子的几何约束变弱,标定的外参容易抖动。

ArUco 的角点精度比不上一整块棋盘格,因为 marker 角点的亚像素精度受打印质量影响很大。能用棋盘格就用棋盘格,ArUco 是给“板子没法完全露出”的场景准备的后悔药。

3.3 标定板采图规范:张数、角度与覆盖

不管选哪种板子,采图都直接决定标定质量的上下限。单目标定最少 15 张较好,稳妥 20 到 30 张。不要连续拍摄几乎相同的画面,那种图再多也只是同一姿态的重复,优化器得不到新约束。

角度上覆盖四种情况:绕 x 轴倾斜、绕 y 轴倾斜、平面内旋转、前后平移改变距离。标定板要出现在画面四个角,不能只摆中间。我看到很多团队拿 10 张图全是正对镜头的,结果 cx cy 倒是稳,但 fx fy 和畸变系数方差很大,这是典型病态约束。双目标定采集时额外注意同步——两个相机必须同时采集同一个标定板姿态,推荐开启硬件触发或共用信号源,否则外参计算会引入几毫秒的时序误差,等效于板子移动了几个像素。

4. 单目和双目标定实操:从采集、角点检测到外参导出

流程上先单目后双目。工具库的典型逻辑是先独立标定两个相机内参,再用双目标定修正外参。不要跳过单目标定直接做立体标定,那会让内外参互相抢夺约束,结果很难收敛。

4.1 单目标定:从角点检测到内参导出

以 OpenCV 为例,核心过程如下:

import cv2 import numpy as np board_size = (11, 8) # 内角点数,是 (宽-1, 高-1) square_len = 0.02 # 单格边长,单位:米,注意和打印尺寸一致 criteria = (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 100, 1e-6) # 构造世界坐标系下的棋盘格角点坐标 objp = np.zeros((board_size[0] * board_size[1], 3), np.float32) objp[:, :2] = np.mgrid[0:board_size[0], 0:board_size[1]].T.reshape(-1, 2) objp *= square_len obj_points, img_points = [], [] for fname in image_files: img = cv2.imread(fname) gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) ret, corners = cv2.findChessboardCorners(gray, board_size) if not ret: continue 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("fx =", mtx[0, 0], "fy =", mtx[1, 1]) print("cx =", mtx[0, 2], "cy =", mtx[1, 2]) print("dist =", dist.ravel())

这段代码里最容易错的地方是 board_size。一个 12x9 的棋盘格,内角点数是 11x8,因为角点定义在格子交点处。填错之后检测出的角点数量对不上,工具会直接报错。square_len 的单位是米,打印的格子边长是 20mm 就填 0.02,不要填 20。这一步严格来说有点玄学,因为标尺测量误差会直接进入 fx 计算,所以用卡尺量尺寸时多量几个位置取平均。

cornerSubPix 的搜索窗口设成 (5,5) 通常够用,图像分辨率很高时可以适当放大。criteria 里的 max_iter 100 次基本满足需要。如果检测失败的图像比较多,先检查这组图是不是严重过曝、反光或运动模糊。工具库部署时,这些参数通常已经给了默认值,新手不要一上来就改。

4.2 双目标定:外参计算与立体校正

单目标定得到两个内参后,双目标定输入同一组图像中左右两边的角点坐标。核心调用如下:

# K1_init/d1_init 来自单目标定结果 # 左右角点必须来自同一时刻采集的图像对 ret, K1, d1, K2, d2, R, T, E, F = cv2.stereoCalibrate( obj_points, img_points_left, img_points_right, K1_init, d1_init, K2_init, d2_init, gray.shape[::-1], flags=cv2.CALIB_USE_INTRINSIC_GUESS ) R1, R2, P1, P2, Q, roi1, roi2 = cv2.stereoRectify( K1, d1, K2, d2, gray.shape[::-1], R, T, alpha=0, newImageSize=(width, height) ) # 生成左右相机的校正映射表 map1_l, map2_l = cv2.initUndistortRectifyMap( K1, d1, R1, P1, (width, height), cv2.CV_32FC1) map1_r, map2_r = cv2.initUndistortRectifyMap( K2, d2, R2, P2, (width, height), cv2.CV_32FC1)

stereoCalibrate 输出里的 R 是右相机相对于左相机的旋转矩阵,T 是平移向量。注意 flags 用了 CALIB_USE_INTRINSIC_GUESS,意思是双目标定不再大规模修改内参,只优化外参和少量畸变参数。这也是推荐的部署方式:先用大量单目图把内参标稳,再做双目联标,否则十几张图同时优化内外参很容易得到一组漂亮但分崩离析的参数。

stereoRectify 的 alpha=0 表示校正后只保留有效区域,边缘会有黑色裁切;alpha=1 保留全部像素但视野变大。对测距应用一般取 0,对视觉定位可以取 0 到 0.5 之间的值。最终得到的 Q 矩阵是视差转深度矩阵,用它就能从视差图计算三维坐标,这也是双目相机标定的最终目的。

4.3 关键参数汇总

参数作用建议值
board_size棋盘格内角点数实际角点行列,注意比格子数少 1
square_len单格边长单位米,与实际打印尺寸一致
criteria迭代终止条件max_iter 100,eps 1e-6
flags双目标定使用单目内参CALIB_USE_INTRINSIC_GUESS
alpha校正后视场保留测距取 0,定位取 0~0.5
采图数量单目 / 双目单目 15~30 张,双目 10~20 对

5. 标定翻车排查:重投影误差合格但测距不准的坑

标定这事的九成问题都出在数据采集和参数设置上,算法本身反而是最不需要怀疑的环节。以下几条是反复出现的踩坑记录。

5.1 现象:重投影误差很低,但测距还是不准

重投影误差低于 0.1 像素是个好消息,但测距偏差依然存在,大概率问题不在算法而在标定板的物理平面度。亚克力板、柔性 PCB 板平放时是平的,锁螺丝或手持时会弯曲变形,这种形变会均匀地骗过重投影误差,却把焦距和畸变参数全部带偏。解决方法是换用玻璃基板或陶瓷板,厚度尽量在 3mm 以上。标定之前先确认板子没有翘角。

5.2 现象:圆形网格检测失败或圆心偏移

圆形网格在广角镜头下会变成椭圆,检测算法里的斑点面积阈值和圆度阈值需要重新调。最常见的问题是把背景杂物当成标定圆。解决办法是先用颜色阈值把标定板区域提取出来,或者用“标定板中心到图像中心距离”约束过滤误检。另外圆形网格对打印精度极其敏感,圆形边缘发虚会让圆心拟合误差直接进入内参,用激光打印而不是喷墨打印。

5.3 现象:ArUco 标定结果抖动厉害

同一个工具、同一套代码,ArUco 标定结果每次都不一样,几乎可以锁定两个原因:字典不匹配或 marker 实际尺寸填错。字典不匹配时检测会偶尔成功偶尔失败。实际尺寸填错时,单目标定结果看起来还行,但外参和畸变轮流漂移。先在图上把每个 marker 的四个角点画出来,肉眼确认检测是否稳定,再打印一个已知尺寸的标记做实测验证。

5.4 现象:双目校正后外极线不水平

校正后外极线还有几像素的倾斜,通常是左右图不是同一时刻采集的,或者双目标定混入了一部分没有配对的图。务必使用硬件同步或共用外部触发,软件触发至少也要用图像时间戳做匹配。此外,双目联标时图像对数量太少会导致外参估计不稳定,10 对是下限,15 对以上比较可靠。

5.5 现象:鱼眼镜头用针孔模型,去畸变后边缘仍然弯曲

这不是参数问题,是模型错误。鱼眼镜头的投影本身就不满足针孔假设,强行用针孔拟合,中心区域可以拟得不错,边缘必然放弃。换用 KannalaBrandt 模型,在工具库配置里改相机模型类型即可。改完模型之后要重新采图,因为鱼眼标定要求标定板靠近镜头并覆盖边缘视角,旧图集如果都是中心附近的图,换了模型也救不回来。

6. 进阶:标定完成后的三分钟验证与 ArUco 后悔药

标定完成不意味着结束,花十分钟验证一下能省下后面排查的血泪经验。第一个验证用已知距离目标法:取标定板上一对相距 L 米的角点,放在距相机 d 米处,理论上成像像素数 l_expected = fx * L / d。实测通过角点检测得到 l_actual,如果偏差超过 2%,说明内参或畸变参数还有问题。这个验证成本极低,五分钟能做完。

第二个验证是外参符号检查。在左相机画面里选一个特征点,按外参投影到右相机画面,投影点应该落在对应位置。这一步能快速发现外参方向是否存反,是部署时最容易犯的错误之一。配合分区重投影误差散点图,如果误差集中在某个角落或边缘,说明采图覆盖不均,可以针对性补拍那个区域的图。

ArUco 的后悔药也在这里:如果已经用 ArUco 板标定且结果不稳,不需要换板子,把标尺重测一遍、多拍几十张不同角度的图重新标定即可。但要把每个 marker 的实际尺寸重新量一遍,不要偷懒沿用生成器标称值。我自己翻过车,觉得标定完成就万事大吉,结果部署时发现外参方向反了,排查了一整天。从此之后,标定完先做投影验证,再进算法流程。希望这个习惯能帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询