首页
/ OpenCV 中 ChArUco Diamond Marker(菱形标靶)的生成、检测与位姿估计完全指南

OpenCV 中 ChArUco Diamond Marker(菱形标靶)的生成、检测与位姿估计完全指南

2026-09-07 12:02:59作者:韦蓉瑛

ChArUco diamond marker(菱形标靶)是 OpenCV objdetect 模块中一类基于 ChArUco 棋盘格的复合标靶:它由 3×3 棋盘格方块与内嵌于白色方块中的 4 个 ArUco 标码构成。与普通 ChArUco board 不同,菱形标靶不依赖标码 ID 来判属,而是依据 4 个标码的相对几何位置完成识别,从而允许 ID 重复复用,常用于需要多目标同图、逐目标差异化定制的标定与位姿估计场景。本文以 charuco_diamond_detection.markdown 为核心脉络,结合仓库内 create_diamond.cppdetect_diamonds.cpp 完整源码,系统讲解 Diamond 标靶的生成、检测、绘制、位姿估计及命令行实战配置,并剖析其与 ChArUco board 在原理上的关键差异。

什么是 Diamond Marker:与 ChArUco Board 的本质差异

一个 ChArUco diamond marker 本质上是一个由 3×3 个棋盘方块 和位于白色方块内的 4 个 ArUco 标码 组成的复合标靶。外观上它与 ChArUco board 几乎一样,但两者的检测逻辑截然不同

对比维度 ChArUco Board Diamond Marker(菱形标靶)
判属依据 直接查看标码 ID,命中即归属 board 依据 4 个标码的相对位置进行几何推断
ID 是否可重复 同一图像中出现多个同 ID 标码会产生歧义 ID 可在同一 diamond 内或不同 diamond 间重复,仍可无歧义同时检出
尺寸约束 可为任意 N×M 棋盘 固定为 3×3 方块 + 4 个标码(由相对位置搜索的复杂度决定)
返回结果 棋盘角点与 board 角点 ID 4 个角点 + 1 个 Vec4i(4 个内部标码的 ID 数组)

正因检测基于相对位置而非 ID,diamond 的四个内部标码 ID 可以完全重复(例如四角都用同一个 ID 的标码),这在实际工程中是 ChArUco board 做不到的。

与单个 ArUco marker 类似,每个 diamond 由 4 个角点(即 4 个棋盘外角点)和 1 个标识符组成;其标识符并非单个整数,而是 4 个内部 ArUco 标码 ID 组成的 cv::Vec4i 数组。由于角点本身就是棋盘方格角点,天然适合用于高精度位姿估计。菱形标靶相关的全部功能都封装在头文件 <opencv2/objdetect/charuco_detector.hpp> 中(见 modules/objdetect/include/opencv2/objdetect/charuco_detector.hpp)。

应用场景

  1. 扩展编码容量:若单个标码字典含 N 个标码,使用 diamond 作为标签可提供最多 N^4 个不同 ID 组合,远超大尺寸字典的直接数量上限。
  2. 标码的语义化分工:例如约定 4 个标码中的某一个 ID 表示该 diamond 的物理尺寸(方块边长),于是同一布局的 diamond 可以以多种尺寸出现在环境中,且只靠更换某一个标码即可标识不同尺度——无需用户手工逐靶标明尺寸。该用法正是 detect_diamonds.cpp-as(自动缩放)参数所演示的用例。

Diamond Marker 的创建

使用 create_diamond.cpp 可以快速生成菱形标靶图片。其核心是构造一个 cv::aruco::CharucoBoard(尺寸固定为 Size(3, 3)),并传入 4 个内部标码 ID,随后调用 generateImage() 渲染:

//! [generate_diamond]
vector<int> diamondIds = {ids[0], ids[1], ids[2], ids[3]};
aruco::CharucoBoard charucoBoard(Size(3, 3), (float)squareLength, (float)markerLength, dictionary, diamondIds);
Mat markerImg;
charucoBoard.generateImage(Size(3*squareLength + 2*margins, 3*squareLength + 2*margins), markerImg, margins, borderBits);
//! [generate_diamond]

