- 计算机视觉
- 图像处理
- 深度学习
- 机器学习
【免费下载链接】opencv_contrib
Repository for OpenCV's extra modules
本文以 opencv_contrib 仓库 ccalib 模块 的官方教程 multi_camera_tutorial.markdown 为核心骨架,系统讲解如何用"随机图案"(random pattern)标定物完成单相机与多相机标定:从随机图案的生成与打印、特征点检测,到MultiCameraCalibration的完整调用流程与输出参数。读完本文,你将掌握该工具箱的全部使用步骤、命令行参数含义与底层实现原理,可直接复刻到自己的多相机系统中。
随机图案:由RandomPatternGenerator随机生成的标定物,图案本身含大量可检测特征点。
实际拍摄的随机图案照片,用于输入标定流程。
1. 概述:为什么用随机图案做多相机标定
传统标定通常使用棋盘格(chessboard),但棋盘格要求标定板在每幅图像中完整可见。多相机标定场景下,多个相机从不同角度拍摄同一个标定物,标定物往往只能部分可见甚至被遮挡。随机图案方案正是为这种场景设计的:由于图案由随机生成的稠密特征构成,只要任意相机视野中出现图案的一部分,就能建立足够多的 2D-3D 对应点,从而完成标定。
从源码注释(randpattern.hpp)可知,该实现参考了论文B. Li, L. Heng, K. Kevin and M. Pollefeys, "A Multiple-Camera System Calibration Toolbox Using A Feature Descriptor-Based Calibration Pattern", IROS 2013,并且该模块最初是作为 OpenCV 的 GSoC 2015 项目被接收的(见 randpattern.cpp 与 multicalib.cpp 头部注释)。
整个流程分为两大步骤,这也是官方教程的主线:
- 随机图案标定物:生成随机图案 → 打印 → 拍摄照片 → 检测图案上的特征点,得到
objectPoints与imagePoints。 - 多相机标定:按约定命名采集的照片 → 构造输入文件 → 调用
MultiCameraCalibration完成标定并输出参数。
2. 随机图案标定物(Random Pattern Calibration Object)
2.1 什么是随机图案
随机图案是一张随机生成的图像。正因为随机,它包含大量的特征点(feature points),非常适合作为标定物。生成后打印出来,即可作为标定对象使用。官方教程中给出两张示例图:随机图案本身,以及用它拍摄的照片(见上文两张插图)。
2.2 使用 RandomPatternGenerator 生成图案
生成随机图案使用ccalib模块中的cv::randpattern::RandomPatternGenerator类,核心调用代码如下(摘自教程):
cv::randpattern::RandomPatternGenerator generator(width, height); generator.generatePattern(); cv::Mat pattern = generator.getPattern();其中width和height是图案图像(像素)的宽和高。生成得到pattern后,将其打印出来,再拍摄若干张包含该图案的照片。
仓库中的完整示例程序 random_pattern_generator.cpp 展示了命令行用法:
random_pattern_generator -iw 600 -ih 850 pattern.png各参数含义:
| 参数 | 含义 | 说明 |
|---|---|---|
-iw | 图案图像宽度(像素) | 必须为正整数 |
-ih | 图案图像高度(像素) | 必须为正整数 |
pattern.png | 输出图案文件名 | 通过imwrite保存 |
其内部实现就是构造RandomPatternGenerator并调用generatePattern()/getPattern()后imwrite保存。注意RandomPatternGenerator的构造函数签名是RandomPatternGenerator(int imageWidth, int imageHeight)(见 randpattern.hpp),两个参数均为像素单位。
2.3 使用 RandomPatternCornerFinder 检测特征点
拿到打印图案与拍摄照片后,需要检测出用于标定的objectPoints(3D 世界坐标点)和imagePoints(2D 图像点)。官方教程给出的示例代码:
cv::randpattern::RandomPatternCornerFinder finder(patternWidth, patternHeight, nMiniMatches); finder.loadPattern(pattern); finder.computeObjectImagePoints(vecImg); vector<Mat> objectPoints = finder.getObjectPoints(); vector<Mat> imagePoints = finder.getImagePoints();其中:
patternWidth/patternHeight:标定物的物理尺寸,单位由使用者自定义(如毫米、厘米),须与实际打印尺寸一致,这是决定标定尺度正确性的关键;nMiniMatches:每帧最少匹配特征点数,低于该阈值的图像帧将被丢弃;vecImg:存放标定图像的vector<Mat>;- 之后便可用
cv::calibrateCamera或cv::omnidir::calibrate等标定函数完成单相机标定。
从 randpattern.hpp 可以看出RandomPatternCornerFinder的完整构造签名与默认行为:
RandomPatternCornerFinder(float patternWidth, float patternHeight, int nminiMatch = 20, int depth = CV_32F, int verbose = 0, int showExtraction = 0, Ptr<FeatureDetector> detector = xfeatures2d::AKAZE::create(xfeatures2d::AKAZE::DESCRIPTOR_MLDB, 0, 3, 0.005f), Ptr<DescriptorExtractor> descriptor = xfeatures2d::AKAZE::create(xfeatures2d::AKAZE::DESCRIPTOR_MLDB, 0, 3, 0.005f), Ptr<DescriptorMatcher> matcher = DescriptorMatcher::create("BruteForce-L1"));默认使用AKAZE 特征检测/描述子与BruteForce-L1 匹配器,可自行传入其他FeatureDetector/DescriptorExtractor/DescriptorMatcher替换。depth决定输出点集的类型(CV_32F或CV_64F)。
实现要点(来自源码):在 randpattern.cpp 中,loadPattern会先对图案图像做灰度化(若非CV_8U则转换),并调用检测器与描述子提取图案的特征点与描述子;computeObjectImagePointsForSingle对每张输入图像先做直方图均衡化(equalizeHist),分别在原图与均衡化图像上提取特征并计算描述子,再与图案特征做双向交叉匹配(crossCheckMatching,即正反两个方向都匹配上才保留),最后选出匹配数更多的结果,降低误匹配。匹配点通过单应性(homography)等方式筛选后,按图案像素坐标与物理尺寸的线性映射生成 3D 点:
x = x / _patternImageSize.width * _patternWidth; y = y / _patternImageSize.height * _patternHeight; objectPoints.push_back(Vec3d(x, y, 0)); // Z = 0,标定板平面也就是说,图案图像的分辨率决定了 2D→3D 的换算精度,打印时图案的物理尺寸(patternWidth/Height)必须准确测量并传入。
2.4 单相机标定完整示例
仓库提供了基于随机图案的单相机标定示例 random_pattern_calibration.cpp,命令行用法:
random_pattern_calibration -pw 600 -ph 850 -mm 20 image_list.xml| 参数 | 含义 | 说明 |
|---|---|---|
-pw | 图案物理宽度 | 必须为正数 |
-ph | 图案物理高度 | 必须为正数 |
-mm | 最小匹配点数 | 示例中要求不小于 15 |
-fp | 固定主点于图像中心 | 可选,等价于设置CALIB_FIX_PRINCIPAL_POINT |
image_list.xml | 输入图像列表 | 由imagelist_creator生成 |
其流程为:读取列表(第一个文件为图案)→ 构造RandomPatternCornerFinder→loadPattern+computeObjectImagePoints→ 调用calibrateCamera得到内参K、畸变D与每帧外参rvec/tvec,最后将结果写入 XML(默认out_camera_params.xml,含camera_matrix、distortion_coefficients、rms等字段)。
3. 多相机标定(Multiple Cameras Calibration)
3.1 前置条件与数据采集约定
目前该工具箱必须使用随机图案标定物(官方教程原文:so far this toolbox must use random pattern object)。多相机标定的数据采集有两条硬性约定:
- 同一时刻多个相机拍摄同一块图案:为标定外参,一个图案需要被至少两台相机同时观测到(同一时刻)。
- 文件命名规则
cameraIdx-timestamp.*:- 文件名由
相机编号-时间戳组成,同一时间戳表示"多台相机同时拍摄的同一块标定物"; cameraIdx必须从0开始;- 官方示例文件名:
"0-129.png"、"0-187.png"、"1-187"、"2-129"——其中0-187与1-187时间戳同为 187,即相机 0 与相机 1 同时拍到的同一图案。
- 文件名由
在 multicalib.cpp 的loadImages中,程序正是通过解析文件名中的cameraIdx-timestamp(sscanf(filename.c_str(), "%d-%d", &cameraVertex, ×tamp))来划分各相机的图像,并用timestamp关联同一图案在不同相机中的观测,因此命名错误会导致数据无法正确归组。
3.2 构造输入文件(imagelist_creator)
MultiCameraCalibration的输入是一个由 OpenCV samples 中的imagelist_creator工具生成的 XML 文件。官方教程说明:列表中的第一个文件名是随机图案文件,其余为标定照片。构建方式为:
imagelist_creator multi_camera_omnidir.xml *.*multi_cameras_calibration.cpp的 usage 注释还提醒:第一个文件名必须是图案,其余为照片名,且照片名须符合cameraIdx-timestamp.*格式、cameraIdx从 0 开始。
3.3 MultiCameraCalibration 核心 API
官方教程给出的调用方式:
cv::multicalib::MultiCameraCalibration multiCalib(cameraType, nCamera, inputFilename, patternWidth, patternHeight, showFeatureExtraction, nMiniMatches); multiCalib.run(); multiCalib.writeParameters(outputFilename);各参数含义如下表(结合 multicalib.hpp 完整构造签名整理):
| 参数 | 含义 |
|---|---|
cameraType | 相机类型,支持multicalib::MultiCameraCalibration::PINHOLE与OMNIDIRECTIONAL |
nCamera | 相机数量 |
inputFilename | imagelist_creator生成的文件名(第一个为随机图案,其余为标定照片) |
patternWidth/patternHeight | 图案的物理宽、高(用户自定义单位) |
verbose | 是否输出详细日志(0/1,默认 0) |
showFeatureExtraction | 布尔标志,是否可视化特征提取过程(0/1) |
nMiniMatches | 每帧最少匹配点数,低于该值的帧被丢弃(默认 20) |
outputFilename | 保存标定参数的 XML 文件 |
完整构造签名还支持flags(标定标志位)、criteria(优化停止条件,默认TermCriteria(COUNT+EPS, 200, 1e-7))以及自定义的detector/descriptor/matcher(默认同为 AKAZE + BruteForce-L1)。
对于全向相机(OMNIDIRECTIONAL),其模型与标定细节可参考cv::omnidir模块及仓库内的 omnidir_tutorial.markdown(底层采用 Mei 的全向相机模型,见 multicalib.cpp 头部注释)。
3.4 完整命令行示例
仓库示例 multi_cameras_calibration.cpp 给出了可直接运行的命令行:
multi_cameras_calibration -nc 5 -pw 800 -ph 600 -ct 1 -fe 0 -nm 25 -v 0 multi_camera_omnidir.xml参数对照:
| 命令行参数 | 含义 | 取值 |
|---|---|---|
-nc | 相机数量 | 正整数 |
-pw/-ph | 图案物理宽/高 | 正数 |
-ct | 相机类型 | 0为针孔(pinhole),1为全向(omnidirectional) |
-fe | 是否显示特征提取过程 | 0或1 |
-nm | 每帧最小匹配点数 | 正整数 |
-v | 是否输出详细日志 | 0或1 |
input_data | 输入文件 | 由imagelist_creator生成 |
示例中-nc 5 -ct 1表示对 5 台全向相机进行标定。运行后默认将参数写入multi-camera-results.xml(可通过修改源码中的outputFilename调整)。
3.5 内部流程:三步式标定管线
run()方法实际依次调用loadImages()→initialize()→optimizeExtrinsics()(见 multicalib.cpp),也可以按需分步调用(示例程序正是分开调用的,并注释说明这三行可替换为multiCalib.run())。三步的职责如下:
loadImages():解析输入列表,按
cameraIdx-timestamp命名将照片分组到各相机;用RandomPatternCornerFinder对每张图检测特征点并生成 object/image points,过滤掉匹配点过少的帧;随后逐相机单独标定——针孔相机调用cv::calibrateCamera,全向相机调用cv::omnidir::calibrate,得到各相机的内参矩阵cameraMatrix、畸变系数、xi(全向模型)以及每帧外参;每个"相机-图案"观测对应图结构中的一条edge,边上的transform由该帧外参的旋转矩阵与平移向量组成 4×4 变换(见initialize()之前的建边逻辑)。initialize():将观测关系构建成图(graph)——相机与图案分别是顶点(
vertex),一条edge连接一个相机顶点与一个图案顶点。以 0 号相机为基准(其位姿为单位矩阵),对图做遍历,推算每个相机相对 0 号相机的初始位姿pose(见 multicalib.cpp)。若有相机与图不连通,程序会打印cameraX is not connected提示。optimizeExtrinsics():对除基准顶点外的所有顶点位姿做类似 bundle adjustment 的全局优化(源码中实现了
computeJacobianExtrinsic、computeProjectError等),最小化重投影误差,精化外参。该步返回最终的平均重投影误差,可通过_error保存并在输出 XML 中记录。
3.6 输出参数 XML 结构
writeParameters(multicalib.cpp)将结果写入 XML,其结构为:
nCameras:相机总数;- 对每台相机
i:camera_matrix_i:内参矩阵;camera_distortion_i:畸变系数;xi_i:仅全向相机输出(OMNIDIRECTIONAL时写入);camera_pose_i:该相机相对 0 号相机的位姿(4×4 变换);
meanReprojectError:平均重投影误差,可用来评估标定质量;pose_timestamp_<ts>:每个图案(照片)顶点在基准相机坐标系下的位姿,<ts>为该照片的时间戳。
得到这些外参后,即可用于多相机系统的三维重建、拼接或位姿估计等下游任务。
4. 实战注意事项与参数调优建议
结合官方教程与源码,以下几点直接影响标定成败与精度:
- 物理尺寸必须准确:
patternWidth/patternHeight与打印实际尺寸不一致,会导致标定尺度错误、重投影误差虚高。图案图像分辨率(width/height)也应尽量高,以保证 2D→3D 映射精度。 - 采集时保证共视:外参标定依赖"同一图案被至少两台相机同时看到",采集时应让相邻相机的视野有重叠区域,并确保图案在重叠区清晰可见。
- 命名严格遵守
cameraIdx-timestamp:cameraIdx从 0 开始连续编号;同一时间戳的照片必须来自同一图案(同一时刻)。文件解析基于该约定,命名错误会导致图像被错误归组。 - 合理设置
nMiniMatches:默认 20。图案被遮挡严重或拍摄模糊时匹配点会减少,阈值过高会丢弃过多帧导致标定失败,过低则可能引入误匹配。可从示例建议的 20~25 起步调试。 - 利用可视化标志排错:将
showFeatureExtraction(-fe 1)打开,可直观检查每帧特征提取与匹配质量;verbose(-v 1)会打印每张图打开是否成功、匹配点数量、每相机初始化的 RMS 与内参矩阵等,是排查数据问题的重要手段。 - 输出文件评估:
meanReprojectError与rms是标定质量的主要参考指标;若某相机cameraX is not connected或某帧匹配点过少,应重新采集该视角的数据。
5. 总结
本文围绕 multi_camera_tutorial.markdown 完整复现了 opencv_contribccalib模块基于随机图案的多相机标定流程:先通过RandomPatternGenerator生成并打印随机图案,再用RandomPatternCornerFinder(AKAZE 特征 + 交叉匹配 + 单应性筛选)提取 2D-3D 对应点,最后以cameraIdx-timestamp命名约定组织多相机数据,交给MultiCameraCalibration完成"逐相机初始化 + 图遍历推算初始位姿 + bundle adjustment 精化外参"的三步标定,并输出结构化 XML 参数。该工具箱尤其适合标定板部分遮挡、多视角部分可见的多相机系统。更多细节可继续阅读仓库中的 multicalib.hpp、randpattern.hpp 头文件,以及 multi_cameras_calibration.cpp、random_pattern_calibration.cpp 两个可直接编译运行的示例程序。
- 计算机视觉
- 图像处理
- 深度学习
- 机器学习
【免费下载链接】opencv_contrib
Repository for OpenCV's extra modules
相关推荐
Obsidian手写笔记插件终极指南:5步实现PDF标注与数字笔记融合
Obsidian手写笔记插件终极指南:5步实现PDF标注与数字笔记融合 你是否渴望在Obsidian知识库中直接创建和编辑手写笔记?Obsidian手写笔记插件
计算机视觉图像处理深度学习机器学习为什么GitHub官方要做堆叠式PR?gh-stack扩展背后的设计动机全解析
为什么GitHub官方要做堆叠式PR?gh stack扩展背后的设计动机全解析 堆叠式PR(Stacked PRs)是 GitHub 官方推出的新能力:把大型改
开发工具版本控制研发协作终极广角镜头校正指南:OpenCV ccalib模块相机标定实战
终极广角镜头校正指南:OpenCV ccalib模块相机标定实战 想要解决广角镜头带来的严重畸变问题吗?OpenCV的ccalib模块提供了完整的全向相机标定解
计算机视觉图像处理机器学习
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考