工具装配检测数据集实战:从YOLOv8训练到产线落地 简介这是一套面向工业装配场景的工具装配检测数据集覆盖枪型工具、钳型工具、尖头工具、扫描工具及手部交互5类目标提供1873条YOLO格式标注数据可直接用于训练YOLOv5/v8等主流目标检测模型为自动化装配、智能质检和机器人视觉引导提供基础语料。资源包共2000个文件包含1873个txt标注文件、125张jpg实景图片、1个yaml类别配置文件和1个docx说明文档压缩包整体约80.98MB目录结构清晰便于按需取用。目前已有188人学习下载适合工业算法工程师、制造系统开发者及职业教育学员。数据样本来自真实装配环境含多角度操作实例能够提升模型在复杂工业场景下的鲁棒性辅助完成工具识别、抓取定位与合规性检测等任务。1. 工具装配检测数据集先搞清楚它能帮你解决什么问题产线上最贵的浪费往往不是设备停机而是“装没装、装没装对”全靠人眼。想跑通装配检测你手里缺的往往不是模型而是数据。这个“工具装配检测数据集.zip”就是把工位相机拍到的装配过程整理成带标注的目标检测样本图像、类别、坐标框同步打包解压后可以直接进 YOLO 系训练管线。它的价值是把工艺要求转成视觉判据适合做产线视觉落地的算法工程师、设备集成人员也适合刚入门想跑通完整流程的开发者。不过有一点要先说清楚数据集不是拿来就训练结构的坑往往比模型参数更早出现。2. 拆开“工具装配检测数据集.zip”目录结构与标注格式的真相2.1 拿到压缩包先别急着解压用这几条命令看目录和样本分布很多人的习惯是双击解压、拖进训练脚本跑不动才回头找原因。我一般会先看一眼压缩包内部结构这一步能省掉后面三个小时的排查时间。命令行里用unzip -l列出条目用管道过滤出目录和样本数量比图形界面直观得多。# 只看压缩包内部结构不解压 unzip -l tool_assembly_detection_dataset.zip | head -n 60 # 看到大致结构后再真正解压到 dataset 目录 unzip -q tool_assembly_detection_dataset.zip -d ./dataset # 查看二级目录结构 find ./dataset -maxdepth 2 -type d | sort # 统计图像和标签的数量心里有个底 find ./dataset -name *.jpg | wc -l find ./dataset -name *.txt | wc -l这段命令的逻辑是先看后动。head -n 60只截前 60 行因为一个几百兆的压缩包内部条目可能非常多直接unzip -l会刷屏你反而看不到根目录长什么样。解压时指定-d ./dataset是让所有文件落在一个干净的目录里避免压缩包内嵌多层目录导致后面 yaml 路径写错。find配合wc -l统计的是图像和标签数量这个数字对不上的话后面训练时一定会出现“读到空标签”的警告。看完这个压缩包的目录结构常见做法是整理成下面这种布局images/train、images/val、labels/train、labels/val四个目录外加一个classes.txt。有的包会把 val 和 test 混在一起甚至把所有图像放一个文件夹、标注放另一个文件夹那你就要自己动手拆分。我的习惯是先建一个data_clean目录做一次重组织绝对不在原压缩包上做修改——防止解压权限问题把源文件搞坏。这种结构组织的关键参数有两个一个是训练集和验证集的比例常见是 8:2 或 9:1另一个是类别文件classes.txt的行顺序它必须和标注文件里的 class id 一一对应不能出现第一行是“螺丝”、标注里 id0 却代表“垫片”的情况。这个对应关系错了模型不会报错只会默默学错等验证时 mAP 惨不忍睹你才意识到问题。2.2 标注格式选型COCO JSON 与 YOLO TXT 的差异装配检测为何常选后者装配检测数据集的标注格式常见两种COCO 的annotations.json和 YOLO 的labels/*.txt。早期接触 coco2017 数据集结构的人都会有个经验COCO 的 JSON 字段嵌套深、id 映射复杂一个annotation要关联image_id、category_id和bbox还要维护images数组里的id与文件名对应。这套结构在通用视觉任务里很规范但对装配产线这种类别相对固定、目标数量明确的场景来说处理成本高于收益。YOLO 的 txt 格式则简单得多每行五个数class_id x_center y_center width height全部归一化到 0~1。这个格式的好处是训练脚本不用解析 JSON直接从文件读就行。如果你的数据源头给的是 COCO 格式我一般会写个一次性转换脚本转成 YOLO txt顺便检查坐标是否越界。import json import os def coco_to_yolo(coco_path, output_dir): with open(coco_path, r, encodingutf-8) as f: coco json.load(f) # 建立 image id 到文件名的映射 img_map {} for img in coco[images]: img_map[img[id]] img[file_name] # 建立 category id 到连续类别索引的映射 cat_map {} for idx, cat in enumerate(coco[categories]): cat_map[cat[id]] idx os.makedirs(output_dir, exist_okTrue) # 按图像聚合标注 anns_by_img {} for ann in coco[annotations]: anns_by_img.setdefault(ann[image_id], []).append(ann) for img_id, anns in anns_by_img.items(): filename img_map[img_id] img_path os.path.join(os.path.dirname(coco_path), filename) if not os.path.exists(img_path): continue # 读取真实宽高而不是用标注里的值 from PIL import Image w, h Image.open(img_path).size txt_path os.path.join(output_dir, os.path.splitext(filename)[0] .txt) with open(txt_path, w, encodingutf-8) as f: for ann in anns: cat_id cat_map[ann[category_id]] x, y, box_w, box_h ann[bbox] # COCO bbox 是左上角坐标 宽高 x_center (x box_w / 2) / w y_center (y box_h / 2) / h norm_w box_w / w norm_h box_h / h # 关键裁剪越界坐标避免训练时出现 NaN x_center min(max(x_center, 0.0), 1.0) y_center min(max(y_center, 0.0), 1.0) norm_w min(norm_w, 1.0 - x_center) norm_h min(norm_h, 1.0 - y_center) f.write(f{cat_id} {x_center:.6f} {y_center:.6f} {norm_w:.6f} {norm_h:.6f}\n) coco_to_yolo(annotations.json, labels_converted)这段脚本的核心逻辑是把 COCO 的左上角坐标加宽高换算成中心点坐标加宽高同时做归一化。两个容易踩的坑在这里提前处理了一是 COCO 的 bbox 是[x, y, width, height]不是中心点表示直接套 YOLO 公式会整体偏移二是坐标可能超出图像边界尤其是标注人员习惯把目标画得大一圈时越界坐标会在损失函数里造成梯度异常。脚本用min(max())把中心点夹取到 0~1再用剩余空间约束宽高保证归一化后的框合法。这里有个小忠告不要依赖压缩包里自带的classes.txt去手动改名类别。装配检测的类别往往很具体比如“内六角扳手”“卡簧钳”“扭力扳手”类名一改检测结果的可解释性就乱了。我的原则是类别文件保持原始命名只在训练配置文件里映射成模型输出的索引这样产线调试时对照图纸也方便。3. 用 YOLOv8 训练自己的装配检测模型从 yaml 到第一个可靠权重3.1 数据集 yaml 写法与最短训练命令把拆好的样本喂给 YOLOv8数据整理成images/train、images/val、labels/train、labels/val四个目录后下一步是写 YOLOv8 要求的数据集 yaml。这就是 YOLOv8 训练自己的数据集时的必要结构yaml 文件里写路径、写类别名模型才会知道去哪里找图、找标注。# tool_assembly.yaml path: /home/vision/datasets/tool_assembly # 数据集根目录的绝对路径 train: images/train # 训练图像目录相对 path val: images/val # 验证图像目录相对 path names: 0: hex_wrench 1: torque_wrench 2: retaining_ring_pliers 3: screw 4: washer这个 yaml 的参数说明第一是path建议写绝对路径而不是相对路径。因为你会把训练脚本、模型权重和数据集放在不同层级相对路径容易在切换工作目录后失效。第二是train和val的写法它们对应的是相对path的子目录但如果你把train写成全路径也是可以的两种写法不能混用。第三是names这个字典的 key 必须从 0 开始连续递增中间缺一个数字都会导致类别解析错位。写好后执行训练命令你不需要一开始就追求大模型先跑一个轻量级实验看数据链路通不通。yolo detect train \ datatool_assembly.yaml \ modelyolov8n.pt \ epochs60 \ imgsz640 \ batch16 \ projectruns/tool_asm \ nameexp_nano \ device0命令里的参数都是有讲究的。modelyolov8n.pt是纳米版预训练权重适合验证数据和代码链路一个晚上能跑完你确认没问题后再换成yolov8m.pt或yolov8l.pt。epochs60是装配检测的一个经验起点不是越多越好这种工业数据集往往样本量不大训练到 80~100 轮容易出现严重过拟合。imgsz640是模型输入分辨率如果你发现小工具漏检这个值要调大或配合切图方案我这里先不细说。batch16根据显存调整跑不动就减半。3.2 训练后第一时间看这五个输出装配场景里指标要怎么读训练结束不要只盯着终端打印的最后一行 mAP。进到runs/tool_asm/exp_nano目录你要按顺序看这几个文件results.png、confusion_matrix.png、val_batch0_pred.jpg、labels.jpg以及每个类别的P、R、mAP50指标表。results.png里最关键的是val/box_loss和val/cls_loss两条曲线。如果它们在最后 20 轮没有明显下降趋势可能说明学习率衰减策略没生效或数据本身难度低到已经收敛如果训练损失还在降、验证损失反而上升那就是过拟合的早期信号此时提前用early_stopping或者减少epochs。装配检测里过拟合有个典型现象训练集 mAP 达到 0.98验证集只有 0.82且漏检集中在某一种工具上——这通常不是模型容量不够而是那个类别的样本形态太少一个工具换个角度就没见过。confusion_matrix.png看的是误检模式。装配检测最怕的是把“垫片”检测成“螺丝”因为两者都是小圆形金属件。如果这个混淆矩阵里有明显的对角线外亮块你要去翻val_batch0_pred.jpg确认具体是哪些样本混淆——有时候是一个角度问题有时候是标注框本身就画歪了。labels.jpg展示的是训练数据里 GT 框的分布中心点集中、尺寸跨度大说明数据采集时相机位置固定、工具大小多样这是正常装配工位的画像。指标表不要只看mAP50要分细看mAP50-95。装配检测对框精度的要求比通用目标检测高因为后续要接抓取或防错逻辑如果mAP50高而mAP50-95低说明框整体偏大或偏小虽然 IoU 0.5 时碰上了但位置并不准。这时候我会回头检查标注的边框是否贴紧了工具轮廓而不是先调模型。4. 装配检测数据集落地的避坑清单五个高频翻车点和排查路径4.1 解压提示 “invalid zip archive: could not find EOCD”一上来就卡住现象用unzip解压这个数据集压缩包时直接报错说找不到End of Central Directory记录工具类软件直接打不开测试数据也一并读了失败。原因这类大体积的数据集 zip 多是网盘或内部传输系统传的断点续传常把文件末尾截断而 EOCD 记录恰好位于 zip 文件最后几十个字节。另一个可能是文件在传输中被当作文本格式改写过换行符被转换破坏了二进制结构。解决先用ls -l看文件实际大小和下载预期大小是否一致不一致就重新传如果大小一致还是报错用zip -F修复试试我习惯同时用 7-Zip 打开看能否进入目录树只要能看到条目优先把文件逐个拖出来而不是纠结把这个 zip 修好。血泪经验是不要在损坏的压缩包上反复操作尽快找到第二份拷贝。4.2 文件数量对得上但训练日志里大量 “found 0 labels”现象图像和 txt 标签文件数量一致但训练时每张图都提示WARNING: found 0 labels in ...训练出来的模型什么也检测不到。原因最常见的是标签文件里其实是空的也就是 txt 文件 0 字节其次是图片文件名或路径里带中文、空格YOLO 的读取器在部分 Linux 环境下编码不兼容。还有一种隐蔽情况这个数据集 zip 解压后标签文件被 Windows 系统隐藏属性影响了文件读取权限。解决先用find labels -name *.txt -size 0找出空文件把对应的图片一并挑出来重新标注或删除再用file labels/train/*.txt | head检查编码非 UTF-8 的覆盖范围。我的统一做法是解压后立刻把所有图片和标签重命名为纯数字前缀比如asm_000001.jpg彻底绕开中文与空格问题。4.3 小尺寸工具普遍漏检mAP 卡在 0.8 上不去现象整体 mAP50 有 0.85 以上但螺丝、垫片这类小件的单类 AP 只有 0.5 左右验证集图片里肉眼可见的小目标一件都没框出来。原因两个因素叠加——原图分辨率不够小工具在图像里只占 20×20 像素以下模型输入imgsz640时这些小目标在经过网络下采样后特征图上的响应非常弱。装配工位的相机安装位置如果偏高偏远这个问题尤其明显。解决优先考虑把相机拉近或换更高分辨率相机这是在数据层面解决算法层面把imgsz从 640 提到 1280 试一下显存不够就配合rectTrue按原图宽高比分批推理避免无意义 resize。如果仍然不行就要用切图方案把小目标区域裁出来单独检测这个我后面专门写。4.4 类别严重不均衡多数类主导训练现象一把扭矩扳手反复出现某些稀有工具整个训练集只有几十个实例训练结果中稀有类的 mAP 远低于平均线。原因装配产线的节拍决定了某个工具反复出现在每个工件的同一装配步骤里稀有类往往只在一个特殊工序里出现一次。这个数据集 zip 里很可能没有做任何类别平衡处理属于原始采样。解决第一优先级是去现场补拍稀有类的不同角度和光照一次采集胜过一次抽卡式增强第二优先级是离线复制稀有类样本并做轻量增强比如随机亮度、随机平移、轻微旋转注意这些增强不能改变工具的可辨识特征第三优先级才是调 loss 权重。我不推荐一上来就把少数类翻很多倍工业场景里光照变化往往比样本数量更重要。4.5 训练不报错但每个类的 mAP 都是 0或者 loss 直接 NaN现象数据链路正常训练能跑完但混淆矩阵全黑验证集预测结果里一个框都没有。原因标注框的坐标在某个环节被重复归一化了。这个数据集 zip 里的标签如果来源是一个导出工具可能已经归一化过又被你的预处理脚本整体除以了图像宽高导致框变成零点几像素的极坐标。还有一种可能是标注框宽高为 0画框时只点了一个点没拉出范围。解决训练前加一个数据校验脚本打印每张图标签中坐标的最大值和最小值如果所有值都在 0~1 之间且最大宽高接近 1说明归一化正确如果有大于 1 的数值说明标签是像素坐标需要二次转换。宽高为 0 的异常标签直接过滤不要留到训练里浪费调试时间。5. 从“能训练”到“上产线”切图、难例挖掘和旋转目标的处理边界5.1 大图切块把小工具放大到模型能分辨的尺度当原始图像分辨率接近 4000×3000而小工具只有 30×30 像素时直接缩放整图会丢失细节。常见做法是切图把一张大图切成多个有重叠的 patch让每个 patch 里的目标占到合理像素比例。我常用下面这个切图脚本它会同时维护原图坐标和切块坐标的映射关系。import cv2 import os import json def split_image_with_labels(image_path, label_path, patch_size640, overlap80, output_prefixpatch): img cv2.imread(image_path) h, w img.shape[:2] labels [] with open(label_path, r) as f: for line in f: parts line.strip().split() labels.append({ class: int(parts[0]), cx: float(parts[1]), cy: float(parts[2]), bw: float(parts[3]), bh: float(parts[4]) }) step patch_size - overlap patch_id 0 for y0 in range(0, h, step): for x0 in range(0, w, step): y1 min(y0 patch_size, h) x1 min(x0 patch_size, w) if y1 - y0 patch_size // 2 or x1 - x0 patch_size // 2: continue # 边缘过小的补丁直接丢弃 patch img[y0:y1, x0:x1] patch_h, patch_w patch.shape[:2] out_lines [] for lb in labels: # 原图坐标换算回像素值 abs_cx lb[cx] * w abs_cy lb[cy] * h abs_bw lb[bw] * w abs_bh lb[bh] * h x_min abs_cx - abs_bw / 2 y_min abs_cy - abs_bh / 2 x_max abs_cx abs_bw / 2 y_max abs_cy abs_bh / 2 # 保留与切块有足够重叠的目标 ix_min max(x_min, x0) iy_min max(y_min, y0) ix_max min(x_max, x1) iy_max min(y_max, y1) inter_w max(0, ix_max - ix_min) inter_h max(0, iy_max - iy_min) if inter_w 0 or inter_h 0: continue orig_area (x_max - x_min) * (y_max - y_min) inter_area inter_w * inter_h # 只有中心落在切块内的目标才保留 if not (x0 abs_cx x1 and y0 abs_cy y1): continue if inter_area / orig_area 0.5: continue # 换算到切块坐标系 px_min x_min - x0 py_min y_min - y0 px_max x_max - x0 py_max y_max - y0 px_min max(0.0, min(px_min, patch_w)) py_min max(0.0, min(py_min, patch_h)) px_max max(0.0, min(px_max, patch_w)) py_max max(0.0, min(py_max, patch_h)) if px_max px_min or py_max py_min: continue nc_x (px_min px_max) / 2 / patch_w nc_y (py_min py_max) / 2 / patch_h nc_w (px_max - px_min) / patch_w nc_h (py_max - py_min) / patch_h out_lines.append(f{lb[class]} {nc_x:.6f} {nc_y:.6f} {nc_w:.6f} {nc_h:.6f}) patch_filename f{output_prefix}_{patch_id:05d}.jpg cv2.imwrite(os.path.join(crops, patch_filename), patch) if out_lines: with open(os.path.join(crops, os.path.splitext(patch_filename)[0] .txt), w) as f: f.write(\n.join(out_lines)) patch_id 1 split_image_with_labels( images/train/asm_000001.jpg, labels/train/asm_000001.txt, patch_size960, overlap120 )这里的patch_size960比训练imgsz常用值要大一些因为切出来之后还要经过模型缩放patch 内容物不能太小overlap80是让相邻切块保持重复区域防止目标被世界坐标边界切开后中心落在切块外被丢弃。脚本里两个关键过滤条件要特别留意中心点必须落在当前切块内且与切块的重叠面积不能小于原框面积的一半。这样可以避免同一工具在多个 patch 里反复出现造成重复标签也避免只露出 10% 的工具被标注成完整目标。切图之后训练集数据量会成倍增加我一般同时把imgsz调回 640让模型在固定输入尺寸下集中学习目标的纹理特征而不是学“目标大小”这个本身不稳定的信息。还有个提醒验证集不要切图直接在原始分辨率上做滑窗推理再拼回大图否则你验证的指标会失真因为产线部署时你是不会给模型喂切块的。5.2 难例挖掘用“错题本”驱动下一轮标注补全很多数据集 zip 是一次性交付的但产线数据会持续产生你不会只训练一次。跑完一轮模型后我的习惯是收集验证集里漏检和误检的样本按类别做个排名优先补充标注最差的类。yolo detect predict \ modelruns/tool_asm/exp_nano/weights/best.pt \ sourcedataset/images/val \ save_txtTrue \ save_confTrue \ conf0.25 \ projectruns/hard_mining \ nameval_predict这条命令在验证集上做全量推理输出每张图的预测 txt。接下来用一个小脚本把预测结果与 GT 做比对找出FNGT 有框而预测没有和FP预测有框而 GT 没有对应的图像。把它们复制到hard_examples/目录按照类别名建子目录这样下一轮人工标注时可以快速定位到“扳手在阴暗处漏检”这类具体问题而不是让标注员面对几千张随机图像。真正的价值在于“按错题分布决定补拍策略”。如果 FN 集中在某个光照条件你需要调整光源如果 FP 集中在背景中的金属反光区域你要考虑在预处理里增加去反光或者干脆把那些背景区域在标注时标成 ignore。难例挖掘不是一次性行为每次训练迭代后跑一遍错题本里的样本会越来越难、越来越接近现场真实分布。5.3 装配件朝向变化大时要不要上旋转框mmrotate 与 DOTA 思路的取舍有些装配检测场景里工具是随意放在工作台上的比如扭矩扳手可能以任意角度出现。水平框在目标倾斜时会框入大量背景一个框里可能同时框到两个相邻工具检测结果一塌糊涂。这时就有人想用旋转框。mmrotate 训练 DOTA 数据集是旋转检测的常见路径DOTA 里飞机、车辆的方向各异正好对应到水平框难以处理的场景。我的判断标准是如果水平框之间的 IoU 大于 0.3 且密集重叠明显旋转框才有必要上。大多数平移固定的装配工位其实目标稀疏水平框加一个小角度增强就够了。旋转框的代价是标注成本几乎翻倍你要为每个工具画四边形的四个点而且在部署时 NMS 和后处理逻辑都要重写产线调试周期拉长。我的建议是先用水平框跑通再评估误检方向是否出在角度上不要上来就上旋转框。6. 复查清单把 FP/FN 可视化发现模型更让人意外的错误6.1 输出错检图集按错误类型归类逐个确认训练完模型除了指标以外我给自己立了一个铁规矩必须把验证集的 FP 和 FN 画到原图上按“漏检”“误检”“框偏”三个目录归类。你光看指标是看不出“为什么漏”的而把检错图画出来贴到产线工艺同事面前他们一眼就能告诉你答案有的是防错工装遮挡有的是工件本身有异形面这些信息比调参重要得多。具体做法是把预测结果画到图上时GT 框用绿色表示漏检的 GT 用红色加粗误检的预测框用黄色表示保存到review/目录再用文件管理器按类型筛选。import cv2 import os def draw_error_cases(image_dir, pred_dir, gt_dir, output_dir): for img_name in os.listdir(image_dir): image_path os.path.join(image_dir, img_name) img cv2.imread(image_path) base os.path.splitext(img_name)[0] pred_path os.path.join(pred_dir, base .txt) gt_path os.path.join(gt_dir, base .txt) logic if os.path.exists(pred_path): with open(pred_path) as f: for line in f: _, x, y, w, h line.strip().split() x, y, w, h float(x), float(y), float(w), float(h) px1 int((x - w / 2) * img.shape[1]) py1 int((y - h / 2) * img.shape[0]) px2 int((x w / 2) * img.shape[1]) py2 int((y h / 2) * img.shape[0]) cv2.rectangle(img, (px1, py1), (px2, py2), (0, 255, 255), 2) logic p if os.path.exists(gt_path): with open(gt_path) as f: for line in f: _, x, y, w, h line.strip().split() x, y, w, h float(x), float(y), float(w), float(h) gx1 int((x - w / 2) * img.shape[1]) gy1 int((y - h / 2) * img.shape[0]) gx2 int((x w / 2) * img.shape[1]) gy2 int((y h / 2) * img.shape[0]) cv2.rectangle(img, (gx1, gy1), (gx2, gy2), (0, 255, 0), 2) logic g if p in logic and g not in logic: # 只有预测没有 GT属于误检 cv2.imwrite(os.path.join(output_dir, fp_ img_name), img) elif g in logic and p not in logic: # 只有 GT 没有预测属于漏检 cv2.imwrite(os.path.join(output_dir, fn_ img_name), img) elif p in logic and g in logic: # 预测和 GT 都存在压到同一张图里确认框偏程度 cv2.imwrite(os.path.join(output_dir, both_ img_name), img) draw_error_cases( dataset/images/val, runs/hard_mining/val_predict/labels, dataset/labels/val, review )这段脚本的功能是快速定位 FP 和 FN。判断逻辑简化了一点只要存在预测框就算检测到没有做 IoU 匹配工程上手先看个大概分布防止你陷进复杂评估引用的少数框里。logical字段记录当前图片是否有预测、是否有 GT然后按情况写进不同前缀的图片。真正投产前的精确评估还要用工具评估类计算 IoU这一步是快速构建“错题本”。我之前在一个装配防错项目里就吃过自作聪明的亏当时 mAP50 从 0.91 提到 0.94我兴致勃勃地打算部署结果这种可视化脚本一跑发现漏检的全是同一型号的卡簧而它在图像远端只占十几个像素。后来我重新测了全部现场图片把相机角度往工件走近了 20 厘米、补了一批暗光样本最终这个类别的 AP 才从 0.42 涨到 0.86。复盘下来不是模型架构不行是我的数据层面没有覆盖产线真实变化。那次之后我留下一个习惯每次训练迭代后不管指标涨了多少都先跑一遍错题可视化再决定下一步改数据还是改模型。这个习惯帮我避免了很多“指标好看、现场掉链子”的尴尬很希望也能帮到你。本文还有配套的精品资源点击获取