机械零件目标检测数据集:VOC与YOLO格式转换及训练实践

发布时间:2026/10/12 0:20:25
机械零件目标检测数据集:VOC与YOLO格式转换及训练实践 简介这是一份面向计算机视觉与工业质检场景的常见机械零件目标检测数据集涵盖轴承、螺栓、法兰、齿轮、螺母、弹簧等典型零件合计约2.4万个标注框采用VOC与YOLO两种通用格式组织便于直接接入主流检测框架训练与评估。包体共2000个文件其中xml标注文件1999个用于记录目标位置与类别信息另附说明txt文件压缩包约224.71MB数据组织清晰适合目标检测入门学习者、算法工程师以及工业视觉质检、自动化分拣方向的研究人员使用。目前已有923人学习下载。借助该数据集读者能快速掌握VOC与YOLO标注格式的转换方法验证不同检测模型在机械零件识别上的精度也可用于数据增强、特征分析和模型调优实验为机械零件自动识别提供高质量训练样本。1. 机械零件检测的数据饥渴这个5900张5类数据集能省你两周时间做工业视觉的同行应该都有同感目标检测入门简单但真正卡住你的往往不是模型而是数据。通用数据集里COCO有80类、VOC有20类拉到产线上一测全是“背景干扰”螺丝、轴承、齿轮这些小零件识别得一塌糊涂。尤其机械零件这类目标尺寸小、反光强、互相遮挡通用预训练权重在真实车间里基本等于从零开始。这个“目标检测常见机械零件数据集5900张5类VOCYOLO.zip”解决的正是这个痛点5900张已经标注好的机械零件图5个常见类别同时打包了VOC和YOLO两种格式下载解压就能喂给模型训练省掉最脏最累的采集和标注环节。适合谁做工业质检、零件分拣、自动化设备视觉方案的工程师以及毕设方向是检测的学生。它的价值不在于模型精度有多高而在于让你绕开“标注一个零件数据集要两周”这个真实成本。2. VOC和YOLO两种标注格式的底层差异先读懂再动手2.1 两种格式各自长什么样目录结构、边界框坐标系与标签映射拿到压缩包先别急着解压训练。这个数据集同时提供VOC和YOLO两种格式是为了兼容不同训练框架但两者对边界框的描述方式完全不同混用是新手最容易翻车的地方。VOC格式源自PASCAL VOC挑战赛它的标签是XML文件每个文件对应一张图片。关键信息在object节点下name存类别名bndbox存边界框的四角坐标xmin、ymin、xmax、ymax。注意这里的坐标是绝对像素值图片是1920像素宽坐标就是0到1920之间的整数。人读着直观但模型训练时需要额外换算。YOLO格式源自Darknet框架现在被ultralytics等库继承。它是纯文本的TXT文件每行对应一个目标格式是class_id x_center y_center width heightx_center、y_center、width、height全部是相对于图片宽高的归一化值范围在0到1之间。模型读取时直接拿这些比值做计算不需要再关心原图尺寸。两种格式之间存在一个固定的换算关系x_center (xmin xmax) / 2 / image_width y_center (ymin ymax) / 2 / image_height width (xmax - xmin) / image_width height (ymax - ymin) / image_height换算本身不复杂真正的坑在数据集目录结构。VOC格式的标准组织方式是Annotations放XML、JPEGImages放原图、ImageSets/Main放训练/验证集划分文件而YOLO格式一般要求镜像目录images放图、labels放TXT还要保证同名文件一一对应一个图片文件对应一个同名TXT文件图片是part_001.jpg标签就必须是part_001.txt。任何一级目录错位ultralytics训练时直接报“No labels found”或者更隐蔽的“Found no valid images”。2.2 自写Python脚本完成VOC到YOLO的转换全套代码与参数说明虽然这个数据集自带两种格式但你在实际项目中经常只会拿到其中一种所以转换脚本必须会写。我一般用一个短脚本处理VOC转YOLO代码如下import xml.etree.ElementTree as ET import os from pathlib import Path # 类别映射表顺序就是YOLO的class_id # 注意这个顺序必须和训练时的data.yaml保持一致 class_mapping { bearing: 0, # 轴承 bolt: 1, # 螺栓 gear: 2, # 齿轮 nut: 3, # 螺母 washer: 4 # 垫圈 } def convert_voc_to_yolo(xml_path, output_dir, img_width, img_height): 将单个VOC XML文件转为YOLO TXT格式 xml_path: VOC标注文件路径 output_dir: 输出目录存放转换后的TXT文件 img_width/img_height: 对应图片的实际宽高 tree ET.parse(xml_path) root tree.getroot() # 优先读取XML里的图片尺寸如果缺失才用传入参数 size root.find(size) if size is not None: img_width int(size.find(width).text) img_height int(size.find(height).text) lines [] for obj in root.iter(object): # 跳过difficult目标这些是公认的难样本强行训容易出噪声 difficult obj.find(difficult) if difficult is not None and int(difficult.text) 1: continue name obj.find(name).text if name not in class_mapping: print(f[WARN] 未知类别 {name}跳过文件: {xml_path}) continue cls_id class_mapping[name] bbox obj.find(bndbox) xmin float(bbox.find(xmin).text) ymin float(bbox.find(ymin).text) xmax float(bbox.find(xmax).text) ymax float(bbox.find(ymax).text) # 边界框越界修正标注软件偶尔给出超出图像范围的坐标 xmin max(0, min(xmin, img_width)) xmax max(0, min(xmax, img_width)) ymin max(0, min(ymin, img_height)) ymax max(0, min(ymax, img_height)) # 过滤掉退化成线段的异常框 if xmax xmin or ymax ymin: print(f[WARN] 非法边界框跳过: {xml_path}) continue # 核心换算公式VOC四角坐标 - YOLO归一化中心点宽高 x_center (xmin xmax) / 2.0 / img_width y_center (ymin ymax) / 2.0 / img_height width (xmax - xmin) / img_width height (ymax - ymin) / img_height # YOLO格式要求所有值在0~1之间超界的说明原标注就有问题 line f{cls_id} {x_center:.6f} {y_center:.6f} {width:.6f} {height:.6f} lines.append(line) if lines: txt_name Path(xml_path).stem .txt out_path Path(output_dir) / txt_name with open(out_path, w, encodingutf-8) as f: f.write(\n.join(lines)) def batch_convert(annotations_dir, output_dir): 批量处理整个Annotations目录 os.makedirs(output_dir, exist_okTrue) xml_files list(Path(annotations_dir).glob(*.xml)) print(f共找到 {len(xml_files)} 个XML文件) for xml_file in xml_files: convert_voc_to_yolo(xml_file, output_dir, None, None) print(f转换完成输出目录: {output_dir}) if __name__ __main__: # 替换成你自己的实际路径 batch_convert( annotations_dirVOC/Annotations, output_dirYOLO/labels )代码逻辑有这么几个关键点值得细说。第一class_mapping的顺序一旦定下来就不能改训练时data.yaml里的类别顺序必须和它完全一致否则所有框的类别全错位模型还能训出来但推理结果完全不可用。第二转换时做了两道防御坐标裁剪和非法框过滤。工业数据集里标注员手抖、标注软件导出bug导致的越界框和零面积框并不少见直接用会让损失函数计算NaN或者训练发散。第三所有值保留6位小数精度足够用还能避免浮点误差累积。转换跑完之后必须做一次反向验证抽一张图把TXT里的归一化坐标乘以图片宽高画框叠在原图上人工核对十几个样本确认边界框没有整体偏移。这一步十分钟的事能拦住绝大部分低级错误。3. 拿到数据集先别急着喂训练5900张图的四道质检关3.1 图片与标签对齐检查找出孤儿文件和重名覆盖数据集的目录结构看着规整不等于没有脏数据。我从网上下载过不少数据集最常见的问题是“孤儿标签”和“孤儿图片”要么XML/TXT存在但对应的JPG不存在要么反过来图片有但标签缺失。训练时这些文件一部分被静默跳过一部分当背景处理结果就是你的类别数对不上、精度神秘地差1到2个点。对齐检查用一段简单的Python就够了import os from pathlib import Path def check_alignment(images_dir, labels_dir): 检查图片目录和标签目录是否一一对应 返回缺失标签的图片列表和没有图片的标签列表 img_files {p.stem: p for p in Path(images_dir).iterdir() if p.suffix.lower() in [.jpg, .jpeg, .png, .bmp]} label_files {p.stem: p for p in Path(labels_dir).iterdir() if p.suffix.lower() in [.txt, .xml]} missing_labels [f for s, p in img_files.items() if s not in label_files] orphan_labels [s for s, p in label_files.items() if s not in img_files] print(f图片总数: {len(img_files)}, 标签总数: {len(label_files)}) print(f缺少标签的图片: {len(missing_labels)} 张 → {missing_labels[:5]}) print(f没有图片的标签: {len(orphan_labels)} 个 → {orphan_labels[:5]}) return missing_labels, orphan_labels # 检查YOLO格式目录 check_alignment(YOLO/images/train, YOLO/labels/train)另外一个隐蔽问题同一批图在Windows上解压再拷到Linux服务器文件名大小写和中文编码都可能变化图片能打开但标签匹配不上。稳妥做法是把所有文件名统一小写化再走一次对齐检查。3.2 统计类别分布和标注质量一张图看清数据是否均衡机械零件数据集天然存在类别不均衡。比如装配线上螺栓和垫圈的数量远多于轴承如果直接按原始分布训练模型会把高频类学得很好低频类几乎不检。先跑个统计脚本import os from pathlib import Path def count_classes(labels_dir): 统计YOLO标签目录下每个类别的目标数量 labels_dir: labels目录可以只传训练集 class_counts {} total_files 0 for txt_path in Path(labels_dir).rglob(*.txt): total_files 1 with open(txt_path, r) as f: for line in f: line line.strip() if not line: continue parts line.split() if len(parts) 5: continue cls_id int(parts[0]) class_counts[cls_id] class_counts.get(cls_id, 0) 1 print(f标注文件数: {total_files}) for cls_id in sorted(class_counts.keys()): print(f类别 {cls_id}: {class_counts[cls_id]} 个目标) # 计算不平衡程度最多类/最少类 if class_counts: max_count max(class_counts.values()) min_count min(class_counts.values()) print(f不均衡比率: {max_count / min_count:.2f} : 1) count_classes(YOLO/labels/train)如果比率超过10比1训练时必须采取措施最简单的做法是对少样本类别做过采样把包含该类别的图片在训练时多喂几遍或者给损失函数按类别加权重ultralytics的data.yaml里暂时不支持直接配class weights需要改到训练脚本里用loss_scale操作成本偏高。更省事的方案是少类别直接做数据增强我对垫圈这种小零件常用的增强组合是scale0.3加mosaic0.5能让模型在小零件上的表现明显改善。3.3 可视化抽查找一个最难检测的类别逐张过统计只能发现数字层面的问题视觉层面的脏数据还得靠人眼。我的习惯是写一个可视化脚本把每张图的标注框画出来按类别抽样导出九宫格拼图一分钟扫完一百张图。看什么三种典型问题漏标的零件、框过大或过小、类别标错的零件。import cv2 from pathlib import Path def visualize_labels(images_dir, labels_dir, target_class, sample_size20): 抽检指定类别的标注质量 target_class: 需要抽检的类别ID sample_size: 抽检张数 img_paths list(Path(images_dir).glob(*.jpg)) shown 0 for img_path in img_paths: if shown sample_size: break label_path Path(labels_dir) / (img_path.stem .txt) if not label_path.exists(): continue with open(label_path, r) as f: lines f.readlines() # 过滤出包含目标类别的标注 has_target any(line.startswith(f{target_class} ) for line in lines) if not has_target: continue img cv2.imread(str(img_path)) h, w img.shape[:2] for line in lines: parts line.strip().split() if not len(parts) 5: continue cls_id, xc, yc, bw, bh map(float, parts) if int(cls_id) ! target_class: continue # 归一化坐标转像素坐标 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.imwrite(fcheck_{target_class}_{shown}.jpg, img) shown 1 print(f已导出 {shown} 张可视化检查图)代码逻辑的核心在于startswith过滤方式它能匹配该类别ID开头的每一行避免把类别10误认成类别1开头的情况。画框用的是中心点换算回四角的公式如果这一步算错你看到的框就是错位或偏大的检查结果就全废了。标注质量的检查标准就一条框是否紧贴零件轮廓是否同时覆盖了零件的阴影。机械零件的边缘往往反光或带倒角标注员的习惯差异会导致同一物体的框宽窄不一致模型对这种不一致非常敏感。你不需要把每张都改但要记住验证集里混进大量标注松散的样本会造成精度虚低影响你判断模型改进方向。这类抽检每类20张足够。4. 用ultralytics跑通首轮训练配置、参数与日志解读4.1 准备数据集yaml5类机械零件的正确写法ultralytics的YOLO训练入口是yaml配置文件它决定了模型读哪里的图、哪里的标签、分哪几个类。一个典型的工业数据集yaml长这样# 数据集根目录推荐用绝对路径避免相对路径在不同机器上失效 path: /home/user/datasets/mechanical_parts # 训练、验证、测试集的图片目录注意是images不是labels train: images/train val: images/val test: images/test # 类别数量 nc: 5 # 类别名称列表顺序必须与标签TXT中的class_id严格一致 names: 0: bearing 1: bolt 2: gear 3: nut 4: washer这中间有三个细节得说清楚。第一path字段写绝对路径。用相对路径时ultralytics会在当前工作目录找数据集你从项目A目录启动训练、从项目B目录做个预览脚本数据集路径就全断了报一个让人摸不着头脑的“AssertionError: train dataset not found”。第二names的索引从0开始而且和TXT里的class_id必须完全一致之前转换脚本里的class_mapping如果定义成了{bearing: 1, bolt: 2}这里就必须同样错位对齐否则模型训练时等于把所有类别标签整体偏移一位推理结果形同虚设。第三train和val的值指向的是images子目录ultralytics会自动推测同级的labels目录所以你创建目录时必须是images/train配labels/train不要自己搞成imgs/train配anns/train推测逻辑会找不到标签。4.2 训练命令和关键参数yolov8n起步、批次与图片尺寸的取舍数据集准备完毕训练命令本身不复杂# 用ultralytics YOLOv8n起步小模型先跑通流程 yolo detect train \ modelyolov8n.pt \ datamechanical_parts.yaml \ epochs100 \ imgsz640 \ batch16 \ device0 \ project./runs \ nameparts_v8n命令里每个参数都有讲究讲过时的教训才是真的长记性。imgsz640是大多数YOLO模型的默认训练尺寸对机械零件这种小目标来说并不理想。5900张图里如果大量零件在画面中占比小于5%640尺寸下它们只有二三十个像素宽模型根本学不到细节特征。我一般看数据集的实际情况来定先统计标注框的宽高分布如果大量框小于32像素就直接上imgsz960甚至1280。代价是显存占用翻倍显存不够就降batch。batch16在8GB显存上跑yolov8n没有问题换了yolov8m就要降到8这个数值不是调参是资源约束下的上限。epochs100对工业数据集偏多但首轮训练就是为了看趋势跑满没坏处。关键在于要不要开pretrained。默认加载yolov8n.pt的COCO预训练权重对机械零件这种风格差异极大的域预训练权重能提供基础的特征提取能力效果一般比随机初始化好但如果你担心COCO的分布干扰训练也可以改成yolov8n.yaml从零训两种都试一轮对比验证集精度再定不猜。4.3 训练日志观察方法loss、P/R曲线和混淆矩阵的判断基准训练一旦跑起来不要干等。看日志有几个关键判据讲几个最常见的健康/病态特征正常训练时box_loss和cls_loss应该在前20个epoch快速下降然后进入缓慢衰减期。如果loss出现尖峰或nan先查数据集——大概率有标签坐标溢出或者无意中喂了损坏的图片而不是模型问题。mAP50和mAP50-95如果前10个epoch纹丝不动不是模型慢热而是配置有问题常见的是data.yaml里nc与标签类别数不一致或者数据增强太强把目标搞变形了。训练结束后一定要看混淆矩阵runs/parts_v8n/confusion_matrix.png。机械零件数据集的典型混淆对是螺栓和螺母这两个类别在视觉上高度相似混淆矩阵里对应位置数值偏高是正常的。真正要警惕的是某一行全黑但有值散布说明该类别的标注本身有问题或者是背景类误检过高——工业场景里背景噪点多背景误检率比自然图像数据集高一个量级这是后面调阈值时的重点。validation集的效果不能只看单次结果要看results.png里的验证曲线是否平稳。我习惯保留三次训练轮次的曲线对比如果验证mAP波动超过2个点先怀疑数据划分不好重新做一次分层抽样划分让每类的比例在训练和验证集里保持一致。5. 机械零件检测最常见的5个坑从现象到解决5.1 金属反光让模型检出“不存在”的零件现象训练loss正常验证mAP挺高但一上真实产线测试模型频繁把背景中的高光区域当成螺栓尤其金属表面的镜面反射形状和零件高度相似。原因训练数据里反光样本太少模型没有学到“反光不是零件”的判别依据。解决给训练集做针对性增强——用HSV空间增强把亮度通道随机拉高、降低模拟不同光照强度。更彻底的做法是采集一批纯背景高光负样本加入训练集告诉模型“这些不是目标”。5.2 类别不均衡引起小类全面漏检现象垫圈类别的mAP只有其他类的三分之一检测结果基本为零。原因数据集里垫圈只有几百个标注而螺栓有三千多个模型在损失函数中被多数类主导少数类的梯度贡献被淹没。解决不做重采样的情况下先给少数类目标做裁剪复制粘贴增强CopyPaste有效又不改变原图场景语义如果还是无效就单独用垫圈类的子集微调一轮冻结主干层只更新检测头。5.3 验证集划分不当导致mAP虚高现象训练轮次交换后验证mAP忽高忽低好的时候能到90%以上差的只有70%。原因按文件名哈希或随机方式划分数据集时同一个零件出现在不同角度的多张图被拆进了训练和验证两个集合等于验证时模型“见过”了同一物体的相似视角mAP虚高部署到新场景立刻打回原形。解决按“物体实例”划分最简单的做法是把同一批次、同一工位拍的图全部放进同一个集合。机械零件数据集没有现成的实例ID可以按文件名前缀例如batch01_开头的归一组划分。5.4 标签框过紧导致NMS后漏检现象单张图有多个零件紧挨着模型检测框大量重叠一通NMS把正确框也抑制掉了最后只留下一个零件。原因标注框太紧贴目标边缘而预测框天然会稍微外扩两者之间间隔不足NMS按IoU阈值0.5计算时把两个框当作同一个目标。解决调整conf和iou阈值是表面功夫根源是把训练数据的标注框适当外扩2~3个像素留出冗余。另一个有效手段是换用iou0.7做推理但需要重新验证验证集精度。5.5 样本重复或近重复导致过拟合现象训练集loss降得非常低验证集mAP却不涨训练曲线正常但实测泛化差。原因5900张数据集里可能包含大量相似图像尤其是固定工位拍摄的图片背景几乎不变只有零件位置略有偏移模型学到的是背景特征而不是零件特征。解决先做相似度去重用感知哈希算法算图片指纹相似度超过90%的只保留一张。这个操作在机械零件数据集上效果明显去重后虽然训练集变小但mAP可能反升1到2个点。6. 从验证集到真实产线用最小推理脚本确认模型真的能用训练完成不代表结束真正的考验是把模型从runs/parts_v8n/weights/best.pt拉到真实场景里跑通。我看过太多人在Jupyter里用测试集图片验证时精度不错一到实际部署就出问题原因往往不是模型而是推理脚本写得太随意。from ultralytics import YOLO # 加载训练好的模型 model YOLO(runs/parts_v8n/weights/best.pt) # 推理参数 # conf0.25 是默认值工业场景建议调高到0.4降低背景误报 # iou0.5 NMS阈值紧挨零件的场景可以降到0.3试试 # imgsz960 和训练尺寸尽量一致不要用640省显存尺寸不匹配会掉精度 results model.predict( sourcetest_images/part_001.jpg, conf0.40, iou0.50, imgsz960, saveTrue, project./runs/infer, namefirst_test ) # 打印检测结果关注每个框的置信度和类别 for r in results: boxes r.boxes for box in boxes: cls int(box.cls[0]) conf float(box.conf[0]) xyxy box.xyxy[0].tolist() # [x1, y1, x2, y2] 像素坐标 name model.names[cls] print(f类别: {name}, 置信度: {conf:.3f}, 框: {xyxy})这个脚本专门在真实场景下验证几个关键点。第一conf阈值调到0.4对比默认值0.25在背景误报上的差异。工业场景里漏检可以接受但不能有大量误检否则产线停机成本远大于漏检一个零件的成本。第二imgsz必须和训练尺寸对齐别觉得640比960省时间就随意改。输入分辨率变了模型学习到的目标尺度也随之失效验证mAP能掉5个点以上。第三逐个打印框的类别和置信度比只看saveTrue保存的检测图更直接你能看到哪些目标被低置信度地检出而这些低置信度框往往意味着需要补数据的方向。推理脚本跑通之后我还有一个固定动作把真实场景中拍摄的20张图单独拉一个hard_set包含各种恶劣条件——过曝、低照度、遮挡、零件堆叠。每轮模型迭代都在这20张图上跑一遍看对比结果而不是只看验证集mAP。mAP是个统计指标它无法告诉你某个具体零件在某种光照下是否会被漏检。这个习惯帮我拦下过很多次“指标好看但实际不能上线”的模型。数据集的复盘同样重要。5900张5类这个体量加上VOC和YOLO双格式从下载到拿到一份能用的模型正常节奏是两天半天做格式检查和质检半天调参训练半天推理验证和补数据。第一轮不要追求高精度目的是把所有环节跑通确认数据干净、格式正确、训练链路无报错精度提升放在第二轮迭代时针对漏检样本做。希望这个流程能帮你在机械零件检测上少走几趟弯路希望帮到你。本文还有配套的精品资源点击获取