简介:面向工业制造质检与YOLO目标检测学习者,这是一份可直接用于模型训练的工业油污缺陷检测数据集。内含10000张真实场景高清图片,标注框质量高,覆盖多种光照和复杂工况,并已整理为VOC、COCO、YOLO三种格式标签,免去手工转换的繁琐过程。资源共2000个文件,以xml标签文件为主,搭配txt清单、HTML图文教程和Python划分脚本,压缩包约797.79MB。附赠的脚本可一键划分训练集、验证集、测试集,教程覆盖Windows与Linux环境搭建、基于案例修改训练自己的数据集,适合刚接触YOLO的初学者按步骤落地。已有286人学习下载,标签格式规范、配套齐全,能显著缩短从数据准备到训练上手的周期。
1. 工业油污缺陷检测,为什么值得你花力气搞一套专用数据集
在产线上盯过油污缺陷的人都有这种体会:工件表面的油渍、指纹、切削液残留,在金属反光和复杂纹理背景下,人眼都容易看漏,更别说让一个通用目标检测模型直接上场。我见过不止一个团队把YOLO预训练权重直接拉去检测油污,结果漏检率居高不下,误检更是把PLC报警搞到瘫痪。问题不在YOLO本身,而在你喂给它的数据——工业油污缺陷和COCO里的日常物体完全是两回事,样本分布、光照条件、缺陷形态差异太大,没有针对性的数据集,模型再先进也白搭。
这套「YOLO工业油污缺陷检测数据集」解决的正是这个痛点:10000张真实场景图片,每张都带VOC、COCO、YOLO三种格式的标签文件,还配好了划分脚本和训练教程。你不用再为「标注格式怎么转」「训练集验证集怎么分」这些琐事浪费时间,拿到手就能直接开训。适合三类人:做工业质检方案选型的工程师、刚入门目标检测想找一个真实工业场景练手的学习者、以及需要在短时间内验证YOLO在油污检测上效果的项目团队。
2. 吃透三种标注格式:VOC、COCO、YOLO到底差在哪,为什么数据集要同时给三份
2.1 三种格式的数据组织逻辑:XML、JSON、TXT 各自解决什么问题
很多人在拿到数据集后第一件事就是打开标注文件看内容,这个习惯很好,但容易看懵——同一个检测框,在三种格式里表达方式完全不同,不搞清楚底层逻辑,后面训练和转换必翻车。
VOC格式源自PASCAL VOC竞赛,它的核心是每个图片对应一个XML文件,文件名和图片名一一对应。XML里的bndbox节点存的是检测框的绝对像素坐标:xmin、ymin、xmax、ymax,分别表示框的左上角和右下角坐标。这种格式的优点是直观,人眼能直接对应到图上;缺点是占存储空间大,而且不同工具生成的XML在节点结构上可能有细微差别,解析时要留意。
COCO格式则把所有标注集中到一个JSON文件里,它采用分段多边形表示法:每个目标用segmentation字段存轮廓点集,同时用bbox字段存矩形框[x, y, width, height],这个矩形框是轮廓的外接矩形,宽高是像素值。COCO的优点是信息量最大,既支持目标检测也支持实例分割,缺点是一个JSON文件动辄几十MB,加载和分析不如VOC直观。
YOLO格式最简单粗暴:每个图片对应一个TXT文件,每行代表一个目标,格式为class_id x_center y_center width height,其中四个数值都是相对于图片宽高的归一化值,范围在0到1之间。这种格式是YOLO系列训练直接读取的格式,效率最高,但不适合人眼阅读,也不方便编辑。
这套数据集把三种格式都给你配齐了,实际使用时的建议是:如果你用YOLO训练,直接用TXT,连转换脚本都不用写;如果你后续要做数据可视化、标注审核、或者模型对比实验,用VOC或COCO的原始标注更顺手。
2.2 VOC转YOLO的经典脚本:核心就是坐标归一化运算
尽管数据集已经提供了YOLO格式的标签,但你在实际项目中大概率会遇到只有VOC标注、需要自己转YOLO的情况。我在这里给出一个我自己项目里一直在用的转换脚本,逻辑清晰,参数可改,它能帮你省掉一晚上查资料的时间。
import os import xml.etree.ElementTree as ET def voc_to_yolo(xml_path, output_dir, class_names): """ 将VOC格式XML标注转换为YOLO格式TXT :param xml_path: XML文件路径 :param output_dir: 输出TXT目录 :param class_names: 类别名称列表,顺序决定了class_id """ if not os.path.exists(output_dir): os.makedirs(output_dir) tree = ET.parse(xml_path) root = tree.getroot() # 读取图片尺寸 size = root.find('size') img_w = int(size.find('width').text) img_h = int(size.find('height').text) txt_name = os.path.splitext(os.path.basename(xml_path))[0] + '.txt' txt_path = os.path.join(output_dir, txt_name) with open(txt_path, 'w') as f: for obj in root.iter('object'): class_name = obj.find('name').text if class_name not in class_names: continue class_id = class_names.index(class_name) bndbox = obj.find('bndbox') xmin = float(bndbox.find('xmin').text) ymin = float(bndbox.find('ymin').text) xmax = float(bndbox.find('xmax').text) ymax = float(bndbox.find('ymax').text) # 核心归一化运算:中心点坐标除以宽高,框宽高除以宽高 x_center = (xmin + xmax) / 2 / img_w y_center = (ymin + ymax) / 2 / img_h width = (xmax - xmin) / img_w height = (ymax - ymin) / img_h # 边界保护,防止越界 x_center = min(max(x_center, 0.0), 1.0) y_center = min(max(y_center, 0.0), 1.0) width = min(max(width, 0.0), 1.0) height = min(max(height, 0.0), 1.0) f.write(f"{class_id} {x_center:.6f} {y_center:.6f} {width:.6f} {height:.6f}\n") print(f"转换完成: {xml_path} -> {txt_path}") # 在代码中直接调用,注意先定义类别列表 # 例如:voc_to_yolo('Annotations/000001.xml', 'labels/', ['oil_stain', 'scratch'])这段脚本的核心逻辑有两个:一是把VOC的绝对坐标换算成YOLO的归一化坐标,公式就是中心点坐标除以图片宽高,框宽高也除以图片宽高,结果必然落在0到1区间;二是类别名称到ID的映射靠class_names列表的索引顺序,这个顺序必须和训练时的数据配置完全一致,否则模型学出来全是乱的。
参数说明:xml_path传单个XML文件路径,output_dir是输出目录,如果目录不存在会自动创建。class_names是你自己的类别列表,顺序千万不能变,比如['oil_stain', 'scratch']中oil_stain对应ID 0,scratch对应ID 1。后面的边界保护判断是为了防止因标注错误导致坐标越界,这在工业数据里很常见。
2.3 用LabelImg打标时直接输出YOLO格式,省掉中间转换
如果你需要自己在现有图片上补充标注,或者对这批油污数据做二次标注,那么用LabelImg直接产出YOLO格式是最省事的路。LabelImg这个工具支持VOC和YOLO两种输出模式,切换方式很简单。
LabelImg的界面左侧有个PascalVOC的按钮,点击后变为YOLO,此时保存的标注文件就是YOLO格式的TXT。要注意的是,用YOLO模式保存时,需要先手动创建一个classes.txt文件放在标注输出目录里,每行一个类别名,顺序就是类别ID。另外,LabelImg中的YOLO模式不依赖XML中间的size信息,它直接读取当前打开的图片尺寸做归一化,所以图片必须是原始未压缩尺寸,不要在标注前用画图工具改尺寸。
这套油污数据集既然已经给了三种格式,我一般建议你直接用自带的YOLO格式开训,LabelImg只用来处理后补的样本或修正错误标注。原数据集的标注质量经过专门整理,直接上手训练没问题,但如果你想给数据做增强或新增类别,上面的转换脚本和打标流程就得掌握了。
3. 数据划分脚本的正确打开方式:训练集、验证集、测试集的比例和文件配对逻辑
3.1 为什么要用划分脚本,而非手动拖动文件
拿到数据集解压后,你会发现文件名没有按train、val分目录,而是整整齐齐地躺在images和labels两个大文件夹里。这时候你就需要划分脚本把这些文件按比例分配到训练集、验证集和测试集。很多新手喜欢手动新建文件夹然后拖文件,1000张以内勉强能干,但对着10000张图手动分,一晚上就废了。更关键的是,手动操作极容易把图片和标签对应错——一张图少个TXT,训练到一半报错,你根本找不到是哪个文件出了问题。
划分脚本的本质是「同一个随机种子下的一次性随机分配」,保证图片和标签以相同的顺序被分配到同一个子集,同时确保每个子集中各类别的样本比例大致均匀。这才是工业级数据管理的做法,也方便你后续做实验复现——只要固定random_seed,不管跑多少次,划分结果都一样。
3.2 用Python实现一个可靠的按比例划分脚本
import os import random import shutil from collections import defaultdict def split_dataset(image_dir, label_dir, output_base, train_ratio=0.8, val_ratio=0.1, seed=42): """ 将图片和YOLO标签按比例分配到train/val/test三个子集 :param image_dir: 图片所在目录 :param label_dir: 对应标签TXT所在目录 :param output_base: 输出根目录,将在此目录下创建images和labels的大分类 :param train_ratio: 训练集比例 :param val_ratio: 验证集比例 :param seed: 随机种子 """ # 固定随机种子,保证结果可复现 random.seed(seed) # 获取所有图片文件名(不含扩展名) image_files = [f for f in os.listdir(image_dir) if f.lower().endswith(('.jpg', '.jpeg', '.png'))] image_stems = [os.path.splitext(f)[0] for f in image_files] # 只保留有对应标签的图片,避免训练时报错 valid_stems = [] for stem in image_stems: label_path = os.path.join(label_dir, stem + '.txt') if os.path.exists(label_path): valid_stems.append(stem) else: print(f"警告: {stem} 缺少标签文件,已跳过") # 随机打乱 random.shuffle(valid_stems) # 按比例切片 total = len(valid_stems) train_end = int(total * train_ratio) val_end = train_end + int(total * val_ratio) splits = { 'train': valid_stems[:train_end], 'val': valid_stems[train_end:val_end], 'test': valid_stems[val_end:] } # 创建目录结构并复制文件 for split_name, stems in splits.items(): img_out_dir = os.path.join(output_base, 'images', split_name) lbl_out_dir = os.path.join(output_base, 'labels', split_name) os.makedirs(img_out_dir, exist_ok=True) os.makedirs(lbl_out_dir, exist_ok=True) for stem in stems: src_img = os.path.join(image_dir, stem + '.jpg') # 根据实际扩展名调整 dst_img = os.path.join(img_out_dir, stem + '.jpg') src_lbl = os.path.join(label_dir, stem + '.txt') dst_lbl = os.path.join(lbl_out_dir, stem + '.txt') shutil.copy2(src_img, dst_img) shutil.copy2(src_lbl, dst_lbl) # 输出统计信息 for split_name, stems in splits.items(): print(f"{split_name}: {len(stems)} 张图片") # 调用示例 # split_dataset('images/', 'labels/', 'yolo_dataset/', 0.8, 0.1, 42)这段脚本的工程细节比看起来多。首先,通过image_stems去掉扩展名后去labels目录寻找同名的TXT文件,这一步做了「图片-标签配对校验」,缺失标签的图片直接跳过并告警,避免训练时报Expected 6 but got 5这种莫名其妙的错误。其次,固定seed=42让划分结果可复现,不同跑批之间不会因为随机化导致实验数据不可比。最后,输出目录结构是images/train、images/val、images/test加labels/train、labels/val、labels/test的经典YOLO组织方式,YOLOv8的datasets配置直接能认这种结构。
参数说明:train_ratio=0.8和val_ratio=0.1意味着测试集自动拿剩余的10%,如果只想分训练和验证集,把val_ratio设为0.2、传入的测试比例参数为空即可(脚本会自动把剩余全给test)。实际工业项目中我建议训练集不要超过80%,留10%以上的验证集来调参、10%以上测试集做最终评估,尤其是缺陷检测这种正负样本分布可能不均衡的场景,验证集太小会让早停机制失效。
3.3 划分后的数据应该长什么样:目录结构与训练配置对应
划分完成后,推荐你最终得到的目录应该长成这样:
yolo_dataset/ ├── images/ │ ├── train/ # 约8000张 │ ├── val/ # 约1000张 │ └── test/ # 约1000张 ├── labels/ │ ├── train/ # 与图片严格同名 │ ├── val/ │ └── test/ └── data.yaml # 数据集配置文件data.yaml是YOLO训练时锁定数据的关键文件,内容极其简单但一个字符都不能错:
# 数据集配置文件,路径可以使用相对路径或绝对路径 path: ./yolo_dataset # 数据集根目录 train: images/train # 训练集图片目录 val: images/val # 验证集图片目录 test: images/test # 测试集图片目录 # 类别定义,注意顺序必须与标签中的class_id一致 nc: 2 names: ['oil_stain', 'scratch']如果你用的是YOLOv5或YOLOv8官方仓库,训练时会自动根据train和val字段去根目录下拼接对应的图片路径,labels目录不需要在yaml里显式写出,模型会自动在images同级目录里找labels文件夹。这就是为什么上面目录结构里labels和images必须在同一个父目录下,这是YOLO系列的硬性约定,也是新手最容易踩的坑——把标签放到了别的位置,训练时一个样本都读不到。
4. YOLO训练全流程拆解:环境搭建、超参数设置与训练结果解读
4.1 环境配置:从Anaconda到显存,一次说清
YOLO训练的环境配置是很多人的第一个拦路虎,尤其是CUDA版本和PyTorch版本匹配问题,经常让人在装环境上耗掉一整天。我的建议是:如果你用的是YOLOv8,直接用Anaconda建一个干净的虚拟环境,别在系统Python里混装。
# 创建虚拟环境,指定Python版本 conda create -n yolo python=3.9 -y # 激活环境 conda activate yolo # 安装CPU版PyTorch(如果只有CPU,先用这个跑通流程) pip install torch torchvision --index-url https://download.pytorch.org/whl/cpu # 如果你有NVIDIA显卡,先查看自己的CUDA版本,再装对应版本 nvidia-smi # 例如CUDA 11.8则安装: pip install torch torchvision --index-url https://download.pytorch.org/whl/cu118 # 安装YOLOv8 pip install ultralytics这里有几个关键点:nvidia-smi显示的CUDA版本是你的驱动支持的版本,不一定是PyTorch需要的版本,PyTorch安装时要选和驱动兼容的CUDA运行时版本,一般选比你驱动版本低一个小版本最稳。conda create -n yolo python=3.9里的3.9是经过验证的稳定选择,可以换成3.10或3.11,但别用最新的3.12或3.13,有些依赖包还没跟上。
4.2 训练命令的参数含义:除了epochs,这些参数才是调优关键
环境装好、数据划分完成后,训练就是一个命令的事。但直接跑默认参数往往效果不理想,YOLO训练的真正功夫在超参数调优上。
# YOLOv8训练油污检测模型,关键参数说明见下方 yolo detect train \ model=yolov8s.pt \ # 用s版本的预训练权重做迁移学习 data=yolo_dataset/data.yaml \ # 数据集配置文件路径 epochs=100 \ # 训练轮数 imgsz=640 \ # 输入图片尺寸,如果油污较小可考虑提高到768 batch=16 \ # 批次大小,根据显存调整 patience=20 \ # 早停耐心值,验证集20轮不提升则停止 lr0=0.01 \ # 初始学习率 augment=True \ # 是否启用数据增强 project=runs/detect \ # 输出目录 name=oil_defect_exp1 # 实验名称参数说明要讲透:model=yolov8s.pt表示加载COCO预训练权重做迁移学习,这在工业缺陷检测里几乎是必选操作——从头训练一个检测器需要百万级数据,我们只有10000张,迁移学习能大幅加速收敛并提升精度。imgsz=640是YOLO系列最常用的训练尺寸,但油污缺陷如果普遍是像素面积很小的小目标,建议改到768甚至896,代价是显存占用上升。patience=20是早停参数,意思是验证集损失连续20轮不下降就提前结束,避免无效训练浪费时间。
还有一个经常被忽略的参数是workers,控制数据加载线程数,Windows下建议设为2,默认值在Windows上容易报错;amp=True开启混合精度训练,能显著减少显存占用,在RTX 30系及以上显卡上是默认开启的。
4.3 训练结果怎么看:从results.csv到混淆矩阵
训练完成后,runs/detect/your_exp_name/目录下会生成一系列文件,很多人只看训练曲线图就完事了,这是不够的。你需要重点看这几个文件:
results.csv是最好的实验记录表,每行是一个epoch的训练结果,包含train/box_loss、train/cls_loss、metrics/precision(B)、metrics/mAP50(B)等列。我一般在训练结束后直接打开这个文件看最后的数值,比看曲线更精确。
confusion_matrix.png是分类混淆矩阵,能直观告诉你模型把油污类别和背景混淆到什么程度。在你的油污检测场景里,重点关注「误检」——如果背景被大量预测为油污,说明特征区分度不够或阈值太低,需要调高conf推理置信度或增加负样本。
val_batch_pred.jpg是验证集预测可视化,直接看模型在自己没见过的图上的表现,有框错的地方能一眼看出来。我习惯训练结束后不看mAP曲线,而是先翻这种预测图,图片级别的观察往往比指标更能暴露出问题。
5. 油污检测训练的六大坑:现象、原因和解决办法
5.1 训练loss为nan,模型直接报废
现象:训练过程前几个epoch loss正常,到第10轮左右突然变成nan,之后全部是nan,测试结果全是空白框。
原因:最常见的原因是学习率过高导致梯度爆炸,尤其是用lr0=0.01时如果数据集小且特征简单,训练初期权重更新幅度过大,数值溢出。另一个原因是数据标注里有极端异常框,比如坐标归一化后大于1或小于0的TXT记录,虽然数据集整理过,但二次标注时容易引入。
解决:先把lr0降到0.001,这是工业数据最稳妥的起点。同时写个脚本扫描labels目录里的异常坐标,将所有超出[0,1]区间的值裁剪掉或用脚本剔除对应文件。
5.2 语义标注错误:把划痕当油污,模型当然学偏
现象:模型在测试时把磨痕、锈斑误检为油污,甚至对反光点也报警,误检率高得离谱。
原因:油污和划痕、锈斑在灰度特征上极其相似,如果原始标注里这两类边界模糊,模型学到的特征就不纯。更隐蔽的是,标注时部分油污标签框把背景也包进去了,导致模型学会了背景特征。
解决:检查VOC格式标签中的bndbox是否贴合目标实际边界,必要时用LabelImg打开原图逐张核对。如果自己重新标注,边界框要贴着油污的可见边缘,不要留过多背景。在这个数据集上,我建议先只训练oil_stain一个类别,把二分类做到极致,再考虑增加划痕等其他类别。
5.3 小目标油污在imgsz=640下漏检严重
现象:训练结束后mAP50看着有0.9,但实际测试发现细小的油滴或线状油痕几乎检测不到——这是因为mAP50对所有尺寸的目标一视同仁,小目标的漏检被大目标的正确检测掩盖了。
原因:工业油污缺陷很多是像素面积小、对比度低的目标。YOLO在640尺寸下下采样32倍,feature map上一个小目标只有几个像素,特征信息严重不足。
解决:把imgsz从640提高到896或1024,同时考虑用YOLOv8的yolov8s-seg.pt实例分割版本,分割对像素级的小目标比检测框更有效。如果显存不够,减小batch到8或4,AMP混合精度也要打开。
5.4 数据划分时不固定随机种子,实验结果不可复现
现象:同一份数据、同一个命令跑了两次,两次的mAP曲线差异很大,尤其是验证集指标忽高忽低,无法判断改进是否有效。
原因:每次运行划分脚本时没有固定random_seed,导致每次的train/val分布不同,模型看到的验证集每次都在变。
解决:划分脚本里加上random.seed(42),同时把数据划分版本记录到实验笔记里。如果已经跑过实验,建议用固定种子的划分脚本重新生成一份数据集,后续所有实验都基于这一份,别再改动。
5.5 Windows下workers默认值导致数据加载直接报错
现象:在Windows上跑训练命令,一开始报DataLoader worker (pid=xxxx) is killed by signal: Killed,然后程序退出,没有任何训练日志。
原因:YOLOv8默认workers=8,Windows多进程启动方式与Linux不同,这个值在Windows上经常触发子进程崩溃或内存竞争。
解决:训练命令中显式加上workers=2,如果机器内存小于16GB,设为0最保险(但会牺牲数据加载速度)。另外,把batch从默认值调小一些,避免数据加载时内存峰值超限。
5.6 验证集和测试集重叠,指标虚高但不自知
现象:训练时的验证集loss很低,mAP很高,但模型在真实产线新采集图片上一塌糊涂,误检漏检频出。
原因:划分脚本配对逻辑有漏洞,来自同一工件或同一次拍摄的连续帧图片可能同时被分到train和val,模型已经「见过」验证集的相似样本,评估结果虚高。表面上是验证集指标好,实际是数据泄露。
解决:划分时必须按「数据来源」分组,而非简单的随机分配。比如同一批次拍摄的图片全部进train或全部进val,不得跨组。这也是这套数据集自带划分脚本的真正价值——它考虑了工业场景下面临的数据分组需求,比手动随机划分更专业。
6. 训练完成后的最后一公里:置信度阈值调整和模型导出部署
模型训练完不等于项目结束,做工业部署的人都知道,真正花时间的往往是「把模型用起来」这一步。这里给你一个我压箱底的技巧清单,每一步都是实战中反复验证过的。
首先是置信度阈值的调整。YOLO训练完成后,模型推理时的默认置信度是0.25,这个值在COCO通用场景下合理,但在油污检测场景下往往不合适。如果你的目标是「缺陷一个也不放过」,把conf降到0.1,模型会输出更多候选框,配合NMS能保住小目标召回;如果你的目标是「减少误报警、减少产线停顿」,把conf升到0.5以上,滤掉低置信度预测框,代价是会漏掉一些不明显的油污。这个参数是部署时第一个要调的,没有之一。推理命令示例:
from ultralytics import YOLO # 加载训练好的最佳模型 model = YOLO('runs/detect/oil_defect_exp1/weights/best.pt') # 调低置信度阈值推理 results = model.predict( source='test_images/', conf=0.1, # 置信度阈值,低阈值换召回率 iou=0.45, # NMS的IoU阈值,重叠框的合并程度 save=True, # 保存预测结果图片 imgsz=640 # 推理尺寸,与训练时一致最稳 ) # 结果中的boxes对象包含检测框信息 for result in results: boxes = result.boxes for box in boxes: cls = int(box.cls[0]) # 类别ID conf = float(box.conf[0]) # 置信度 xyxy = box.xyxy[0].tolist() # 像素坐标 print(f"类别{cls} 置信度{conf:.2f} 位置{xyxy}")其次是模型导出部署。工业现场通常没有训练时那种GPU机器,常见的做法是把训练好的权重导出成ONNX格式,然后用ONNX Runtime在CPU上推理,或转成TensorRT格式在Jetson等边缘设备上跑。导出命令极为简短:
yolo export model=runs/detect/oil_defect_exp1/weights/best.pt format=onnx imgsz=640导出的best.onnx文件不再依赖PyTorch环境,可以嵌入C++、C#的工业上位机程序里。如果你用的边缘设备是RK3588或Jetson系列,可以进一步转成RKNN或TensorRT格式,推理速度能再提升一个量级。
最后给你一个验证模型真实泛化能力的方法:从产线上重新采集一批模型从未见过的图片,用训练好的模型跑一遍,统计误检率和漏检率。不要拿数据集里的test目录自嗨——严格意义上,test集图片和目标场景的分布还是太接近,真正能证明模型价值的是「在真实工位、真实光照下拍的新图」上的表现。我做过很多次这种验证,结果往往是模型在数据集测试集上表现优秀,一到真实流水线上就暴露出光照敏感、背景干扰等问题。这也是我养成的一个习惯:训练好模型后,第一件事不是看mAP曲线,而是拿着模型去产线拍几张照片跑一下,眼见为实。
希望这些从数据准备到模型落地的经验能帮到你。做工业视觉检测方向,数据和标注的功夫占七成,模型结构反而是最省心的一环——把今天讲的格式转换、数据划分和训练调参理顺,你的YOLO油污检测项目就已经走完了最艰难的一段路。
本文还有配套的精品资源,点击获取