简介:这份资源面向从事工业视觉检测、智能制造与深度学习目标检测的开发者与学习者,提供一套机械零件识别数据集,可用于训练与验证轴承、螺栓、法兰、齿轮、螺母、弹簧等常见零件的检测模型,适合课程设计、算法对比实验及产线质检原型开发。压缩包共约2000个文件,以1999个VOC格式xml标注文件和1个说明txt为主,图片与YOLO格式txt标注配套齐全,整体约224.71MB,xml与txt分别对应Pascal VOC和YOLO两种主流训练框架,方便直接接入不同检测网络。数据集共5913张jpg图片,标注类别6类,总框数达24049个,其中nut、bolt、gear、bearing等类别样本较充足,flange与spring相对偏少,便于开展类别不均衡实验。目前已有924人学习下载,可帮助读者省去从零采集与标注的成本,快速搭建机械零件检测基线并复现训练流程。
1. 机械零件检测数据集:5900 张 5 类 VOC+YOLO 双格式到底能跑出什么
产线上掉了一个螺栓、装配时混进一个垫片,这类问题靠人眼抽检迟早要翻车。我最近拆了一份机械零件目标检测数据集,5900 张图、5 个类别、同时给了 VOC 和 YOLO 两套标注格式,拿到手就能直接喂给检测网络。它解决的不是“从零标注”的问题,而是“标注完还要转格式、还要清洗、还要划分”的重复劳动。适合谁?做工业质检 Demo 的算法同学、带学生做课程设计的导师、想验证自己模型在零件场景下泛化能力的从业者。如果你手头正缺一份能直接跑通的机械零件数据,这份资源值得花十分钟看完怎么用。
2. 拆开压缩包:VOC 与 YOLO 双格式的目录结构与类别映射
拿到一个数据集,我第一件事不是急着训练,而是把目录结构摸清楚。因为格式不对、类别对不上,后面训练再花哨都是白搭。这份资源同时提供 VOC 和 YOLO 两套标注,意味着你可以跳过格式转换这一步,但前提是你得知道两套格式各自长什么样、类别索引怎么对应。
2.1 VOC 格式:Annotations 与 JPEGImages 的配对逻辑
VOC 格式的核心是两张表要对上:图片在JPEGImages/下,标注在Annotations/下,文件名(不含扩展名)必须一一对应。标注文件是 XML,里面记录了图片尺寸、每个目标的类别名和边界框坐标(xmin、ymin、xmax、ymax,左上角加右下角)。
常见做法是先用脚本扫一遍配对情况,确认没有孤儿文件。我一般会跑这么一段:
import os import xml.etree.ElementTree as ET img_dir = "VOCdevkit/VOC2007/JPEGImages" ann_dir = "VOCdevkit/VOC2007/Annotations" # 收集图片和标注的文件名(不含扩展名) img_names = {os.path.splitext(f)[0] for f in os.listdir(img_dir) if f.endswith(('.jpg', '.png'))} ann_names = {os.path.splitext(f)[0] for f in os.listdir(ann_dir) if f.endswith('.xml')} # 找出只有图片没标注、或只有标注没图片的情况 only_img = img_names - ann_names only_ann = ann_names - img_names print(f"图片总数: {len(img_names)}, 标注总数: {len(ann_names)}") print(f"缺标注的图片: {len(only_img)} 个 -> {list(only_img)[:5]}") print(f"缺图片的标注: {len(only_ann)} 个 -> {list(only_ann)[:5]}") # 统计每个类别的目标数量 cls_count = {} for ann_file in os.listdir(ann_dir): if not ann_file.endswith('.xml'): continue tree = ET.parse(os.path.join(ann_dir, ann_file)) for obj in tree.getroot().findall('object'): name = obj.find('name').text cls_count[name] = cls_count.get(name, 0) + 1 print("类别分布:", cls_count)这段脚本干三件事:检查图片和标注是否一一对应、统计缺失情况、统计每个类别的目标框数量。参数上没什么需要改的,把img_dir和ann_dir换成你解压后的实际路径就行。跑完你会对数据集的完整性心里有数——如果某个类别只有几十个框,那训练时就得考虑过采样或者类别权重了。
2.2 YOLO 格式:labels 目录下的归一化坐标
YOLO 格式就简洁得多:每张图对应一个.txt文件,每行是一个目标,格式为类别索引 x_center y_center width height,全部是归一化到 0~1 的值。类别索引从 0 开始,对应一个classes.txt或data.yaml里的类别列表。
这里最容易踩的坑是类别索引的顺序。VOC 里用的是类别名(比如 “bolt”、“nut”),YOLO 里用的是数字索引。如果两套格式的类别顺序不一致,你混着用就会把螺栓标成垫片。我一般会写个脚本把两边的类别映射关系打印出来核对:
import os # 读取 YOLO 的类别列表 with open("classes.txt", "r", encoding="utf-8") as f: yolo_classes = [line.strip() for line in f.readlines() if line.strip()] print("YOLO 类别索引映射:") for idx, name in enumerate(yolo_classes): print(f" {idx} -> {name}") # 从 VOC 标注里提取所有出现过的类别名 import xml.etree.ElementTree as ET voc_classes = set() ann_dir = "VOCdevkit/VOC2007/Annotations" for ann_file in os.listdir(ann_dir): if ann_file.endswith('.xml'): tree = ET.parse(os.path.join(ann_dir, ann_file)) for obj in tree.getroot().findall('object'): voc_classes.add(obj.find('name').text) print("\nVOC 中出现的类别:", sorted(voc_classes)) print("两边类别是否一致:", set(yolo_classes) == voc_classes)逻辑说明:先读 YOLO 的类别文件,再扫一遍 VOC 标注提取实际出现的类别名,最后做集合比较。如果输出False,说明两套格式的类别定义有出入,必须手动对齐后再用。参数上注意classes.txt的路径,有些打包方式会把它放在labels/同级或者data.yaml里,按实际情况改。
2.3 5 类机械零件的典型分布与划分建议
5 个类别在机械零件场景下,常见的是螺栓、螺母、垫片、轴承、齿轮这类组合,但具体是哪 5 类得看你解压后的classes.txt。不管具体类别是什么,5900 张图分到 5 个类,平均每类 1180 张,但实际分布往往不均匀——大零件(比如齿轮)出现频率高,小零件(比如垫片)可能偏少。
我一般按 7:2:1 划分训练集、验证集、测试集。如果某个类别样本特别少,会在训练集里做过采样,但验证集和测试集保持原始分布,这样才能真实反映模型在产线数据上的表现。划分脚本可以用sklearn的train_test_split,但要注意按类别分层抽样,否则某个类别可能在验证集里一个都没有。
提示:划分前先把所有图片文件名和对应的标注路径整理成一个列表,打乱后再切分,避免因为文件名排序导致某个类别的图片集中在某一批。
3. 从零跑通 YOLO 训练:data.yaml 配置与超参设置
格式核对完之后,就可以进入训练环节了。这份数据集给了 YOLO 格式,最省事的路径就是直接用 Ultralytics 的 YOLO 系列来跑。但“能跑”和“跑得好”之间差着一堆参数,这一章把配置和调参的细节拆开讲。
3.1 data.yaml 的五个关键字段
YOLO 训练依赖一个data.yaml文件,里面至少要有这几个字段:
path: /home/user/dataset # 数据集根目录 train: images/train # 训练集图片相对路径 val: images/val # 验证集图片相对路径 test: images/test # 测试集图片相对路径(可选) nc: 5 # 类别数量 names: # 类别名称列表,顺序必须和 labels 里的索引一致 - bolt - nut - washer - bearing - gear这里最容易翻车的是names的顺序。YOLO 的标签文件里存的是数字索引,训练时按names列表去映射类别名。如果顺序错了,模型学到的就是错的类别。我一般会拿一张图做可视化验证:用cv2把标注框画出来,看看框的位置和类别名是否对得上。
import cv2 import os img_path = "images/train/001.jpg" label_path = "labels/train/001.txt" names = ["bolt", "nut", "washer", "bearing", "gear"] img = cv2.imread(img_path) h, w = img.shape[:2] with open(label_path, "r") as f: for line in f: cls_id, xc, yc, bw, bh = map(float, line.strip().split()) # 归一化坐标转像素坐标 x1 = int((xc - bw / 2) * w) y1 = int((yc - bh / 2) * h) x2 = int((xc + bw / 2) * w) y2 = int((yc + bh / 2) * h) cv2.rectangle(img, (x1, y1), (x2, y2), (0, 255, 0), 2) cv2.putText(img, names[int(cls_id)], (x1, y1 - 5), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0, 255, 0), 2) cv2.imwrite("vis_check.jpg", img) print("可视化结果已保存,打开 vis_check.jpg 核对类别和框位置")这段代码把 YOLO 格式的归一化坐标还原成像素坐标,画框并标注类别名。跑完打开图片看一眼,如果框的位置明显偏了或者类别名对不上,说明names顺序或者坐标解析有问题。参数上注意names列表要和data.yaml里完全一致。
3.2 训练命令与 batch size、imgsz 的取舍
配置好data.yaml之后,训练命令本身不复杂:
yolo detect train \ data=data.yaml \ model=yolov8n.pt \ epochs=100 \ imgsz=640 \ batch=16 \ lr0=0.01 \ patience=20 \ project=runs/train \ name=mech_parts逐项说明:model选yolov8n.pt是最小的预训练权重,适合先跑通流程;如果显存够,可以换yolov8s.pt或yolov8m.pt。imgsz=640是默认输入尺寸,机械零件如果普遍偏小,可以提到 800 或 1024,但显存占用会明显上升。batch=16在 8GB 显存上比较稳,如果 OOM 就降到 8。lr0=0.01是初始学习率,配合patience=20做早停——20 个 epoch 验证指标不提升就停,省时间。
我一般会先跑 10 个 epoch 看看 loss 曲线和 mAP 走势,确认没有明显异常再拉长到 100 或 200。如果一开始 loss 就炸了,多半是学习率太大或者数据有问题,别硬跑。
3.3 训练过程中的指标解读与早停策略
YOLO 训练输出里几个关键指标:box_loss(框回归损失)、cls_loss(分类损失)、mAP50(IoU 阈值 0.5 时的平均精度)、mAP50-95(IoU 从 0.5 到 0.95 的平均)。机械零件检测里,如果零件尺寸差异大,mAP50-95往往比mAP50低不少,这是正常的。
早停策略上,patience=20是个保守值。如果数据集只有 5900 张,训练很快,可以设patience=30给模型更多机会。但要注意:如果验证集 mAP 一直在震荡不上升,可能是学习率没降下来,可以加cos_lr=True让学习率按余弦退火。
注意:训练日志里如果
cls_loss一直不降,优先检查类别标签是否越界(比如索引从 1 开始而不是 0),这是 YOLO 格式最常见的翻车点。
4. 避坑与排查:机械零件数据集训练中的五类常见问题
这一章是我自己踩过的坑,也是周围人问得最多的。每条按“现象 → 原因 → 解决”写,你对照着排查能省不少时间。
4.1 现象:训练 loss 正常但 mAP 始终为 0
原因:类别索引越界或者names列表长度和nc不一致。YOLO 在计算分类损失时,如果标签里的类别索引超过了nc-1,会直接忽略该目标,导致模型学不到任何东西。
解决:跑一遍标签检查脚本,统计所有标签文件里出现的最大类别索引,确认它小于nc。同时核对data.yaml里names的长度是否等于nc。
4.2 现象:验证集 mAP 很高但实际推理时框全偏了
原因:训练时用了rect=True(矩形推理)或者数据增强里的mosaic导致模型对边界敏感,而推理时用了默认的 letterbox 填充方式,两者坐标映射不一致。
解决:推理时显式指定imgsz和训练时一致,并且用augment=False关闭测试时增强。如果还是偏,检查验证集的标注是否和训练集用了同一套坐标定义(VOC 的 xyxy 和 YOLO 的 xywh 别混)。
4.3 现象:某个类别几乎检测不到,其他类别正常
原因:类别样本极度不平衡。5900 张图里如果某个类只有几十个实例,模型会倾向于把它预测成背景或其他大类。
解决:在data.yaml里给这个类别加权重,或者用copy_paste增强把该类别实例复制到其他图上。更直接的办法是过采样包含该类别的图片,但要注意验证集不能过采样,否则指标虚高。
4.4 现象:训练到一半突然 OOM 或者 loss 变 NaN
原因:学习率太大或者 batch size 太大导致梯度爆炸。机械零件数据集里如果有些图分辨率特别高,imgsz设大了也会在后期触发 OOM。
解决:把lr0降到 0.001 或 0.005,batch减半,加amp=True开启混合精度。如果 loss 已经 NaN,从上一个保存的权重继续训练,别从头再来。
4.5 现象:VOC 和 YOLO 两套格式混用后类别错乱
原因:VOC 的类别名和 YOLO 的索引没有做显式映射,直接拿 VOC 的 XML 去生成 YOLO 标签时,类别顺序按字母排序了,和classes.txt不一致。
解决:写一个固定的映射字典,比如{"bolt": 0, "nut": 1, "washer": 2, "bearing": 3, "gear": 4},生成标签时严格按这个字典取索引,不要依赖sorted()或set()的默认顺序。
5. 进阶技巧:用 VOC 格式做交叉验证与模型导出
跑通 YOLO 训练之后,这份数据集的价值还没榨干。VOC 格式的标注可以拿来做交叉验证,也可以导出成 ONNX 或 TensorRT 部署到边缘设备。这一章讲两个我常用的进阶操作。
5.1 用 VOC 标注做 5 折交叉验证
YOLO 官方没有直接提供交叉验证的命令,但可以用 VOC 格式的标注文件自己划分 5 折。思路是把所有图片按类别分层分成 5 份,每次取 1 份做验证、其余 4 份做训练,跑 5 次取平均 mAP。
import os import xml.etree.ElementTree as ET from sklearn.model_selection import StratifiedKFold import numpy as np ann_dir = "VOCdevkit/VOC2007/Annotations" img_dir = "VOCdevkit/VOC2007/JPEGImages" # 构建样本列表和主类别标签(取每张图里出现次数最多的类别作为分层依据) samples = [] labels = [] for ann_file in os.listdir(ann_dir): if not ann_file.endswith('.xml'): continue tree = ET.parse(os.path.join(ann_dir, ann_file)) classes = [obj.find('name').text for obj in tree.getroot().findall('object')] if not classes: continue # 取众数作为该图的主类别 main_cls = max(set(classes), key=classes.count) samples.append(os.path.splitext(ann_file)[0]) labels.append(main_cls) samples = np.array(samples) labels = np.array(labels) skf = StratifiedKFold(n_splits=5, shuffle=True, random_state=42) for fold, (train_idx, val_idx) in enumerate(skf.split(samples, labels)): print(f"Fold {fold}: train={len(train_idx)}, val={len(val_idx)}") # 这里把 train_idx 和 val_idx 对应的文件名写入各自的 train.txt / val.txt with open(f"fold{fold}_train.txt", "w") as f: for i in train_idx: f.write(samples[i] + "\n") with open(f"fold{fold}_val.txt", "w") as f: for i in val_idx: f.write(samples[i] + "\n")逻辑说明:用StratifiedKFold按主类别分层,保证每折里各类别比例接近。参数n_splits=5是折数,random_state=42固定随机种子方便复现。跑完会生成 5 组 train/val 文件列表,分别训练后取平均 mAP,比单次划分更能反映模型稳定性。
5.2 导出 ONNX 并验证推理一致性
训练完的 YOLO 权重可以导出成 ONNX,方便在 C++ 或 TensorRT 环境里部署:
yolo export model=runs/train/mech_parts/weights/best.pt format=onnx imgsz=640 opset=12 simplify=True参数说明:format=onnx指定导出格式,imgsz=640要和训练时一致,opset=12是 ONNX 算子集版本,simplify=True会做图优化。导出后一定要用同一张图分别跑 PyTorch 和 ONNX 推理,对比输出框的差异。如果差异超过 1e-3,检查预处理(归一化、letterbox)是否一致。
我一般会写个小脚本做一致性验证:
import onnxruntime as ort import numpy as np import cv2 # 预处理:letterbox 到 640x640 def preprocess(img_path, imgsz=640): img = cv2.imread(img_path) h, w = img.shape[:2] scale = min(imgsz / h, imgsz / w) nh, nw = int(h * scale), int(w * scale) resized = cv2.resize(img, (nw, nh)) canvas = np.full((imgsz, imgsz, 3), 114, dtype=np.uint8) canvas[:nh, :nw] = resized blob = canvas[:, :, ::-1].transpose(2, 0, 1).astype(np.float32) / 255.0 return np.expand_dims(blob, 0) session = ort.InferenceSession("best.onnx") input_name = session.get_inputs()[0].name blob = preprocess("test.jpg") outputs = session.run(None, {input_name: blob}) print("ONNX 输出形状:", outputs[0].shape)这段代码只做到推理输出,后续还要做 NMS 和坐标还原。重点是确认 ONNX 输出和 PyTorch 输出在相同输入下的数值差异。如果差异大,优先检查opset版本和预处理是否对齐。
从那以后我每次导出模型都强制走一遍一致性验证,哪怕只差一个小数点也要查清楚。希望帮到你。
本文还有配套的精品资源,点击获取