news 2026/9/8 22:33:56

OpenCV ChArUco 标定板实战:从板图生成、角点检测到位姿估计的完整指南

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
OpenCV ChArUco 标定板实战:从板图生成、角点检测到位姿估计的完整指南

OpenCV ChArUco 标定板实战:从板图生成、角点检测到位姿估计的完整指南

【免费下载链接】opencvOpen Source Computer Vision Library项目地址: https://gitcode.com/GitHub_Trending/opencv31/opencv

ChArUco(Charuco)板把棋盘格的角点精度与 ArUco 标记的检测灵活性合二为一,是 OpenCVaruco模块中面向高精度相机标定与位姿估计的核心标定物。本文以 charuco_detection.markdown 官方教程为主线,结合本仓库中 detect_board_charuco.cpp、create_board_charuco.cpp 及 charuco_detector.hpp 源码,系统讲解 ChArUco 板的创建、角点插值检测原理、两种位姿估计调用链,并提供可直接运行的命令行示例与关键 API 说明。读完本文,你将能独立完成 ChArUco 板图生成、无标定/带标定两种模式下的角点检测,以及基于solvePnP的高精度位姿估计。

为什么要用 ChArUco 板:两种传统方案的取舍

ArUco 标记与 ArUco 板检测快、容错强(允许部分遮挡、部分视野),但它的一个短板是:即使做了亚像素细化,角点位置的精度依然不够高

与之相对,棋盘格的每个角点都被两个黑色方块包围,角点定位可以被细化得非常精确;但棋盘格的检测非常"娇气"——必须完整可见,不允许任何遮挡,灵活性远不如 ArUco 板。

ChArUco 板的设计目标正是融合二者的优点:

  1. ArUco 部分负责插值棋盘格角点的位置,因此它继承了标记板的灵活性——允许遮挡与部分视野;
  2. 插值出的角点从属于棋盘格结构,因此天然具备很高的亚像素精度。

当精度成为首要诉求时(典型场景就是相机标定),ChArUco 板是比普通 ArUco 板更优的选择。这也是 charuco_detector.hpp 中对CharucoBoard的官方注释:它"同时提供 ArUco 标记的通用性与棋盘格角点的精度,对标定与位姿估计至关重要"。

本教程的目标

  • 如何创建一个 ChArUco 板?
  • 如何在不进行相机标定的情况下检测 ChArUco 角点?
  • 如何在携带相机标定参数的情况下检测 ChArUco 角点并完成位姿估计?

示例代码位置与运行环境

ChArUco 全套示例位于仓库的samples/cpp/tutorial_code/objectDetection/目录下,其中本文涉及两个核心程序:

文件作用
create_board_charuco.cpp生成一张可打印的 ChArUco 板图片
detect_board_charuco.cpp读取图像/视频/相机,检测 ChArUco 角点并估计位姿

两个程序均通过cv::CommandLineParser解析命令行参数,并共用工具头文件 aruco_samples_utility.hpp(内含相机参数读取、字典解析等辅助函数)。

ChArUco 相关的公共 API 声明集中在两个头文件中,均需包含到你的工程里:

// 使用 ChArUco 板必须包含的头文件 #include <opencv2/objdetect/charuco_detector.hpp> #include <opencv2/highgui.hpp> #include <opencv2/imgproc.hpp> #include <opencv2/calib3d.hpp> // solvePnP、FileStorage 所在模块

ChArUco 板的创建(Board Creation)

构造所需的五个要素

aruco模块提供cv::aruco::CharucoBoard类表示 ChArUco 板,它继承自cv::aruco::Board。定义一个CharucoBoard需要:

  • X、Y 两个方向上的棋盘格方块数
  • 方块边长(square side length);
  • 标记边长(marker side length);
  • 标记使用的字典(dictionary);
  • 所有标记的id

cv::aruco::GridBoard一样,模块提供了非常简洁的构造方式。从 create_board_charuco.cpp 中可见其用法:

cv::aruco::CharucoBoard board(Size(squaresX, squaresY), (float)squareLength, (float)markerLength, dictionary);

