ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

相机标定模型选型与双目标定:从针孔到鱼眼全向相机实战指南

相机标定模型选型与双目标定:从针孔到鱼眼全向相机实战指南 简介这套工具库围绕相机内参标定与双目立体标定设计适合从事计算机视觉、摄影测量或机器人视觉的工程师与研究者使用。它同时支持针孔、Kannala-Brandt、MEI与Scaramuzza等主流相机模型也兼容棋盘格、圆形网格、非对称圆形网格以及ArUco标定板可满足从普通镜头到广角鱼眼镜头等多样化的标定需求。压缩包共含225个文件整体仅24.14MB内容包括C实现源码、Python脚本、YAML与JSON配置、PDF说明文档、Markdown笔记以及大量JPEG样例图像覆盖标定板检测、相机模型实现、畸变系数求解和双目外参估计等关键模块便于直接阅读、编译和二次开发。目前已有102人学习浏览可作快速上手标定工具链的参考。通过组合不同模型与标定板示例工程清晰展示了完整标定流程使用者能结合实际场景选择最合适的配置从而显著缩短开发周期、提升标定准确性。1. 从针孔到全向相机一套工具库打通内参标定与双目标定的选型思路标定翻车十次有八次不是原理看不懂而是相机模型和标定板没对上。相机内参标定和双目标定在工业级项目里已经非常成熟一套同时支持针孔模型、KannalaBrandt模型、MEI模型和Scaramuzza模型的工具库能让你从普通镜头一路覆盖到鱼眼和折反射全向相机标定板也兼顾棋盘格、圆形网格、非对称圆形网格和ArUco基本把视觉项目里能遇到的标定场景都包圆了。适合谁做视觉SLAM、双目测距、机器人抓取的工程师坐标从像素到相机到机械臂第一环就是这套标定结果。这个方向值得投入因为标定是所有视觉系统最底层的定位精度来源。拿到这样的工具库部署时最该先弄清楚的是每个模型对应哪类镜头、每块标定板怎么检测而不是急着跑默认命令。2. 四种相机模型怎么选针孔、KannalaBrandt、MEI与Scaramuzza的投影数学多模型工具库值钱的地方在于把投影模型和畸变模型解耦。你选模型本质上是在选投影函数。选错了畸变系数在小数点后面怎么调都收敛不到合理值。所以哪怕是拿到现成工具库也建议先花十分钟把四种模型的参数含义过一遍。2.1 针孔模型常规镜头的默认选择别把视场角硬塞给它针孔模型是最标准的透视投影三维点(X,Y,Z)到像素(u,v)的映射写作s * [u, v, 1]^T K * [X, Y, Z]^T K [fx, 0, cx; 0, fy, cy; 0, 0, 1]畸变部分用径向k1、k2、k3和切向p1、p2描述。OpenCV的cv2.calibrateCamera默认就是这套模型绝大多数工业镜头、手机前摄、单目结构光相机都属于它。选型边界很明确视场角在100度以内的镜头针孔模型是首选自由度少、参数稳定重投影误差很容易标到0.1像素以下。一旦镜头视场角超过100度图像边缘的拉伸开始明显针孔模型有限的多项式畸变就拟合不过来继续用只会得到一组看起来不大、实际边缘全歪的参数。实验室里拿大广角镜头硬套针孔模型标定结果K矩阵没问题但校正后的直线全是弧线这种翻车我在项目里见过不止一次。2.2 KannalaBrandt模型等距投影多项式鱼眼镜头的正解鱼眼镜头不能满足针孔模型的透视关系KannalaBrandt模型改用等距投影假设并直接用畸变多项式修正投影角。它的核心公式是theta_d theta * (1 k1*theta^2 k2*theta^4 k3*theta^6 k4*theta^8)其中theta是入射光线与光轴的夹角theta_d是经过镜头畸变后的实际投影角。像素坐标再由theta_d乘上焦距折算出来。注意这套模型只有k1到k4四个径向畸变系数没有切向p1、p2。这不是偷工减料鱼眼镜头在装配上的主要误差体现在径向切向分量基本淹没在噪声里。OpenCV的cv2.fisheye.calibrate就是KannalaBrandt模型的实现输入标定板的点流程和针孔类似只是dist数组长度固定为4。标定时看k1到k4的量级正常应该在-1到1之间超过这个范围基本说明采集数据有问题或者镜头根本不是鱼眼。车载环视、全景相机这类视场角120度以上的镜头用KB模型是目前行业里的标准做法。2.3 MEI模型与Scaramuzza模型全向相机的镜面反射与多项式拟合MEI模型解决的是折反射全向相机也就是镜头前面扣一个双曲面镜或抛物面镜那种结构。它的投影过程分两步空间点先经镜面反射到一个虚视点再由一个虚拟针孔相机成像。模型参数里多了一个xi用来描述镜面的离心率。xi接近1时近似抛物面镜xi接近0时退化成普通针孔所以MEI模型天然向下兼容普通镜头。Scaramuzza模型则是另一条路它不关心镜面具体是什么几何形状直接把整个投影过程用一个泰勒多项式打包拟合。投影函数写成f(rho) a0 a1*rho a2*rho^2 a3*rho^3 a4*rho^4 a5*rho^5其中rho是图像点到主点(cx,cy)的距离。实际应用中取到5阶6个系数足够描述绝大多数鱼眼和折反射相机的径向变形。这种黑匣子拟合思路的好处是遇到非标准镜面也能硬拟合出来坏处是系数没有物理含义换一个镜头就得重新标。工具库里这两类模型的典型入口是OpenCV的cv2.omnidir模块MEI和Scaramuzza两种投影都可以在里面调。常见的做法是先试MEI因为xi参数能告诉你镜面到底是不是标准的旋转对称结构如果MEI的重投影误差一直压不下来再切换到Scaramuzza的多项式拟合。2.4 模型选型速查表与工具库部署入口工具库部署后第一步先确认模型枚举和标定板的对应关系。这里给一张选型表照着判断当前相机该走哪个入口相机模型适用镜头投影方式主要参数常见标定入口针孔普通工业镜头、手机镜头透视投影fx, fy, cx, cy k1, k2, p1, p2, k3cv2.calibrateCameraKannalaBrandt鱼眼、车载环视、全景镜头等距投影多项式fx, fy, cx, cy k1, k2, k3, k4cv2.fisheye.calibrateMEI双曲面镜折反射相机镜面反射透视fx, fy, cx, cy, xi 畸变多项式cv2.omnidir.calibrateScaramuzza特殊镜面、超广角泰勒多项式拟合6个多项式系数 cx, cyOCamCalib / omnidir选型判断一句话视场角100度以内用针孔120度以上用KannalaBrandt折反射结构用MEIMEI拟合不好换Scaramuzza。工具库的接口一般会把这些模型包装成枚举参数你传model_typekannala_brandt或model_typepinhole就能切换。如果库内部用的是OpenCV它通常还会额外做一层检测器和标定器的拆分方便你在不同标定板之间横跳。3. 单目标定落地生成标定板、检测角点、计算内参与重投影误差单目标定是整个工具库的地基。双目标定的R和T建立在左右单目内参都准确的前提下如果单目标定参数本身是歪的后面双目标定再怎么优化都是白费。所以这一章把单目标定的完整链路走一遍从生成标定板到输出内参。3.1 生成标定板棋盘格、非对称圆形网格和ChArUco的打印参数标定板可以买也可以自己打印关键是物理尺寸要准。棋盘格要卡尺量实际格子边长不能只信打印设置的标称尺寸。非对称圆形网格板可以自己用OpenCV生成。import cv2 import numpy as np # 生成非对称圆形网格标定板 page_w, page_h 840, 594 # 图像尺寸px spacing 50 # 相邻点间距px dot_radius 6 # 圆点半径px rows, cols 6, 11 # 行数、基准列数 img np.full((page_h, page_w, 3), 255, np.uint8) for r in range(rows): # 奇数行偏移半格构成非对称结构 offset spacing // 2 if r % 2 else 0 for c in range(cols): x page_w // 2 - (cols * spacing) // 2 c * spacing offset y page_h // 2 (r - rows / 2) * spacing cv2.circle(img, (x, y), dot_radius, (0, 0, 0), -1) cv2.imwrite(asymmetric_circle_grid.png, img)这段代码生成的是非对称圆形网格标定板。非对称结构就是奇数行圆点偏移半个间距OpenCV在CALIB_CB_ASYMMETRIC_GRID模式下靠这个偏移量判断标定板朝向而不是像棋盘格那样靠黑白交界。打印时要控制实际图像尺寸比如图案区域打印成大约250mm乘180mm然后用卡尺实测圆点间距标定时代入实测值而非理论值。ArUco标定板同样可以自己生成推荐用ChArUco板因为它在ArUco码的基础上插值出了棋盘格角点角点数目和精度都更高。dictionary cv2.aruco.getPredefinedDictionary(cv2.aruco.DICT_4X4_50) board cv2.aruco.CharucoBoard((11, 8), 0.025, 0.018, dictionary) img_board board.generateImage((840, 594), marginSize50) cv2.imwrite(charuco_board.png, img_board)ChArUco的参数里square_length0.025是棋盘格边长marker_length0.018是内部ArUco码边长单位都是米。打印后需要实测校验这两个值特别是marker_length不能大于square_length否则ArUco码会互相侵占。3.2 如何标定相机内参棋盘格与圆形网格的最小检测流程标定相机内参的标准流程是采集若干张标定板图像检测角点物体点与图像点配对最后求解内参和畸变。棋盘格的检测代码是所有流程里最稳定的。import glob import cv2 import numpy as np pattern (11, 8) # 内角点列数、行数 square_size 0.025 # 棋盘格边长米 objp np.zeros((pattern[0] * pattern[1], 3), np.float32) objp[:, :2] np.mgrid[0:pattern[0], 0:pattern[1]].T.reshape(-1, 2) objp * square_size obj_pts, img_pts [], [] for f in glob.glob(calib/*.jpg): gray cv2.imread(f, cv2.IMREAD_GRAYSCALE) ret, corners cv2.findChessboardCorners(gray, pattern, None) if not ret: print(skip:, f) continue criteria (cv2.TERM_CRITERIA_EPS cv2.TERM_CRITERIA_MAX_ITER, 30, 1e-6) corners cv2.cornerSubPix(gray, corners, (11, 11), (-1, -1), criteria) obj_pts.append(objp) img_pts.append(corners)findChessboardCorners返回的是内角点pattern(11,8)表示标定板图案是12乘9个格子检测的是内部11乘8个角点。cornerSubPix是在整像素角点周围做亚像素精化窗口大小(11,11)越大越稳但耗时也越高。物体点用三维坐标表示Z轴全为0X和Y是格子边长乘以索引。这里用米作单位最终K矩阵的fx和fy单位是像素而平移向量tvec的单位就是米。圆形网格检测的核心区别是把棋盘格检测换成findCirclesGrid并用SimpleBlobDetector提取圆点blob_params cv2.SimpleBlobDetector_Params() blob_params.filterByArea True blob_params.minArea 80 # 圆点在图像里的最小面积 blob_params.maxArea 5000 detector cv2.SimpleBlobDetector_create(blob_params) ret, centers cv2.findCirclesGrid( gray, pattern, None, flagscv2.CALIB_CB_SYMMETRIC_GRID, blobDetectordetector) # 非对称圆形网格把flags换成 # cv2.CALIB_CB_ASYMMETRIC_GRIDminArea和maxArea是按实际图像中圆点占的像素面积设置的需要根据相机分辨率和拍摄距离调整。设置太小会捡到反光噪点设置太大边缘位置的小圆点会被漏掉。圆形网格板受光照和模糊影响比棋盘格大但它没有方向歧义特别适合标定鱼眼镜头边缘区域。工具库一般会在这一层封装一个detect(board_type, image)接口内部帮你切换棋盘格和圆形网格的检测逻辑。3.3 ArUco与ChArUco检测用标定位姿换角点稳定性棋盘格要求整块板都出现在画面里哪怕少一行角点都检测失败。ArUco码的优势是允许部分遮挡哪怕画面里只有几个marker也能给出位姿。工具库支持ArUco标定板时内部通常按以下步骤做检测。dictionary cv2.aruco.getPredefinedDictionary(cv2.aruco.DICT_4X4_50) detector cv2.aruco.ArucoDetector(dictionary) corners, ids, _ detector.detectMarkers(gray) if ids is None: print(no marker detected) continue # 按id排序保证物体点和图像点一一对应 order np.argsort(ids.ravel()) corners [corners[i] for i in order] ids ids[order]detectMarkers返回的marker顺序是随机的直接按顺序展开角点会导致物体点和图像点错位。按id排序是必须的操作。每个marker给出4个角点如果你用的是普通ArUco板那就把marker的4个角点坐标按预置的3D坐标展开后送入标定函数。但纯ArUco板有精度瓶颈marker角点数量少平均到每张图只有几十个点而且角点检测精度不如棋盘格。更推荐ChArUco板检测出marker后插值出棋盘格角点ret, charuco_corners, charuco_ids cv2.aruco.interpolateCharucoCorners( corners, ids, gray, board) if ret 10: print(too few charuco corners) continueinterpolateCharucoCorners会利用marker的位置关系推算出棋盘格内角点哪怕标定板有部分遮挡也能交出足够数量的角点。ret是成功插值出的角点数量一般至少要有10个以上才建议送入标定器。ChArUco板的优势就在这ArUco负责位姿鲁棒性棋盘格负责角点精度。3.4 内参求解与重投影误差验证标定效果好不好看这个数角点收集齐了内参计算就是一行调用的事。真正要花心思的是验证标定结果。ret, K, dist, rvecs, tvecs cv2.calibrateCamera( obj_pts, img_pts, gray.shape[::-1], None, None) # 重投影误差把三维物体点重新投影回图像比较和检测角点的偏差 mean_error 0.0 for i in range(len(obj_pts)): proj, _ cv2.projectPoints( obj_pts[i], rvecs[i], tvecs[i], K, dist) err cv2.norm(img_pts[i], proj, cv2.NORM_L2) / len(proj) mean_error err print(freprojection error: {mean_error / len(obj_pts):.4f} px)重投影误差是所有标定结果的总结账。针孔模型在良好采集下应该小于0.15像素0.1到0.3像素都属于正常范围超过0.5像素就需要排查了。鱼眼相机的fisheye用同样的思路验证但要把projectPoints换成cv2.fisheye.projectPoints全向相机则用cv2.omnidir对应的投影函数。重投影误差小不代表一定正确它只能说明数学模型和采集数据是自洽的数据如果全是正面拍摄误差依旧会很低。所以采集阶段就要刻意制造姿态变化这个坑在后面专门说。4. 双目标定从左右单目到R、T、E、F和立体校正双目相机标定和单目标定的关系是先标左右各自的内参再标左右之间的外参。内参不准外参必然歪。很多工具库会把双目标定封装成一个两步函数calibrate_left_right()然后stereo_calibrate()。这一章把采集配对、外参求解和立体校正串起来。4.1 双目采集配对规则时间同步和姿态覆盖决定外参质量双目采集最容易被忽略的是左右图配对。如果左右相机不是硬件同步快门拍摄运动物体时两张图上的标定板姿态会有差异算法会把这种差异当成外参的一部分吸收掉标出来的R和T就是错的。配对规则有三条左右图必须来自同一时刻同一个标定板姿态保存时按文件名一一对应比如left_01.jpg和right_01.jpg。采集数量至少20对板子覆盖正对、左倾、右倾、上仰、下俯五种姿态并且要有近景和远景。相机固定不动板子由人拿着移动不要手持相机对着固定板子拍摄否则左右图的抖动无法同步。板子要贴在平整的硬质底板上不要捏着纸板一角悬空拍摄。纸板弯曲造成的误差在双目里会被放大成基线的误差标出来的T向量会带一个固定的偏置。我一般会用铝板或亚克力板贴标定板背面上两个支架方便手持。4.2 双目相机标定的核心代码stereoCalibrate输出R、T、E、F左右单目的内参标定完成后双目标定的输入是左右各自的角点序列和内参初值。OpenCV的cv2.stereoCalibrate函数直接给出旋转、平移、本质矩阵和基础矩阵。# 左右图已经分别通过单目标定得到 K1,D1 和 K2,D2 # obj_pts_l/r 是配对好的物体点列表img_pts_l/r 是对应的图像角点列表 ret, K1, D1, K2, D2, R, T, E, F cv2.stereoCalibrate( obj_pts_l, img_pts_l, img_pts_r, K1, D1, K2, D2, image_size, flagscv2.CALIB_FIX_INTRINSIC)flagscv2.CALIB_FIX_INTRINSIC的意思是固定单目标定得到的内参不变只优化左右相机之间的外参。这是双目标定的推荐做法因为单目内参在独立标定时已经足够准如果放开来一起优化左右内参会互相补偿外参的误差最后得到的R和T看起来收敛但插上新的分辨率或换镜头就露馅。R是右相机相对左相机的旋转矩阵T是右相机光心在左相机坐标系下的平移向量单位由你给物体点用的物理单位决定。如果objectPoints用米T单位就是米后续做深度计算时不需要再换算。E和F分别是本质矩阵和基础矩阵它们描述左右图的极线约束立体校正阶段一般用不到主要用来做对极几何验证或外点剔除。跑完stereoCalibrate之后正确的习惯是打印R和T看一眼量级T向量的模长应该和实际双目光心间距一致差太多说明采集配对有问题。4.3 立体校正与重映射让左右图逐行对齐立体校正的目的是把左右图像重映射成行对齐这样视差搜索只在水平方向进行。校正过程分成两步计算校正变换然后生成查表映射并重采样。R1, R2, P1, P2, Q, roi1, roi2 cv2.stereoRectify( K1, D1, K2, D2, image_size, R, T, flagscv2.CALIB_ZERO_DISPARITY) map_lx, map_ly cv2.initUndistortRectifyMap( K1, D1, R1, P1, image_size, cv2.CV_32FC1) map_rx, map_ry cv2.initUndistortRectifyMap( K2, D2, R2, P2, image_size, cv2.CV_32FC1) rect_l cv2.remap(gray_l, map_lx, map_ly, cv2.INTER_LINEAR) rect_r cv2.remap(gray_r, map_rx, map_ry, cv2.INTER_LINEAR)R1和R2是左右相机的校正旋转矩阵P1和P2是校正后的投影矩阵Q矩阵负责把视差转换为深度。initUndistortRectifyMap生成从原图到校正图的像素映射表remap按表重采样。生成的映射表可以离线保存成npz文件运行时直接加载省掉每次启动的重复计算这是工具库在部署到一个固定相机时常见的优化。校正效果最简单的验证方法把左右校正图画到一起拉一条水平线观察同名特征点是否落在同一条线上。如果双目相机标定和外参都正确校正后左右图的同一物理点在像素纵向坐标差应该小于1个像素。实际项目里我还习惯把校正图保存下来放到图像查看器里逐行扫比只看一个平均误差指标靠谱。5. 从采集到参数收敛多模型标定中常见的五个坑位工具库再成熟也挡不住采集数据本身的问题。这一章把踩过的坑按现象、原因、解决整理出来基本都是采集和模型匹配层面的问题参数层面反而很少出错。5.1 鱼眼镜头用针孔流程标定畸变系数爆表现象鱼眼镜头走cv2.calibrateCamera标出来的k1超过3重投影误差接近1像素校正后图像边缘出现明显的波浪形畸变。原因鱼眼镜头采用等距投影针孔模型的透视投影假设在视场角超过100度后不再成立。算法为了让多项式拟合边缘大畸变只能把径向系数推到异常大的数值结果中间区域过校正边缘区域欠校正。解决鱼眼镜头必须切换到fisheye标定入口也就是KannalaBrandt模型。标定完成后检查dist数组的4个系数正常应该在-1到1之间。如果系数落在这个范围但重投影误差还是压不下来优先排查标定板是否覆盖了图像边缘而不是怀疑模型。5.2 非对称圆形网格板转个方向就检测不到角点现象非对称圆形网格板正对相机时findCirclesGrid正常返回retTrue把板子旋转90度或者大角度倾斜后检测失败。原因非对称圆形网格靠奇偶行错位来编码方向当标定板旋转角度过大时算法根据初始点位置推断的网格方向失效圆点排序对不上检测器直接放弃。解决采集时把板子旋转角度控制在正负30度以内不要横竖乱转。如果项目里确实需要大角度姿态覆盖建议改用ChArUco板它的marker提供了明确的方向信息不会因为旋转丢掉方向判定。工具库里如果同时支持圆形网格和ArUco全向相机标定时我一般优先用ChArUco。5.3 双目标定结果正常校正后左右图同名点却不在同一行现象stereoCalibrate和stereoRectify没报任何错误R和T量级看起来也正常但左右校正图拼接后水平线上同名点错开3到4个像素。原因左右图采集不是同一时刻拍的。标定板稍微一动左右两帧里的姿态差异就被当成外参的一部分校正时无法做到逐行对齐。另一个常见原因是左右图配对文件错位left_01对上了right_02这种错位在平均误差指标上不一定暴露得很明显但行对齐会翻车。解决检查配对文件名的对应关系严格逐帧核对。如果相机支持硬件触发把左右相机接到同一个触发器上不支持就用静态标定板确保两次拍摄之间板子完全没有移动。5.4 标定板打印比例漂移内参fx和fy与理论值偏差过大现象用A4纸打印棋盘格标定板量出来格子间距是25mm输入后标出的fx和fy比相机标称值差3%以上。原因打印机在送纸方向和非送纸方向的DPI可能不一致或者排版软件把图片缩放到了适应页面导致格子实际物理间距和标称值不符。标定程序接收的square_size是错的焦距自然错。解决用游标卡尺测量打印后每个格子的实际边长取多个格子间距的平均值代进代码。不要相信打印设置里的原始尺寸选项打印机的物理误差会直接变成标定误差。这一点做机器人手眼标定时更致命差3%的焦距会直接变成机械臂末端3%的定位偏差。5.5 重投影误差0.08像素实测校正后直线依旧是弯的现象单目标定报告的重投影误差低得漂亮但拿到真实环境拍摄一张有直线的场景校正后直线边缘还是弧线。原因采集的30张图全部是标定板居中、正对相机板子姿态单一。重投影误差只反映模型对这批数据的拟合程度如果数据没有覆盖镜头的边缘和不同入射角模型就在中间区域过拟合边缘区域的真实畸变根本没被约束。解决重新采集保证每张图像里标定板占画面面积的1/3到1/2并且让板子覆盖四个角落和中心位置姿态至少包含三种俯仰角和三种偏航角。不要看见重投影误差已经低于0.1就停止采集误差低很容易骗人覆盖度才是标定质量的真正保障。我现在的习惯是采集完先画角点位置分布图看到四角有角点覆盖才继续往下走这个习惯帮我省掉了大量返工时间。6. 进阶用ChArUco做在线标定以及三个可以当验收标准的验证指标在线标定是产线和设备调试里很实用的动作。ChArUco板因为支持部分遮挡和局部检测可以在相机画面里任意摆放程序后台持续累积角点累积够了自动触发标定。流程是把3.3节的检测逻辑包成一个累积器每帧检测到charuco角点就追加到列表达到15张触发单目标定达到20对触发双目标定。这样不再需要人按要求摆pose降低了对操作人员的要求标定一致性反而更好。标定完成后的验收建议看三个指标。第一是重投影误差RMS单目针孔用projectPoints计算鱼眼用fisheye.projectPoints这个数要小于0.3像素才算及格。第二是立体行对齐质量校正后的左右图上取10个以上特征点统计纵向视差的标准差要求小于1像素这是双目测距精度的直接保障。第三是直线度测试在标定场景中放一把直尺校正图上沿尺子边缘取点做最小二乘直线拟合计算每个点到拟合直线的最大偏差要求小于0.5像素。这三个指标都过了标定参数基本可以放心交给下游算法。工具库的最终价值是把这些流程收口成一个可重复的脚本而不是一把梭的参数调优。我现在拿到一个新相机第一件事就是用视场角选模型然后生成一块适配的ChArUco板跑一遍在线采集和上述三个指标验证整个流程控制在两小时内。这套方法在我经手的项目里基本没有翻过车希望帮到你。本文还有配套的精品资源点击获取
返回列表