【Python】OpenCV ArUco 使用指南
【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:
import cv2
print(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:
from pathlib import Path
import cv2
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)
if not cv2.imwrite(str(output_path), printable):
raise RuntimeError(f"保存标记失败:{output_path}")
参数含义:
DICT_4X4_50:使用的字典;0:字典中的 ID;800:生成图像中黑色标记主体的边长,单位是像素;120:额外添加的白色留边,打印尺寸应另行控制。
不要从网页截图后打印,也不要用会产生模糊插值的方式缩放。用于测量时,优先输出足够分辨率的图像或矢量标定板,并关闭打印软件的“适应页面”“缩放填充”等选项。
5.3 检测示例
import cv2
image = cv2.imread("camera_image.png")
if image is None:
raise FileNotFoundError("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)
if ids is None:
print("没有检测到 ArUco 标记")
else:
for marker_id, marker_corners in zip(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:外观像方形标记、但未通过字典解码的候选区域,主要用于调试。
DAMO开发者矩阵,由阿里巴巴达摩院和中国互联网协会联合发起,致力于探讨最前沿的技术趋势与应用成果,搭建高质量的交流与分享平台,推动技术创新与产业应用链接,围绕“人工智能与新型计算”构建开放共享的开发者生态。
更多推荐

所有评论(0)