简介:这份ArUco标记检测数据集面向机器人定位导航、增强现实研发及计算机视觉研究者,提供真实场景采集的标记识别训练素材,帮助解决空间定位与标记跟踪模型的数据来源问题。包内共2000个文件,以1300个YOLO格式txt标注、698张jpg实拍图像为主,另含1个yaml数据配置与1份docx说明文档,压缩包约42.21MB,标注兼容主流深度学习框架,可直接用于目标检测与实例分割任务。数据集划分训练集905张、验证集395张,覆盖多角度、多光照与遮挡条件下的ArUco标记实例,有助于提升模型鲁棒性。目前已有81人学习下载,适合从事机器人视觉、SLAM及AR交互开发的工程师与学术研究者,用于模型训练、算法验证与工程教育实践。
1. ArUco标记检测数据集:从一张标定板到可训练样本的完整链路
做视觉定位、相机标定或者机器人位姿估计的工程师,大概率都绕不开 ArUco 标记。它本质上是一组黑白方块组成的二进制编码图案,检测算法能从单张图里同时拿到标记 ID 和四个角点的亚像素坐标,进而解出相机相对标记的位姿。但真正动手时,卡住大多数人的不是检测算法本身,而是数据——手里只有几张随手拍的标定板照片,想训一个鲁棒的标记检测模型,或者想批量验证检测精度,样本量根本不够。ArUco标记检测数据集.zip 这个标题指向的,正是把「采集—标注—增强—封装」这条链路走通,产出一份能直接喂给检测网络或评测脚本的数据集。这篇内容适合三类人:想自己造 ArUco 数据集的算法工程师、需要批量生成标定样本的机器人开发者、以及拿 ArUco 做定位但被真实场景漏检折磨过的从业者。下面按「先立住原理、再动手复现、最后避坑」的顺序讲透。
2. ArUco 标记的编码原理与数据集该长什么样
2.1 字典、ID 与角点顺序:检测器到底在算什么
ArUco 的核心是字典(Dictionary)。一个字典定义了若干固定尺寸的标记,比如 4x4、5x5、6x6 的比特矩阵,每个矩阵对应一个唯一 ID。检测流程分两步:先做自适应阈值和轮廓提取,找出候选四边形;再把候选区域透视变换成正视图,按字典逐位比对,算出汉明距离,距离小于阈值就判定为某个 ID。所以数据集里每张图的标注,本质上要给出「这个标记是哪个 ID」和「它的四个角点在哪」。
角点顺序是新手最容易翻车的地方。OpenCV 的cv2.aruco返回的角点顺序是固定的:从左上角开始,顺时针排列。如果你自己标注时顺序乱了,训练出来的模型即使框对了,位姿解算也会整体旋转 90 度。数据集标注文件里必须明确这个约定,否则后续用 PnP 解位姿时会出现「明明检测到了,姿态却完全不对」的玄学问题。
标记尺寸和字典选择直接决定数据集的适用面。常见做法是:小场景用 4x4_50 或 5x5_100,标记数量少、鲁棒性高;大场景或多标记同框用 6x6_250 甚至 7x7_1000。数据集如果只覆盖单一字典,泛化性会很差,建议至少覆盖两种字典、三种物理尺寸。
2.2 一份合格数据集的目录结构与标注格式
数据集不是一堆图片堆在一起就完事。可复现的结构应该让训练脚本和评测脚本都能直接读。我一般会按下面的结构组织:
aruco_dataset/ ├── images/ │ ├── train/ │ │ ├── 000001.jpg │ │ └── ... │ └── val/ │ └── ... ├── labels/ │ ├── train/ │ │ ├── 000001.txt │ │ └── ... │ └── val/ │ └── ... ├── dictionary_info.json └── README.md标注格式用 YOLO 风格的 txt 最省事,每行class_id x_center y_center width height,归一化到 0-1。但 ArUco 的特殊之处在于:光有外接框不够,位姿估计需要四个角点。所以更完整的做法是在 txt 里额外存角点,或者单独用一个 json 存每个标记的id和corners。下面这个 json 结构是我常用的:
{ "image": "000001.jpg", "markers": [ { "id": 23, "dictionary": "DICT_5X5_100", "corners": [[120, 88], [210, 90], [208, 180], [118, 178]], "bbox": [118, 88, 92, 92] } ] }corners按左上、右上、右下、左下顺序存,bbox是[x_min, y_min, width, height]。这样一份数据既能训 YOLO 系列做粗检测,也能直接喂给cv2.solvePnP做位姿验证。dictionary_info.json里记录字典名称、标记物理边长(毫米)、生成时用的 DPI,方便复现。
提示:标注文件里的角点顺序一旦定下,整个数据集必须统一。中途换顺序等于把之前训的模型废掉。
3. 从零生成 ArUco 标记检测数据集:脚本与参数
3.1 用 OpenCV 批量生成标记图与合成场景
真实拍摄成本高,第一步先用合成数据把量堆起来。OpenCV 自带cv2.aruco模块,可以生成标记图,再贴到随机背景上模拟不同视角。下面这段脚本生成单标记合成样本:
import cv2 import numpy as np import os import random # 参数说明: # dict_id: 字典类型,这里用 5x5_100 # marker_size: 标记像素边长 # img_size: 输出画布尺寸 # num_samples: 生成数量 dict_id = cv2.aruco.DICT_5X5_100 marker_size = 200 img_size = 640 num_samples = 500 out_dir = "aruco_dataset/images/train" os.makedirs(out_dir, exist_ok=True) aruco_dict = cv2.aruco.getPredefinedDictionary(dict_id) for i in range(num_samples): marker_id = random.randint(0, 99) marker_img = cv2.aruco.generateImageMarker(aruco_dict, marker_id, marker_size) # 随机背景灰度,模拟不同光照 bg = np.random.randint(80, 200, (img_size, img_size), dtype=np.uint8) canvas = cv2.cvtColor(bg, cv2.COLOR_GRAY2BGR) # 随机缩放和旋转 scale = random.uniform(0.5, 1.5) new_size = int(marker_size * scale) marker_resized = cv2.resize(marker_img, (new_size, new_size)) angle = random.uniform(-180, 180) M = cv2.getRotationMatrix2D((new_size/2, new_size/2), angle, 1.0) marker_rot = cv2.warpAffine(marker_resized, M, (new_size, new_size), borderValue=255) # 随机位置贴图 x = random.randint(0, img_size - new_size) y = random.randint(0, img_size - new_size) canvas[y:y+new_size, x:x+new_size] = cv2.cvtColor(marker_rot, cv2.COLOR_GRAY2BGR) cv2.imwrite(f"{out_dir}/{i:06d}.jpg", canvas)这段脚本的逻辑是:先生成干净标记,再随机缩放、旋转、贴到随机灰度背景上。scale控制标记在画面中的占比,angle覆盖全角度,bg的灰度范围模拟不同曝光。跑完 500 张大概几十秒。但合成数据有个明显短板:没有真实的光照渐变、运动模糊和镜头畸变,直接拿去训模型,到真实场景容易翻车。所以合成数据只做预训练,真实数据必须补。
3.2 真实采集:相机、光照与角点标注的实操参数
真实采集时,相机内参最好先标定好,否则后续位姿验证没有基准。采集脚本用 OpenCV 读摄像头,按空格存图,同时用检测器实时画出角点,方便判断这张图有没有价值:
import cv2 cap = cv2.VideoCapture(0) aruco_dict = cv2.aruco.getPredefinedDictionary(cv2.aruco.DICT_5X5_100) params = cv2.aruco.DetectorParameters() detector = cv2.aruco.ArucoDetector(aruco_dict, params) idx = 0 while True: ret, frame = cap.read() if not ret: break corners, ids, _ = detector.detectMarkers(frame) if ids is not None: cv2.aruco.drawDetectedMarkers(frame, corners, ids) cv2.imshow("capture", frame) key = cv2.waitKey(1) if key == 32 and ids is not None: # 空格且检测到标记才存 cv2.imwrite(f"raw/{idx:06d}.jpg", frame) idx += 1 elif key == 27: break cap.release() cv2.destroyAllWindows()关键参数在DetectorParameters里。adaptiveThreshWinSizeMin和adaptiveThreshWinSizeMax控制自适应阈值的窗口范围,默认 3 到 23,光照不均时把 max 调到 45 能明显减少漏检。minMarkerPerimeterRate默认 0.03,标记在画面里很小时要往下调,否则直接过滤掉。polygonalApproxAccuracyRate默认 0.05,标记边缘模糊时可以放宽到 0.08。
采集时的血泪经验:不要只在一个距离和角度拍。标记在画面中的像素边长从 40 到 400 都要覆盖,俯仰角至少覆盖 ±60 度,光照要有顺光、逆光、侧光三种。每个 ID 至少 30 张,否则训出来的模型对特定 ID 过拟合。
3.3 自动标注与人工校验的配合
真实采集的图,标注不用全手工。用检测器先跑一遍自动标注,把结果存成 json,再人工过一遍修正漏检和误检。自动标注脚本:
import cv2 import json import glob aruco_dict = cv2.aruco.getPredefinedDictionary(cv2.aruco.DICT_5X5_100) detector = cv2.aruco.ArucoDetector(aruco_dict, cv2.aruco.DetectorParameters()) for img_path in glob.glob("raw/*.jpg"): img = cv2.imread(img_path) corners, ids, _ = detector.detectMarkers(img) result = {"image": img_path.split("/")[-1], "markers": []} if ids is not None: for c, i in zip(corners, ids.flatten()): pts = c.reshape(4, 2).tolist() xs = [p[0] for p in pts] ys = [p[1] for p in pts] result["markers"].append({ "id": int(i), "corners": pts, "bbox": [min(xs), min(ys), max(xs)-min(xs), max(ys)-min(ys)] }) with open(img_path.replace("raw", "labels").replace(".jpg", ".json"), "w") as f: json.dump(result, f, indent=2)自动标注的准确率在清晰图上能到 95% 以上,但模糊、遮挡、强反光的情况下会漏。人工校验的重点就是这些难例。校验时用cv2.aruco.drawDetectedMarkers把标注画回图上,肉眼扫一遍,漏的补上,错的删掉。这一步不能省,否则数据集里混进错误标注,训练时 loss 会莫名其妙震荡。
注意:自动标注的角点顺序和检测器一致,人工补标时也要按左上、右上、右下、左下,别凭感觉点。
4. 数据增强与格式转换:让数据集真正能训
4.1 针对 ArUco 的增强策略:哪些有用哪些是坑
通用增强里,翻转要慎用。水平翻转后,标记的编码图案左右镜像,字典比对会失败,除非你同时把 ID 映射也改掉。所以 ArUco 数据集里,水平翻转和垂直翻转基本不能用,旋转可以,但旋转后角点顺序要重新排。亮度、对比度、高斯噪声、运动模糊这四类增强是安全且有效的。
下面这段用 albumentations 做增强,注意关掉翻转:
import albumentations as A import cv2 transform = A.Compose([ A.RandomBrightnessContrast(brightness_limit=0.3, contrast_limit=0.3, p=0.7), A.GaussNoise(var_limit=(10.0, 50.0), p=0.5), A.MotionBlur(blur_limit=7, p=0.3), A.Rotate(limit=180, border_mode=cv2.BORDER_CONSTANT, p=0.8), A.Affine(scale=(0.8, 1.2), translate_percent=0.1, shear=10, p=0.5), ], bbox_params=A.BboxParams(format='pascal_voc', label_fields=['class_ids'])) # image 为原图,bboxes 为 [[x_min,y_min,x_max,y_max],...] # 增强后 bboxes 同步变换,角点需要单独用同样的变换矩阵处理RandomBrightnessContrast的 limit 设 0.3 是经验值,再大标记可能过曝到全白。MotionBlur的 blur_limit 别超过 7,否则标记边缘糊到检测器认不出。旋转用Rotate时,border_mode用常量填充,填充值设成背景均值,避免出现黑边干扰。
角点增强是个麻烦事。albumentations 只帮你变换 bbox,四个角点得自己用变换矩阵算。简单做法是:增强前把角点也当成关键点传进去,或者增强后重新用检测器跑一遍拿角点。后者更省事,但增强太狠的图检测器可能也检不到,所以增强强度要克制。
4.2 转成 YOLO 格式与 COCO 格式的脚本
YOLO 格式只需要 bbox 和 class_id。class_id 直接用标记 ID 会有一个问题:ID 范围 0-99,类别数就是 100,但很多 ID 在数据集里根本没出现,导致类别不平衡。常见做法是把 ID 映射到 0 到 N-1 的连续类别,映射表存在dictionary_info.json里。
import json import os def json_to_yolo(json_path, out_txt, id_map): with open(json_path) as f: data = json.load(f) lines = [] for m in data["markers"]: x, y, w, h = m["bbox"] # 归一化,假设图像尺寸 640x640 xc = (x + w/2) / 640 yc = (y + h/2) / 640 nw = w / 640 nh = h / 640 cls = id_map[m["id"]] lines.append(f"{cls} {xc:.6f} {yc:.6f} {nw:.6f} {nh:.6f}") with open(out_txt, "w") as f: f.write("\n".join(lines)) # 构建 id 映射 all_ids = set() for jp in os.listdir("labels/train"): with open(f"labels/train/{jp}") as f: for m in json.load(f)["markers"]: all_ids.add(m["id"]) id_map = {old: new for new, old in enumerate(sorted(all_ids))}转 COCO 格式类似,只是把 bbox 写成[x, y, w, h]绝对坐标,再加category_id和image_id。COCO 的好处是能直接用 pycocotools 算 mAP,评测更规范。转换时注意image_id要和图片文件名对应,别用随机数,否则评测时对不上。
4.3 数据集划分与类别平衡的检查
划分不能随机分。同一个标记 ID 的图如果全进了训练集,验证集上这个 ID 就是没见过的,mAP 会虚低。正确做法是按 ID 分层抽样:每个 ID 的图,80% 进训练,20% 进验证。如果某个 ID 图太少,就整个 ID 只放训练集,验证集不评这个 ID。
划分完检查一下类别分布:
from collections import Counter import json, glob counter = Counter() for jp in glob.glob("labels/train/*.json"): with open(jp) as f: for m in json.load(f)["markers"]: counter[m["id"]] += 1 print(counter.most_common())如果某个 ID 的样本数是其他 ID 的 10 倍以上,要么补采,要么在训练时用类别权重。ArUco 数据集里 ID 不平衡很常见,因为采集时总习惯反复用那几个顺手的标记。补采时专门挑冷门 ID 拍,比调权重省事。
5. 避坑与排查:ArUco 数据集最容易翻车的五个地方
5.1 检测器能检到,但标注角点顺序错乱
现象:训练 loss 正常下降,但推理时 bbox 很准,PnP 解出来的姿态却整体旋转 90 度或镜像。原因:标注时角点顺序没统一,有的图从右上开始,有的从左下开始。解决:写一个校验脚本,对每张图的角点算一下顺序——用叉积判断是否顺时针,用坐标判断起点是否最靠近左上。不满足的自动重排或标红人工修。
5.2 合成数据训的模型,到真实场景大面积漏检
现象:合成数据上 mAP 0.95,真实测试集掉到 0.4。原因:合成图背景太干净、光照太均匀,模型学到了「标记一定在纯色背景上」这种伪特征。解决:合成时叠加真实背景图,加运动模糊和镜头畸变模拟,合成与真实的比例控制在 1:1 以内,真实数据永远是主力。
5.3 增强开了翻转,ID 全乱
现象:增强后的图,标记看起来正常,但检测器识别出的 ID 和原图不一致。原因:水平翻转把编码矩阵镜像了,字典比对时匹配到了另一个 ID。解决:增强配置里显式禁用HorizontalFlip和VerticalFlip,旋转增强后重新跑检测器验证 ID 是否一致。
5.4 标注文件里 bbox 越界,训练时直接报错
现象:训练脚本读标注时报IndexError或坐标超出图像范围。原因:自动标注时角点算出的 bbox 有负值或超过图像宽高,尤其是标记贴边时。解决:转换脚本里加裁剪,x = max(0, x),w = min(w, img_w - x),宽高小于 2 像素的直接丢弃。
5.5 验证集和训练集用了同一个标记的相邻帧
现象:验证集 mAP 高得离谱,实际部署完全不行。原因:采集时连续拍了同一个标记的几十帧,随机划分后相邻帧分别进了训练和验证,模型等于在验证集上「见过」。解决:按时间或按场景分,同一段连续采集的图要么全进训练,要么全进验证,不能拆开。
6. 用检测结果反推位姿精度:数据集价值的终极验证
数据集做完,怎么判断它值不值得投入?我的习惯是拿它跑一遍完整的位姿估计,看重投影误差。具体做法:对验证集每张图,用检测到的角点和已知的标记物理尺寸,调cv2.solvePnP解出相机相对标记的旋转和平移,再把标记的 3D 点投影回图像,算投影点和检测角点的像素距离。这个距离就是重投影误差,正常应该在 0.5 到 2 像素之间。
import cv2 import numpy as np # marker_length: 标记物理边长,单位与 tvec 一致 marker_length = 0.05 # 5cm obj_points = np.array([ [-marker_length/2, marker_length/2, 0], [ marker_length/2, marker_length/2, 0], [ marker_length/2, -marker_length/2, 0], [-marker_length/2, -marker_length/2, 0] ], dtype=np.float32) def reprojection_error(corners, camera_matrix, dist_coeffs): img_points = np.array(corners, dtype=np.float32).reshape(4, 1, 2) ok, rvec, tvec = cv2.solvePnP(obj_points, img_points, camera_matrix, dist_coeffs) if not ok: return None proj, _ = cv2.projectPoints(obj_points, rvec, tvec, camera_matrix, dist_coeffs) err = cv2.norm(img_points, proj, cv2.NORM_L2) / 4 return errcamera_matrix和dist_coeffs必须用采集相机的真实标定值,不能拿默认值凑。如果重投影误差普遍大于 3 像素,说明角点标注精度不够,或者相机标定本身有问题。这时候回头查标注,比继续堆数据有用。
一个具体技巧:把重投影误差按标记在画面中的位置画散点图。如果误差集中在画面边缘,说明镜头畸变没标好;如果集中在某个角度,说明那个角度的样本标注质量差。这个图比看 mAP 更能暴露数据集的问题。
我自己踩过的最大坑,是早期图省事,用手机随手拍了几百张就当数据集,相机内参用的还是网上抄的默认值。结果模型训出来看着挺好,一上机器人,位姿抖得没法用。后来老老实实标定相机、按 ID 分层采样、每张图校验角点顺序,重投影误差才压到 1 像素以内。数据集这东西,省下的每一步都会在部署时加倍还回来。希望帮到你。
本文还有配套的精品资源,点击获取