要点说明:

  • 尺寸关系sl(squareLength,方块边长)与 ml(markerLength,标码边长)均以像素为单位;示例中 sl=200ml=120,即生成方块边长 200 px、标码边长 120 px 的标靶。实际打印时必须保证 ml < sl 使标码完整嵌入白色方块内部。
  • ID 顺序diamondIds 中 4 个 ID 的排列顺序与标准 ChArUco board 一致,即 上(top)、左(left)、右(right)、下(bottom)(对应代码注释语义),且 ids 必须正好给出 4 个值,否则源码会抛出 std::runtime_error("Incorrect ids format\n")
  • 输出尺寸:生成图像宽高为 3*squareLength + 2*margins,其中 margins 为外留白像素(-m 参数),borderBits 控制标码边框位数(-bb 参数,默认 1)。
  • 字典选择:通过 -d 指定内置字典编号(0~21,如 DICT_4X4_50=0DICT_6X6_250=10 为默认值),也可用 -cd 传入自定义字典 YAML 文件。

生成的标靶示例图见 diamondmarker.png

由 create_diamond.cpp 生成的单个 ChArUco diamond 标靶示例

命令行示例

create_diamond.cpp 使用 cv::CommandLineParser 解析参数,典型调用为:

_path_/create_diamond _path_/mydiamond.png -sl=200 -ml=120 -d=10 -ids=0,1,2,3

对应参数含义:输出文件 mydiamond.png、方块边长 200 px、标码边长 120 px、字典 DICT_6X6_250(d=10)、内部标码 ID 依次为 0、1、2、3。可通过 -m(留白)、-bb(边框位)、-si(是否弹出显示生成结果)进一步定制。

Diamond Marker 的检测

与其他 ArUco 类功能一致,diamond 检测同样需要先检测内部 ArUco 标码,再由标码间的相对几何关系推导 diamond。核心 API 为 cv::aruco::CharucoDetector::detectDiamonds()detect_diamonds.cpp 中的调用方式如下:

//! [detect_diamonds]
vector<int> markerIds;
vector<Vec4i> diamondIds;
vector<vector<Point2f> > markerCorners, diamondCorners;
vector<Vec3d> rvecs, tvecs;

detector.detectDiamonds(image, diamondCorners, diamondIds, markerCorners, markerIds);
//! [detect_diamonds]

参数行为:

  • image:输入原始图像。即便上层已经传入了 markerCorners/markerIds,原图仍不可省略——因为棋盘角点的亚像素(subpixel)细化需要原图像素数据。
  • markerCornersmarkerIds:若为空,函数会在内部自动完成 ArUco 标码检测(按头文件源码注释,若提供了相机标定参数则基于反投影 reprojection 搜索,否则基于单应 homography——后者更快但精度略低);若不空,则直接用已检出的标码结果,仅执行 diamond 推断。
  • diamondCorners:输出,每个检测到的 diamond 的 4 个角点,格式与 ArucoDetector::detectMarkers() 输出一致,角点按顺时针从左上角开始排列。
  • diamondIds:输出,每个角点对应的 ID 数组,类型为 4 元素数组,可用 cv::Vec4i 表示。

上层检测器通过 CharucoBoardSize(3,3) + 真实尺寸 squareLength/markerLength + 字典)与 CharucoParametersDetectorParameters 构造:

aruco::CharucoBoard charucoBoard(Size(3, 3), squareLength, markerLength, dictionary);
aruco::CharucoDetector detector(charucoBoard, aruco::CharucoParameters(), detectorParams);

注意:此处构造 CharucoBoard不传 diamondIds,位姿估计用的物方点坐标直接由 squareLength(真实方块边长)推导,见下文。从 CharucoParameters 源码默认值可知,其内部 minMarkers=2tryRefineMarkers=falsecheckMarkers=true,这些参数会参与 ChArUco 角点插值判定。

结果可视化

检测结果可用 cv::aruco::drawDetectedDiamonds() 绘制,效果等价于 drawDetectedMarkers(),但会在每个 diamond 旁打印其 4 个内部标码 ID:

