【Python】OpenCV ArUco 使用指南
1. ArUco 介绍
ArUco 是一种方形人工视觉标记(fiducial marker)。
它不是用于存放网址或长文本的通用二维码,而是为了让计算机稳定地获得以下信息:
- 这个标记是谁;
- 它在图像中的什么位置:四个有序角点和中心点;
- 它朝向哪里:编码可以消除 0°、90°、180°、270°之间的旋转歧义;
- 它相对相机的三维位置和姿态:在已知标记尺寸及相机内参时,通过 PnP 求解。
ArUco 是机器人视觉中常用的技术栈,其原始项目是一个基于 OpenCV 的独立 C++ 库,后来其核心能力进入 OpenCV 的aruco模块。
典型处理链如下:
相机图像 ↓ 检测候选方形轮廓 ↓ 透视展开并读取内部黑白编码 ↓ 在指定字典中识别 ID 和旋转方向 ↓ 获得 ID、四个有序角点、中心点 ↓(可选:已知相机内参和标记尺寸) solvePnP ↓ 获得标记相对相机的三维旋转和平移2. 一个 ArUco 码长什么样
一个标准 ArUco 标记包含:
- 正方形外形;
- 连续的黑色边框,用于快速寻找候选轮廓;
- 内部
N × N黑白编码单元; - 标记外部的白色留白,用于与背景分离。
以DICT_4X4_50为例,4X4表示内部编码矩阵为 4×4,而50表示该字典中共有 50 个合法 ID,即0~49。黑色外框不计入 4×4 的编码矩阵。
需要特别注意:字典名是编码协议的一部分。DICT_4X4_50的 ID 0 和其他字典的 ID 0 并不是同一个图案。生成端与检测端必须选择完全相同的字典。
3. ArUco 是如何工作的
OpenCV 的经典检测过程可以简化为两个阶段。
3.1 定位
检测器先对图像进行自适应阈值处理,再寻找轮廓,并排除不满足要求的候选,例如:
- 不是凸四边形;
- 面积过小或过大;
- 四个角距离不合理;
- 距离图像边缘太近;
- 与其他候选过于接近。
这一步只说明“它看起来像一个方形标记”,还不能确定它是合法 ArUco。
3.2 解码
对每个候选四边形,检测器会:
- 通过透视变换把候选区域展开成正方形;
- 对展开图进行二值化;
- 按编码网格读取每个黑白单元;
- 检查黑色边框;
- 将读取结果的四种旋转与字典中的合法编码比较;
- 根据汉明距离完成识别和有限的错误校正。
由于会比较四种旋转,检测器不仅能得到 ID,还能恢复标记的标准方向。OpenCV 随后按标记自身的标准方向返回四个角:
0:标准左上角 1:标准右上角 2:标准右下角 3:标准左下角4. 携带信息
ArUco 内部编码的主要输出是字典 ID。业务层一般建立一张映射表:
MARKER_TYPES={0:"机器人基座",1:"抓取工位",2:"放置工位",3:"工件A",4:"工件B",}这种做法的优势是编码紧凑、解码稳健,适合数量有限且含义预先约定的对象。其限制也很明确:
- 可以表达有限的 ID 或类型;
- 不能像 QR Code 一样直接保存网址、JSON 或长序列号;
- 业务系统必须知道“字典 + ID”与业务含义的映射;
- 如果映射会变更,应在配置或数据库中维护,而不要散落在算法代码中。
如果现场需要标记直接携带较长信息,可以让二维码负责业务数据,另加 ArUco/AprilTag 负责高效定位;也可以只在标签上打印短 ID,再由系统查询数据库。
OpenCV 提供了多组预定义字典,常见的有:
| 字典 | 内部网格 | ID 数量 | 适合场景 |
|---|---|---|---|
DICT_4X4_50 | 4×4 | 50 | 类型不多、标记较小、入门验证 |
DICT_4X4_100 | 4×4 | 100 | 需要 50~100 个 ID |
DICT_5X5_100 | 5×5 | 100 | 希望比 4×4 有更大的编码区分空间 |
DICT_6X6_250 | 6×6 | 250 | ID 较多、图像分辨率和标记尺寸充足 |
DICT_7X7_1000 | 7×7 | 1000 | 大规模编号,要求每个单元仍有足够像素 |
DICT_ARUCO_ORIGINAL | 原始 ArUco | 1024 | 兼容历史项目 |
DICT_APRILTAG_36h11 | AprilTag 族 | 587 | 用 OpenCV API 检测 AprilTag 编码 |
选择原则不是“位数越高越好”,而是:
- 先确定业务所需的 ID 数量;
- 选择能容纳这些 ID 的尽量小的字典;
- 再根据打印尺寸、最远距离和相机分辨率决定网格大小;
- 用真实相机和真实光照做检出率、误识别率及角点抖动测试。
同样的物理尺寸下,7×7 的单元比 4×4 更小。远距离成像时,每个编码单元可能只剩很少像素,网格更多反而可能更难识别。OpenCV 官方也建议选择能够满足应用规模的较小字典,因为较小字典通常可以保留更好的码间距离。
5. 开发测试
5.1 运行环境
先检查当前 OpenCV 是否包含aruco:
importcv2print(cv2.__version__)print(hasattr(cv2,"aruco"))print(hasattr(cv2.aruco,"ArucoDetector"))本文使用环境的输出为:
4.13.0 True True一些较早的 Python 安装只包含基础 OpenCV,可能没有cv2.aruco。项目已有固定环境时,不应为解决这一问题直接升级或混装依赖,应先核对现有依赖约束和部署环境。
5.2 生成码
下面生成DICT_4X4_50字典中的 ID 0:
frompathlibimportPathimportcv2 output_path=Path("aruco_dict4x4_50_id0.png")dictionary=cv2.aruco.getPredefinedDictionary(cv2.aruco.DICT_4X4_50)marker=dictionary.generateImageMarker(0,800)# 白边不属于 ArUco 编码,但有利于黑色外框与环境分离。printable=cv2.copyMakeBorder(marker,120,120,120,120,cv2.BORDER_CONSTANT,value=255)ifnotcv2.imwrite(str(output_path),printable):raiseRuntimeError(f"保存标记失败:{output_path}")参数含义:
DICT_4X4_50:使用的字典;0:字典中的 ID;800:生成图像中黑色标记主体的边长,单位是像素;120:额外添加的白色留边,打印尺寸应另行控制。
不要从网页截图后打印,也不要用会产生模糊插值的方式缩放。用于测量时,优先输出足够分辨率的图像或矢量标定板,并关闭打印软件的“适应页面”“缩放填充”等选项。
5.3 检测示例
importcv2 image=cv2.imread("camera_image.png")ifimageisNone:raiseFileNotFoundError("camera_image.png")dictionary=cv2.aruco.getPredefinedDictionary(cv2.aruco.DICT_4X4_50)parameters=cv2.aruco.DetectorParameters()detector=cv2.aruco.ArucoDetector(dictionary,parameters)corners,ids,rejected=detector.detectMarkers(image)ifidsisNone:print("没有检测到 ArUco 标记")else:formarker_id,marker_cornersinzip(ids.flatten(),corners):points=marker_corners.reshape(4,2)print(f"ID={int(marker_id)}")print(points)cv2.aruco.drawDetectedMarkers(image,corners,ids)cv2.imwrite("detection_result.png",image)三个输出分别是:
corners:每个已识别标记的四个有序角点;ids:与corners一一对应的 ID;rejected:外观像方形标记、但未通过字典解码的候选区域,主要用于调试。