参数含义与注意事项:

  • 第一个参数Size(squaresX, squaresY):X、Y 方向上的棋盘格方块数。注意这与标定中常用的"内角点数"概念不同,这里给的是完整方块数。
  • 第二、三个参数(squareLength/markerLength:方块边长与标记边长,二者必须使用同一单位(通常用米)。此单位将直接决定后续估计出的位姿平移量的量纲。
  • 最后一个参数dictionary:标记字典。标记会按字典顺序嵌入棋盘的白色方块中。
  • id:默认按从 0 开始的升序自动分配,与cv::aruco::GridBoard构造行为一致;若需自定义,可直接访问父类Boardboard.ids向量修改。构造函数的ids形参默认为空(noArray()),传入自定义 id 数组亦可,详见 aruco_board.hpp。

将板渲染为可打印图片

拿到CharucoBoard对象后,有两种方式生成打印用的板图:

  1. 使用脚本apps/pattern-tools/generate_pattern.py(同时可输出 DICT JSON 与 SVG,仓库apps/pattern-tools/目录下已预置各尺寸的 DICT_4X4/5X5/6X6/7X7、ARUCO_ORIGINAL、APRILTAG、MIP 等字典压缩包,适合大批量/离线生成);
  2. 调用cv::aruco::CharucoBoard::generateImage(),这是最直接的代码方式。

generateImage()的调用方式同样取自 create_board_charuco.cpp:

Mat boardImage; Size imageSize; imageSize.width = squaresX * squareLength + 2 * margins; // 外边距左右各一份 imageSize.height = squaresY * squareLength + 2 * margins; board.generateImage(imageSize, boardImage, margins, borderBits);

参数说明:

参数含义
outSize(此处为imageSize输出图像尺寸(像素)。若不与板尺寸成比例,板会被居中绘制在图像中央。样本中把尺寸设为"板的总边长 + 2 倍边距",恰好严丝合缝
img输出的板图Mat
marginSize可选外边距(像素),保证任何标记都不贴到图像边缘;样本默认取squareLength - markerLength
borderBits标记黑色边框占的位(模块)数,语义与cv::aruco::generateImageMarker()一致,默认 1

生成出的板图大致如下(此处为仓库教程目录内的样例图):

命令行生成示例

create_board_charuco.cpp 的全部可选参数如下,程序会在未传参时打印用法:

const char* keys = "{@outfile |res.png| Output image }" "{w | 5 | Number of squares in X direction }" "{h | 7 | Number of squares in Y direction }" "{sl | 100 | Square side length (in pixels) }" "{ml | 60 | Marker side length (in pixels) }" "{d | | dictionary: ... }" "{cd | | Input file with custom dictionary }" "{m | | Margins size (in pixels). Default is (squareLength-markerLength) }" "{bb | 1 | Number of bits in marker borders }" "{si | false | show generated image }";

官方教程给出的调用示例为(w/h/sl/ml此处以像素为单位,-d=10对应DICT_6X6_250):

_output_path_/chboard.png -w=5 -h=7 -sl=100 -ml=60 -d=10

-d字典 id 与字典名的完整映射见样本源码注释(DICT_4X4_50=0 … DICT_ARUCO_MIP_36h12=21);若都不传-d且不传-cd,aruco_samples_utility.hpp 中的辅助函数会默认选用DICT_4X4_50

ChArUco 板角点检测(Detection)

检测的本质与总体流程

检测 ChArUco 板,实际检测的是板上每一个棋盘格内角点(chessboard corners)。板上每个角点都有唯一 id,从 0 递增到板的总角点数减一。

官方教程把整条流水线拆成如下几个阶段,以下逐一展开:

① 读取输入图像。detect_board_charuco.cpp 中从VideoCapture取帧。这张原始图像必须保留,因为后续要对 ChArUco 角点做亚像素细化,细化过程需要原始灰度纹理信息。

② 读取相机标定参数(仅在带标定的检测流程中需要)。核心是 aruco_samples_utility.hpp 中的readCameraParamsFromCommandLinereadCameraParameters

bool readOk = readCameraParameters(parser.get<std::string>("c"), camMatrix, distCoeffs);

readCameraParameters通过cv::FileStorage从 yml 中读取键camera_matrixdistortion_coefficients,返回布尔值表示标定参数是否有效。若仅做无标定角点检测,这一步可以完全跳过。

③ 检测 ArUco 标记,并从标记插值出 ChArUco 角点。ChArUco 角点的检测建立在已检出的 ArUco 标记之上:先检标记,再从标记反推棋盘角点。这一步由cv::aruco::CharucoDetector::detectBoard()完成,是整套流程的枢纽:

// 组装 detector:板 + ChArUco 参数 + ArUco 标记检测参数 aruco::CharucoBoard charucoBoard(Size(squaresX, squaresY), squareLength, markerLength, dictionary); aruco::CharucoParameters charucoParams; charucoParams.tryRefineMarkers = refine; // 为 true 时 detectBoard 内部会先做 refineDetectedMarkers() charucoParams.cameraMatrix = camMatrix; // 可为 detectBoard() 提供相机内参 charucoParams.distCoeffs = distCoeffs; // 可为 detectBoard() 提供畸变系数 aruco::CharucoDetector charucoDetector(charucoBoard, charucoParams, detectorParams); // 逐帧检测:同时输出 ChArUco 角点与中间层 ArUco 标记结果 charucoDetector.detectBoard(image, charucoCorners, charucoIds, markerCorners, markerIds);

注意CharucoDetector在构造函数阶段就一次性绑定板与全部参数(板、CharucoParametersDetectorParameters,以及可选的RefineParameters),见 charuco_detector.hpp;循环体内只调用detectBoard(),因此多帧/视频场景下每帧的额外开销很小。

detectBoard() 的参数语义

依据 charuco_detector.hpp 的函数签名:

参数类型含义
imageInputArray输入图像,用于亚像素细化(以及必要时内部触发detectMarkers()
charucoCornersOutputArray检出的角点图像坐标列表
charucoIdsOutputArraycharucoCorners一一对应的角点 id
markerCornersInputOutputArrayOfArrays检测到的标记角点(输入/输出均可)
markerIdsInputOutputArray检测到的标记 id

关键行为(header 注释与官方文档双重确认):

  • 若传入的markerCorners/markerIds为空,函数会先自动执行 ArUco 标记检测再插值;
  • 若提供了相机标定参数CharucoParameters中带cameraMatrix/distCoeffs),角点插值走"先由 ArUco 标记粗估计位姿、再把 ChArUco 角点反投影回图像"的路线;
  • 若未提供标定参数,插值改走"计算 ChArUco 平面与其图像投影之间的单应(homography)"的路线;
  • 只返回"可见"的角点,即仅返回其周围标记确实被检测到的那些角点。

单应 vs 反投影:两种插值方式的取舍

两种插值策略在 charuco_detector.hpp 中被明确注释为:

If camera parameters are provided, the process is based in an approximated pose estimation, else it is based on local homography. Only visible corners are returned.

  • 单应(homography)路线(无标定):无需内参即可工作,但对图像畸变更敏感。为抑制畸变影响,算法只使用每个 ChArUco 角点邻近的标记来计算局部单应,而非整板一张单应。
  • 近似位姿 + 反投影路线(带标定):需要相机内参与畸变系数,但能抵消镜头畸变,精度更高,是标定/精密测量的首选。

影响插值质量的三个关键细节

(1)建议关闭 ArUco 标记的角点细化。在 ChArUco 检测(尤其走单应路线)中,官方文档明确建议禁用标记的亚像素细化:因为棋盘方块彼此贴得很近,对标记做亚像素细化反而可能产生明显的角点偏移,且这种偏移会传播到 ChArUco 角点插值结果中,导致整体精度变差。示例程序中通过-rs开关控制charucoParams.tryRefineMarkers,默认关闭。

(2)棋盘方块与标记之间要保持足够边距。官方注释给出量化建议:方块边与标记之间的空白应大于一个标记模块(module)的 70%,否则插值易受干扰产生偏差。

(3)只信任"两个环绕标记都被检出"的角点。每个 ChArUco 角点被若干方块围住。只有当其周围标记被全部/足够检测到时该角点才会被输出。若某个角点的环绕标记有缺失,通常意味着该区域存在遮挡或图像质量不佳——此时宁可丢弃该角点,也要保证输出的插值角点"高度可信"。这正是CharucoParametersminMarkers字段(默认 2,表示至少需要检测到多少相邻标记才返回该角点)与checkMarkers字段(默认 true,校验标记确属同一板)的作用,见 charuco_detector.hpp。

角点插值完成后,算法还会自动执行一次亚像素细化以收敛到棋盘角点的最优位置。

绘制检测结果

插值完成后,用cv::aruco::drawDetectedCornersCharuco()即可把角点叠加画回图像上(默认颜色为Scalar(255, 0, 0)即蓝色,可自定义):

aruco::drawDetectedCornersCharuco(imageCopy, charucoCorners, charucoIds, cv::Scalar(255, 0, 0));

参数说明(与 charuco_detector.hpp 一致):

  • image:输入/输出图像(需为 1 或 3 通道),通常是检测用的原图副本;
  • charucoCorners/charucoIdsdetectBoard()的输出;
  • cornerColor:可选角点/序号绘制颜色。

对下面这张原始输入图:

检测并绘制后的结果大致为:

而当场景存在遮挡时(仓库还提供了一张带遮挡的样张chocclusion.jpg),尽管部分角点清晰可见,只要其环绕标记因遮挡未被检出,这些角点便不会被插值输出——这正是 ChArUco 插值"宁可少、不可错"策略的直观体现。教程还提供了完整示例视频Nj44m_N_9FY可对照观察。

检测程序的命令行用法

detect_board_charuco.cpp 除-w/-h/-sl/-ml/-d/-c/-cd外还支持以下开关:

"{c | | Output file with calibrated camera parameters }" // 严格说此处为输入标定文件 "{v | | Input from video or image file, if ommited, input comes from camera }" "{ci | 0 | Camera id if input doesnt come from video (-v) }" "{dp | | File of marker detector parameters }" "{rs | | Apply refind strategy }"

官方教程给出的无标定角点检测示例(注意此时sl/ml为单位,配合示例图片):

-w=5 -h=7 -sl=0.04 -ml=0.02 -d=10 -v=doc/tutorials/objdetect/charuco_detection/images/choriginal.jpg

程序运行时会周期性打印每帧检测耗时(毫秒)与均值,便于在视频/实况场景中评估性能。仓库目录doc/tutorials/objdetect/charuco_detection/images/中可找到choriginal.jpgchocclusion_original.jpg等测试原图直接用于复现。

ChArUco 板的位姿估计(Pose Estimation)

坐标系约定

ChArUco 板做位姿估计的最终目标是高精度标定或位姿解算。与cv::aruco::GridBoard一致,CharucoBoard的坐标系定义在板平面内,Z 轴指向板平面内侧(朝向纸张内部),原点位于板的左下角

重要兼容性提示(OpenCV 4.6.0 起的破坏性变更):4.6.0 之后板的坐标系发生了不兼容改动——Z 轴从"指向平面外侧"改为"指向平面内侧"。判断方法:objPoints按**顺时针(CW)排列时对应 Z 轴向内,按逆时针(CCW)**排列时对应 Z 轴向外。因此,若你在 4.6.0 前后混用标定数据或手写对象点,务必核对绕序,否则位姿符号会翻转。

使用 matchImagePoints + solvePnP 估计位姿

位姿估计的推荐姿势是cv::aruco::CharucoBoard::matchImagePoints()(自Board父类继承)配合cv::solvePnP()。示例程序中的调用位于 detect_board_charuco.cpp:

bool validPose = false; if (camMatrix.total() != 0 && distCoeffs.total() != 0 && charucoIds.size() >= 4) { Mat objPoints, imgPoints; charucoBoard.matchImagePoints(charucoCorners, charucoIds, objPoints, imgPoints); validPose = solvePnP(objPoints, imgPoints, camMatrix, distCoeffs, rvec, tvec); }

要点:

  • matchImagePoints()负责把"检出的 2D 角点 + id"换算成对应的3D 对象点objPoints)与2D 图像点imgPoints),是 ChArUco 位姿估计的核心桥梁,定义见 aruco_board.hpp;
  • cameraMatrixdistCoeffs来自readCameraParameters读入的标定文件,位姿估计必须携带标定参数(无标定模式只能输出 2D 角点);
  • rvec/tvec为输出的旋转向量与平移向量;
  • 示例程序还设了charucoIds.size() >= 4的门槛:角点太少不足以稳定解算;
  • cv::solvePnP()返回布尔值表示是否成功;失败的主因通常是角点数不足,或所有角点共线(几何退化,无法唯一确定姿态)。