//! [draw_diamonds]
if(diamondIds.size() > 0) {
    aruco::drawDetectedDiamonds(imageCopy, diamondCorners, diamondIds);
//! [draw_diamonds]

该函数在 charuco_detector.hpp 中声明为 drawDetectedDiamonds(image, diamondCorners, diamondIds = noArray(), borderColor = Scalar(0,0,255)),默认红色边框,ID 缺省可不绘。

三个菱形标靶同时被检出并以方框与四元 ID(如 id=[10,4,11,12])标注的检测结果

命令行示例

detect_diamonds.cpp 的命令行参数覆盖检测、细化与字典配置:

-dp=path_to_opencv/samples/cpp/tutorial_code/objectDetection/detector_params.yml -sl=0.4 -ml=0.25 -refine=3
-v=path_to_opencv/doc/tutorials/objdetect/charuco_diamond_detection/images/diamondmarkers.jpg
-cd=path_to_opencv/samples/cpp/tutorial_code/objectDetection/tutorial_dict.yml

其中 -sl-ml 在本例中为真实物理尺寸(米为单位,square=0.4 m、marker=0.25 m),两者比值是相对位置推断与 ChArUco 角点插值的必需输入。-dp 指向 detector_params.yml,其中包含标码检测全套参数,如 adaptiveThreshWinSizeMin/Max/Step(自适应阈值窗口 3/23/步长 10)、minMarkerPerimeterRate=0.03polygonalApproxAccuracyRate=0.05cornerRefinementMethod=0 及 ArUco 3 的 useAruco3Detection=0minSideLengthCanonicalImg=32minMarkerLengthRatioOriginalImg=0.02cameraMotionSpeed=0.1 等(对应 ArUco 3 论文中的 τc/τi/τs 阈值参数);-refine=3 表示将角点细化方法覆盖为 CORNER_REFINE_APRILTAG(取值范围:NONE=0、SUBPIX=1、CONTOUR=2、APRILTAG=3,源码在读取 -refine 后直接覆写 detectorParams.cornerRefinementMethod 并打印所采用的细化方式)。-cd 指定自定义字典,tutorial_dict.yml 是一个含 35 个 6×6 标码、直接以二进制串描述每个 marker 位型的字典示例。若省略 -v 则默认从摄像头(-ci 指定编号)实时采集。

Diamond Marker 的位姿估计

Diamond 的 4 个角点即棋盘方格角点,因此其位姿估计与单个 ArUco marker 一致,使用 cv::solvePnP() 完成。注意此时必须提供方块边长 squareLength 而非标码边长,同时需要相机内参与畸变系数。参考 detect_diamonds.cpp

//! [diamond_pose_estimation]
// estimate diamond pose
size_t N = diamondIds.size();
if(estimatePose && N > 0) {
    cv::Mat objPoints(4, 1, CV_32FC3);
    rvecs.resize(N);
    tvecs.resize(N);
    if(!autoScale) {
        // set coordinate system
        objPoints.ptr<Vec3f>(0)[0] = Vec3f(-squareLength/2.f, squareLength/2.f, 0);
        objPoints.ptr<Vec3f>(0)[1] = Vec3f(squareLength/2.f, squareLength/2.f, 0);
        objPoints.ptr<Vec3f>(0)[2] = Vec3f(squareLength/2.f, -squareLength/2.f, 0);
        objPoints.ptr<Vec3f>(0)[3] = Vec3f(-squareLength/2.f, -squareLength/2.f, 0);
        // Calculate pose for each marker
        for (size_t i = 0ull; i < N; i++)
            solvePnP(objPoints, diamondCorners.at(i), camMatrix, distCoeffs, rvecs.at(i), tvecs.at(i));
//! [diamond_pose_estimation]

物方 4 点按顺时针从左上起分布在以 diamond 中心为原点的 XY 平面上,边长 squareLength。求解得到的旋转向量存入 rvecs、平移向量存入 tvecs。为验证位姿正确性,可调用 cv::drawFrameAxes() 在图像上叠加坐标轴:

//! [draw_diamond_pose_estimation]
if(estimatePose) {
    for(size_t i = 0u; i < diamondIds.size(); i++)
        cv::drawFrameAxes(imageCopy, camMatrix, distCoeffs, rvecs[i], tvecs[i], squareLength*1.1f);
}
//! [draw_diamond_pose_estimation]

对 diamond 求解位姿后绘制的坐标系轴(drawFrameAxes)效果

坐标系的约定与单 ArUco marker 位姿估计一致:原点位于 diamond 中心、Z 轴指向相机外(屏幕外方向)。OpenCV 官方提供了一段效果演示视频(对应 YouTube 视频 OqKpBnglH7k)。

以 ChArUco board 方式估计位姿

由于 diamond 本质就是 3×3 的 ChArUco board,其位姿也可以走 CharucoBoard 的角点匹配路径:先用 charucoBoard.matchImagePoints() 把图像角点与 board 坐标系角点配对,再交给 solvePnP

// estimate diamond pose as Charuco board
for (size_t i = 0ull; i < N; i++) { // estimate diamond pose as Charuco board
    Mat objPoints_b, imgPoints;
    // The coordinate system of the diamond is placed in the board plane centered in the bottom left corner
    vector<int> charucoIds = {0, 1, 3, 2}; // if CCW order, Z axis pointing in the plane
    // vector<int> charucoIds = {0, 2, 3, 1}; // if CW order, Z axis pointing out the plane
    charucoBoard.matchImagePoints(diamondCorners[i], charucoIds, objPoints_b, imgPoints);
    solvePnP(objPoints_b, imgPoints, camMatrix, distCoeffs, rvecs[i], tvecs[i]);
}

注意此种方式的坐标系约定与直接 solvePnP 不同:board 坐标系置于棋盘平面、以左下角为原点;且内部角点编号顺序 {0,1,3,2}(逆时针,Z 指向平面内)与 {0,2,3,1}(顺时针,Z 指向平面外)会改变 Z 轴朝向,使用时需按应用需求选择。

使用 -as 自动缩放:由末位 ID 反推物理尺寸

detect_diamonds.cpp 还演示了本文开头所述“用一个标码 ID 编码尺度”的完整落地:开启 -as(autoscale)后,-sl-ml 只用于表示方块与标码的相对比例,实际方块边长由第 4 个内部标码的 ID 决定:

// if autoscale, extract square size from last diamond id
for(size_t i = 0; i < N; i++) {
    float sqLenScale = autoScaleFactor * float(diamondIds[i].val[3]);
    ...
    objPoints.ptr<Vec3f>(0)[0] = Vec3f(-sqLenScale/2.f, sqLenScale/2.f, 0);
    objPoints.ptr<Vec3f>(0)[1] = Vec3f(sqLenScale/2.f, sqLenScale/2.f, 0);
    objPoints.ptr<Vec3f>(0)[2] = Vec3f(sqLenScale/2.f, -sqLenScale/2.f, 0);
    objPoints.ptr<Vec3f>(0)[3] = Vec3f(-sqLenScale/2.f, -sqLenScale/2.f, 0);
    solvePnP(objPoints, diamondCorners.at(i), camMatrix, distCoeffs, rvecs.at(i), tvecs.at(i));
}

其中 diamondIds[i].val[3] 即第 4 个(下)标码 ID,乘以 autoScaleFactor 后作为方块边长用于构建物方点。前提是生成 diamond 时已把该 ID 编码为实际尺度值,这正是“diamond 允许 ID 复用、可语义化”特性的直接收益。

完整位姿命令行示例

位姿估计需要额外提供相机内参文件,在上一节命令基础上追加 -c 即可:

-dp=path_to_opencv/samples/cpp/tutorial_code/objectDetection/detector_params.yml -sl=0.4 -ml=0.25 -refine=3
-v=path_to_opencv/doc/tutorials/objdetect/charuco_diamond_detection/images/diamondmarkers.jpg
-cd=path_to_opencv/samples/cpp/tutorial_code/objectDetection/tutorial_dict.yml
-c=path_to_opencv/samples/cpp/tutorial_code/objectDetection/tutorial_camera_params.yml

tutorial_camera_params.ymlcamera_matrix 为 3×3 双精度内参矩阵(fx=628.158、fy=628.156、cx=324.099、cy=260.908),distortion_coefficients 为 5 元畸变向量(k1=0.0995485、k2=-0.206384、p1=0.00754589、p2=0.00336531、k3=0),实际项目中应替换为自身相机标定结果。

源码 API 小结与延伸阅读

  • 全部核心 API 位于 charuco_detector.hppCharucoDetector::detectDiamonds()CharucoDetector 构造、自由函数 drawDetectedDiamonds(),以及 CharucoParametersminMarkerstryRefineMarkerscheckMarkers、可选的 cameraMatrix/distCoeffs)。
  • 创建标靶:参考 create_diamond.cpp(含 -sl/-ml/-d/-ids/-m/-bb/-cd 等全部参数解析)。
  • 检测与位姿估计:参考 detect_diamonds.cpp(含 -dp/-sl/-ml/-refine/-as/-v/-ci/-c/-cd 等全部参数解析、性能计时打印与结果绘制)。
  • 参数文件:标码检测参数 detector_params.yml、相机参数 tutorial_camera_params.yml、自定义字典示例 tutorial_dict.yml

配套学习建议:先阅读 ChArUco board 检测与 ArUco 相机标定两篇教程建立基础,再结合本文实现从“生成一张 diamond 图片 → 拍照检测 → 求解并验证位姿 → 接入 -as 自动缩放”的完整链路。真实场景中需注意打印平整度、光照均匀性与 sl/ml 比例(标码必须完整落在方块内),并务必使用经标定验证过的相机内参以获得可靠位姿。

登录后查看全文
热门项目推荐
相关项目推荐