AprilTag介绍
AprilTag介绍
1. 什么是 AprilTag
AprilTag 是一种广泛应用于机器人、无人机、增强现实和相机标定中的视觉基准标记系统。它由密歇根大学的 April Robotics Lab 提出,通过识别黑白方块组成的编码图案,可以在单张图像中快速、鲁棒地检测标签的 ID,并估计标签相对于相机的三维位置与姿态。
与常见的 QR 码、ArUco 码相比,AprilTag 具有以下优势:
- 检测速度快,适合实时视觉系统;
- 抗模糊、抗光照变化能力较强;
- 支持更小的标签尺寸,适用于高精度定位;
- 提供丰富的标签家族,如
tag36h11、tag25h9、tag16h5等。
2. AprilTag 定位的基本原理
AprilTag 的定位过程通常分为两个层次:检测与位姿估计。
2.1 标签检测
标签检测的目的是在图像中找到 AprilTag 的四边形轮廓,并识别出标签 ID。典型流程如下:
- 将图像转换为灰度图;
- 计算图像梯度,寻找边缘;
- 对边缘进行聚类,提取候选四边形;
- 对四边形区域进行透视变换,得到标准化的标签图像;
- 将标准化图像与已知标签编码库进行匹配,得到标签 ID。
2.2 位姿估计
在检测到标签的四个角点后,利用透视投影模型求解相机与标签之间的刚体变换。
设相机的内参矩阵为 K,标签上某点的世界坐标为 P_w,其对应的像素坐标为 p,则满足:
s * p = K * [R | t] * P_w
其中 R 是旋转矩阵,t 是平移向量,s 是尺度因子。
求解该问题的常用方法是 PnP,包括:
- DLT 直接线性变换;
- P3P;
- EPnP;
- 非线性优化。
AprilTag 定位中通常先使用 DLT 或 EPnP 给出初值,再通过 Levenberg-Marquardt 等方法进行非线性优化,以提高位姿精度。
3. 环境准备
本文以 Python 为例,使用 OpenCV 与 apriltag 库完成检测与定位。
安装依赖:
pip install opencv-python
pip install apriltag
如果你的 OpenCV 版本自带了 cv2.aruco,也可以用它实现类似功能。本文重点介绍 apriltag 库,因为它对 AprilTag 家族支持更完整。
验证安装:
import cv2
import apriltag
print("OpenCV 版本:", cv2.__version__)
print("AprilTag 库加载成功")
4. 相机内参标定
要获得真实世界的距离信息,必须先标定相机内参。相机内参描述了像素坐标系与相机坐标系之间的映射关系,主要包括焦距、主点和畸变系数。
使用 OpenCV 棋盘格标定的基本代码如下:
import cv2
import numpy as np
import glob
chessboard_size = (9, 6)
square_size = 0.025 # 棋盘格方块尺寸,单位:米
objp = np.zeros((chessboard_size[0] * chessboard_size[1], 3), np.float32)
objp[:, :2] = np.mgrid[0:chessboard_size[0], 0:chessboard_size[1]].T.reshape(-1, 2)
objp *= square_size
objpoints = []
imgpoints = []
images = glob.glob('calibration_images/*.jpg')
for fname in images:
img = cv2.imread(fname)
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
ret, corners = cv2.findChessboardCorners(gray, chessboard_size, None)
if ret:
objpoints.append(objp)
corners2 = cv2.cornerSubPix(
gray,
corners,
(11, 11),
(-1, -1),
(cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 30, 0.001),
)
imgpoints.append(corners2)
ret, mtx, dist, rvecs, tvecs = cv2.calibrateCamera(
objpoints, imgpoints, gray.shape[::-1], None, None
)
print("相机内参矩阵:")
print(mtx)
print("畸变系数:")
print(dist)
np.savez('camera_calibration.npz', mtx=mtx, dist=dist)
标定完成后,保存内参矩阵与畸变系数,后续定位时直接加载即可。
5. 生成与检测 AprilTag
5.1 生成 AprilTag 图片
可以使用 apriltag 库生成标签图片,方便打印后粘贴到目标物体上:
from apriltag import apriltag_generator
generator = apriltag_generator('tag36h11')
tag = generator.generate(tag_id=0)
tag.save('tag36h11_id0.png')
也可以使用在线工具生成 PNG 或 SVG 文件。实际使用时应选择合适的大小,通常标签边长在 5 到 20 厘米之间更容易获得稳定的检测与定位结果。
5.2 检测 AprilTag
读取图像并检测所有标签:
import cv2
import apriltag
image = cv2.imread('scene.jpg', cv2.IMREAD_GRAYSCALE)
options = apriltag.DetectorOptions(
families='tag36h11',
nthreads=4,
quad_decimate=1.0,
quad_sigma=0.0,
refine_edges=True,
decode_sharpening=0.25,
debug=False,
)
detector = apriltag.Detector(options)
results = detector.detect(image)
print(f"检测到 {len(results)} 个 AprilTag")
for r in results:
print(f"标签 ID: {r.tag_id}")
print(f"中心坐标: {r.center}")
print(f"四个角点: {r.corners}")
检测结果中的 corners 顺序通常为左下、右下、右上、左上,但在不同版本或库中可能有所不同。涉及位姿计算时,务必确认角点顺序与使用的 PnP 接口要求一致。
6. 计算标签位姿
检测到标签角点后,结合相机内参即可求解标签相对于相机的旋转矩阵和平移向量。
6.1 使用 OpenCV solvePnP
import cv2
import numpy as np
import apriltag
# 加载相机内参与畸变系数
with np.load('camera_calibration.npz') as data:
mtx = data['mtx']
dist = data['dist']
# 标签边长,单位:米
tag_size = 0.108
# 标签坐标系下的四个角点:左上、右上、右下、左下
# 原点位于标签中心
half = tag_size / 2.0
object_points = np.array([
[-half, half, 0],
[ half, half, 0],
[ half, -half, 0],
[-half, -half, 0],
], dtype=np.float64)
image = cv2.imread('scene.jpg', cv2.IMREAD_GRAYSCALE)
options = apriltag.DetectorOptions(families='tag36h11')
detector = apriltag.Detector(options)
results = detector.detect(image)
for r in results:
image_points = r.corners.reshape(-1, 2).astype(np.float64)
ret, rvec, tvec = cv2.solvePnP(
object_points,
image_points,
mtx,
dist,
flags=cv2.SOLVEPNP_IPPE_SQUARE,
)
# 将旋转向量转换为旋转矩阵
R, _ = cv2.Rodrigues(rvec)
print(f"标签 {r.tag_id} 的平移向量 tvec: {tvec.ravel()}")
print(f"标签 {r.tag_id} 的旋转矩阵 R:")
print(R)
x, y, z = tvec.ravel()
distance = np.linalg.norm(tvec)
print(f"标签到相机的距离约为 {distance:.3f} 米")
6.2 使用 detector.detection_pose
apriltag 库也提供了底层的位姿估计接口:
import apriltag
for r in results:
pose, init_error, final_error = detector.detection_pose(
r,
(fx, fy, cx, cy),
tag_size=tag_size,
)
print(f"标签 {r.tag_id} 的位姿矩阵:")
print(pose)
print(f"初始误差: {init_error:.5f}, 优化后误差: {final_error:.5f}")
其中 fx、fy、cx、cy 来自相机内参矩阵:
fx = mtx[0, 0]
fy = mtx[1, 1]
cx = mtx[0, 2]
cy = mtx[1, 2]
pose 通常是一个 4x4 的齐次变换矩阵,表示从相机坐标系到标签坐标系的变换。使用前建议核对所用库版本的矩阵方向约定,避免出现旋转方向相反的问题。
7. 在图像上绘制定位结果
为了直观查看检测与定位效果,可以将标签边框、ID 和坐标信息绘制到原始图像上:
import cv2
color_image = cv2.imread('scene.jpg')
for r in results:
corners = r.corners.astype(int)
# 绘制标签边框
cv2.polylines(color_image, [corners], True, (0, 255, 0), 2)
# 计算中心点
cx, cy = map(int, r.center)
cv2.circle(color_image, (cx, cy), 5, (0, 0, 255), -1)
# 显示标签 ID
cv2.putText(
color_image,
f"ID: {r.tag_id}",
(cx - 30, cy - 15),
cv2.FONT_HERSHEY_SIMPLEX,
0.7,
(255, 0, 0),
2,
)
cv2.imwrite('detected_result.jpg', color_image)
如果已经计算出平移向量,还可以将相机到标签的距离显示在画面上:
distance_text = f"Distance: {distance:.2f} m"
cv2.putText(
color_image,
distance_text,
(30, 30),
cv2.FONT_HERSHEY_SIMPLEX,
0.8,
(0, 0, 255),
2,
)
8. 实时视频定位
将上述流程应用到摄像头视频流中,即可实现实时定位:
import cv2
import numpy as np
import apriltag
with np.load('camera_calibration.npz') as data:
mtx = data['mtx']
dist = data['dist']
tag_size = 0.108
half = tag_size / 2.0
object_points = np.array([
[-half, half, 0],
[ half, half, 0],
[ half, -half, 0],
[-half, -half, 0],
], dtype=np.float64)
cap = cv2.VideoCapture(0)
cap.set(cv2.CAP_PROP_FRAME_WIDTH, 1280)
cap.set(cv2.CAP_PROP_FRAME_HEIGHT, 720)
options = apriltag.DetectorOptions(families='tag36h11', refine_edges=True)
detector = apriltag.Detector(options)
while True:
ret, frame = cap.read()
if not ret:
break
gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
results = detector.detect(gray)
for r in results:
image_points = r.corners.reshape(-1, 2).astype(np.float64)
ret, rvec, tvec = cv2.solvePnP(
object_points,
image_points,
mtx,
dist,
flags=cv2.SOLVEPNP_IPPE_SQUARE,
)
distance = np.linalg.norm(tvec)
corners = r.corners.astype(int)
cv2.polylines(frame, [corners], True, (0, 255, 0), 2)
cv2.putText(
frame,
f"ID: {r.tag_id} D: {distance:.2f} m",
(int(r.center[0] - 50), int(r.center[1] - 20)),
cv2.FONT_HERSHEY_SIMPLEX,
0.7,
(0, 0, 255),
2,
)
cv2.imshow('AprilTag Tracking', frame)
if cv2.waitKey(1) & 0xFF == ord('q'):
break
cap.release()
cv2.destroyAllWindows()
运行后,将打印出来的 AprilTag 放在摄像头前方,即可看到标签被实时框选,并显示标签 ID 与距离信息。
9. 提高定位精度的建议
9.1 保证标签平整
AprilTag 位姿估计算法默认标签位于平面上。如果标签弯曲、褶皱或贴在曲面物体上,会导致角点检测误差增大,进而影响定位精度。
9.2 选择合适的标签尺寸
标签过小会导致图像中的角点像素数量不足,检测不稳定;标签过大又会限制相机与标签之间的有效距离范围。建议根据工作距离选择标签尺寸,使标签在图像中的边长保持在 20 到 100 像素以上。
9.3 控制光照条件
强烈反光、过暗或阴影过重都可能使标签边缘提取失败。可使用散射光源,并尽量保证标签表面光照均匀。
9.4 使用较高的图像分辨率
分辨率越高,角点定位越精细。但高分辨率会增加计算时间,实际应用中需要在精度与速度之间权衡。
9.5 开启亚像素角点细化
在检测选项中设置 refine_edges=True,并对角点使用 cv2.cornerSubPix 进一步细化,可以显著提高位姿估计精度。
9.6 优化相机标定质量
标定图像的覆盖范围、角度多样性和数量会直接影响内参精度。建议拍摄至少 15 到 20 张不同姿态的棋盘格图像,并在标定时排除重投影误差过大的图像。
10. 常见问题与排查
10.1 检测不到标签
可能原因:
- 标签不在图像中或距离过远;
- 图像模糊、运动模糊严重;
- 光照过强或过暗;
- 标签被遮挡;
- 标签家族设置错误,例如使用了
tag36h11标签却配置为tag25h9; - 图像分辨率过低。
排查方法:
- 适当靠近标签;
- 降低相机运动速度;
- 调整光照;
- 确认
families参数与实际标签一致; - 尝试将
quad_decimate调小,如设为0.5,以提高对远距离标签的检测能力。
10.2 距离误差较大
可能原因:
- 相机内参不准确;
- 标签物理尺寸设置错误,例如实际标签为 10.8 厘米,代码中却写成 0.050 米;
- 标签平面与相机光轴夹角过大;
- 标签发生了透视变形或弯曲。
10.3 检测结果抖动
可以加入滤波算法进行平滑,常用的有一阶低通滤波、卡尔曼滤波等。以下是一个简单的一阶低通滤波示例:
alpha = 0.3
smoothed_distance = None
for r in results:
# ... 计算 distance
if smoothed_distance is None:
smoothed_distance = distance
else:
smoothed_distance = alpha * distance + (1 - alpha) * smoothed_distance
11. 典型应用场景
- 移动机器人物料搬运:将 AprilTag 粘贴在货架或工位上,帮助机器人进行视觉引导定位;
- 无人机平台精准降落:地面铺设 AprilTag,无人机通过识别标签实现自主着陆;
- 增强现实:将虚拟物体固定在 AprilTag 上,实现虚实融合;
- 相机标定与多相机组网:利用 AprilTag 作为标定板,标定多相机系统外参;
- 自动驾驶与仓储 AGV:作为低成本、高精度的视觉定位标记。
12. 总结
AprilTag 提供了一套完整的视觉基准标记检测与定位方案。通过“边缘检测 → 四边形提取 → ID 识别 → 角点匹配 → PnP 位姿求解”的流程,可以在已知相机内参和标签物理尺寸的前提下,实时估计标签相对于相机的三维位置与姿态。
使用 AprilTag 进行定位时,关键要点包括:
- 正确选择标签家族并保证家族参数一致;
- 认真完成相机标定;
- 准确设置标签物理尺寸;
- 保持良好的成像条件;
- 必要时对检测结果进行平滑滤波。
在移动机器人、无人机、增强现实等领域,AprilTag 已经成为一种成熟且可靠的视觉定位方案,值得深入学习和实践。
DAMO开发者矩阵,由阿里巴巴达摩院和中国互联网协会联合发起,致力于探讨最前沿的技术趋势与应用成果,搭建高质量的交流与分享平台,推动技术创新与产业应用链接,围绕“人工智能与新型计算”构建开放共享的开发者生态。
更多推荐

所有评论(0)