绘制坐标轴验证结果

位姿是否正确,可调用cv::drawFrameAxes()把坐标轴画到图像上目视验证。示例中坐标轴长度取棋盘短边的一半(0.5 * min(squaresX, squaresY) * squareLength):

if (validPose) cv::drawFrameAxes(imageCopy, camMatrix, distCoeffs, rvec, tvec, axisLength);

渲染规则为 X 轴红色、Y 轴绿色、Z 轴蓝色,效果如下:

带标定参数的完整命令行示例

仓库在 samples/cpp/tutorial_code/objectDetection/ 目录提供了配套的标定结果文件tutorial_camera_charuco.ymlsamples/data/aruco/下亦有一份),内含camera_matrixdistortion_coefficients节点,格式与readCameraParameters的读取键完全对应。官方示例的完整调用为:

-w=5 -h=7 -sl=0.04 -ml=0.02 -d=10 \ -v=doc/tutorials/objdetect/charuco_detection/images/choriginal.jpg \ -c=samples/cpp/tutorial_code/objectDetection/tutorial_camera_charuco.yml

与标定流程的衔接及延伸

本教程完整覆盖了 ChArUco 的"生成 → 检测 → 位姿"闭环,在仓库samples/cpp/tutorial_code/objectDetection/中还提供了一脉相承的进阶示例:

  • calibrate_camera_charuco.cpp:基于 ChArUco 板的多帧相机标定——正好把本文检测到的高精度角点批量喂给标定函数,是 ChArUco 最典型的落地场景;
  • detect_board.cpp 与 create_board.cpp:标准 ArUco 板的对照实现(对应官方教程序列中的前一/后一篇,前者介绍 ArUco 板检测,后者介绍 ChArUco Diamond 检测,位于doc/tutorials/objdetect/教程目录)。

一句话总结适用决策:追求亚像素级角点精度并愿意接受"只返回被充分环绕标记支撑的角点"这一约束时,选 ChArUco;反之若需要更多标记同时入镜、对绝对精度要求不高,普通 ArUco 板仍是更轻量的选择。

【免费下载链接】opencvOpen Source Computer Vision Library项目地址: https://gitcode.com/GitHub_Trending/opencv31/opencv

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/8 22:31:54

架构图与流程图设计实战:从受众分析到工具选型的完整指南

做了这么多年技术方案和产品梳理&#xff0c;我越来越觉得&#xff0c;画图这件事被很多人低估了。diagram-design 听起来只是"把东西画出来"&#xff0c;可真正上手你会发现&#xff0c;有人三分钟画出一张别人看不懂的图&#xff0c;也有人花三个小时磨出一张能让评…

作者头像 李华
网站建设 2026/9/8 22:30:01

微信小程序商城源码实战:从解压调试到支付上线的完整避坑指南

简介&#xff1a;面向小型团队与个人开发者的微信小程序商城源码&#xff0c;是一套基于PHPMySQL的前后端全开源电商解决方案。系统涵盖分销、拼团、抽奖、红包、多店运营、会员管理、种草社交与新零售O2O场景&#xff0c;架构简明&#xff0c;采用MVC与RESTful API设计&#x…

作者头像 李华