资讯动态

YOLOv5齿轮缺陷检测数据集训练避坑指南:目录格式与数据划分

发布时间:2026/10/27 17:44:05 来源:尧图企业网站定制
简介面向目标检测与工业质检场景的YOLOv5格式齿轮缺陷数据集包含表面擦伤、齿轮掉牙、齿轮不足三个类别共2392张真实拍摄的800×600 RGB图像每张均含多个目标框适合小目标检测与密集检测研究。压缩包共2000个文件其中1999个txt标注文件严格匹配YOLOv5目录规范另附1个可视化脚本可随机读取图片并绘制边界框便于快速验证标注质量。数据已划分为训练集2382张与验证集596张配套标签一一对应下载解压后即可直接接入YOLOv5训练流程无需额外转换。资源包整体41.27MB体量适中适合目标检测初学者或工业视觉从业者用于缺陷识别模型的快速迭代。目前已有911人次学习下载标注完整、目录清晰是实践小目标检测的实用数据基础。1. 齿轮缺陷检测数据集3类目标为什么先讲目录格式再讲训练收到一份标注好的齿轮缺陷检测数据集3个类别训练集、验证集已经分开看起来万事俱备。很多人第一反应是直接跑train.py结果要么val的mAP高得可疑要么训练到一半loss变成inf要么推理时类别编号对不上——这类翻车我见过太多次根子很少在模型结构上而在YOLOv5目录格式本身标签txt的编号从几开始、图片和标签是否同名、train和val有没有数据泄漏。这篇文章把这类数据集的目录检查、划分重切、训练参数、避坑排错完整过一遍适合第一次用目标检测数据集训练YOLOv5的从业者也适合准备把这份3类别齿轮数据投入产线的工程师。先说明一点这份数据只包含train和val没有test最终评估时你得自己留一手里。2. YOLOv5目录格式解剖images与labels的同名约定和验证脚本2.1 标准目录结构images/train、labels/val与文件同名规则YOLOv5目录格式的核心约定一句话图片放在images下标签放在labels下train和val分别对应目录结构长这样dataset/ ├── images/ │ ├── train/ # 训练图片jpg或png │ └── val/ # 验证图片 └── labels/ ├── train/ # 每张图片对应一个txt └── val/YOLOv5在训练时按「图片路径 把images替换成labels」去找标签文件所以images和labels必须是同级目录命名不能改成img/ann这类自定义名字。另一个硬性约定是文件同名图片叫gear_20240115_batch03_001.jpg标签就必须叫gear_20240115_batch03_001.txt扩展名无所谓主名必须完全一致。从网上下载的所谓YOLO格式数据集里偶尔会混进darknet风格的train.txt路径列表文件那种格式YOLOv5不认需要先确认你拿到的是目录结构而不是索引文件。拿到手第一件事不是看标注长什么样而是先扫一遍目录完整性。下面的命令依次统计图片总数、标签总数并找出有图没标签的「孤儿文件」echo 图片总量: $(find dataset/images -name *.jpg | wc -l) echo 标签总量: $(find dataset/labels -name *.txt | wc -l) echo --- 只有图片没有标签的孤儿文件 --- for img in $(find dataset/images -name *.jpg); do txt${img/images/labels} txt${txt%.jpg}.txt [ -f $txt ] || echo $img done这段bash把dataset/images/train/xxx.jpg的路径先替换成labels目录再把后缀换成txt然后判断文件是否存在。如果输出里出现dataset/images/val/xxx.jpg这类路径说明标注导出漏了图。还有一种情况是图片是png但脚本只扫了jpg会出现大面积误报实际检查时要把jpg和png都纳入或者直接把-name *.jpg改成-name *.*再加一层后缀过滤。另外目录路径里尽量不要带中文和空格YOLOv5在Windows下对带空格路径的支持时好时坏这是玄学问题没必要赌。2.2 一个txt里可能有多行类别编号、中心坐标与归一化齿轮缺陷检测是目标检测不是分类一张图里可能有多个缺陷所以标签txt里每一行代表一个缺陷实例。每行格式固定为5个字段class x_center y_center width height其中class是类别编号从0开始后四个值是归一化后的中心点坐标和宽高范围都在0到1之间。比如0 0.523 0.483 0.142 0.088表示类别0的缺陷中心在图片52.3%宽、48.3%高的位置占图片宽14.2%、高8.8%。为什么用归一化而不是绝对像素因为YOLOv5训练时会把输入resize到640或1280归一化坐标在resize、letterbox之后可以直接等比缩放不需要关心原图分辨率。这也是从VOC格式转YOLO格式时必须做归一化的原因。用一段简单的Python脚本读一个标签文件能快速确认格式是否正常label_path dataset/labels/val/gear_001.txt with open(label_path, r, encodingutf-8) as f: lines f.readlines() for line in lines: parts line.strip().split() if len(parts) ! 5: print(f格式错误(字段数不对): {line}) continue cls int(parts[0]) x, y, w, h map(float, parts[1:]) if not (0 x 1 and 0 y 1 and 0 w 1 and 0 h 1): print(f归一化越界: {line}) else: print(f类别{cls}: 中心({x:.3f},{y:.3f}) 宽高({w:.3f},{h:.3f}))注意几个细节第一如果一行只有4个字段说明标注导出时漏了类别字段这类文件YOLOv5读的时候会直接报错第二w或h小于0.005的框在640尺度下只有3个像素左右多半是标注软件误操作生成的噪点框后面可以考虑过滤掉第三cls超过2说明类别编号从1开始或者标注工具混入了第四类这个问题后面第5章单独讲。2.3 快速摸底类别分布、空标签与脏标签训练前值得花30秒统计类别分布和空标签文件这两个数据直接决定你要不要调整训练策略echo --- 训练集各类别框数量 --- for f in dataset/labels/train/*.txt; do awk {print $1} $f; done | sort -n | uniq -c echo --- 空标签文件数量 --- find dataset/labels -name *.txt -size 0 | wc -l第一段命令把训练集所有标签文件的第一列类别编号拼成一个流排序后计数输出类似120 0、85 1、240 2这样的结果。如果三个类别的框数量相差5倍以上训练时少数类基本学不出来需要做样本均衡或调整focal loss参数。空标签文件是背景图或标注遗漏的图片它们混在val里会让mAP波动混在train里会让模型学到「这张图没有缺陷」的错误先验务必在训练前处理掉。这里的处理不是简单删除而是看数量如果只有几张删掉对应图片即可如果有几十张且都是正常的良品齿轮图说明数据采集时故意拍了无缺陷样本它们应该单独保留做负样本背景集。这一点第5章会展开。3. 训练集与验证集重切先排查数据泄漏再谈划分比例3.1 严格讲训练集、测试集、验证集是三个东西标题只有前两个很多YOLOv5教程把训练集、测试集、验证集三个词混着说但在这份数据里目录只有train和val没有test。训练集负责更新权重验证集负责选模型和调超参测试集才是最后汇报泛化能力的依据。只有train和val意味着你调参时用的val同时也被拿来对外报指标结果必然偏乐观——因为你的早停、学习率调整都是看着val做的模型已经「偷看」过val了。我的做法是如果这份数据后续要部署到产线先把val按比例再切出一部分当test比如val有200张就切60张出来单独放成test目录训练时完全不碰它最后训完跑一次test拿最终指标。注意顺序不能反先切test再从剩余数据里切val而不是先训再切。如果是纯粹学习练手train和val也够用但要清楚你报的mAP是「调参后的val成绩」不是「盲测成绩」。另外验证集的数量不是越多越好val太小比如少于50张mAP曲线会剧烈抖动早停经常误判val太大又挤压训练数据。一般经验是val占总数据10%到20%这个比例已经是目标检测数据集里的常见做法。3.2 连续帧泄漏齿轮数据最常见的虚高来源齿轮缺陷数据大多来自转台或产线相机连续采集同一个齿轮按不同角度拍出一串连续帧。如果按文件名排序后机械地切前80%做train、后20%做val很可能出现train的最后10张和val的前10张几乎是同一角度、同一缺陷的相似帧。YOLOv5没有做去重这些高度相似的图片会让val的mAP变成「开卷考试」成绩高得不敢信。判断有没有连续帧泄漏不需要看图片看文件名就够了。像gear_20240115_batch03_001.jpg这种命名里如果有batch、frame、seq这类字段先按字段分组。如果文件名没有任何批次信息只能按文件修改时间或目录来源猜测。产线相机按时间戳命名的情况时间戳前若干位相同的就是同一次拍摄批次。3.3 重切脚本按批次粒度切分train/val一个批次不拆开下面这个脚本按文件名里的batch字段把图片分组整组整组地划入train或val避免同一采集批次的连续帧被拆散import random import shutil from pathlib import Path random.seed(42) IMG_DIR Path(dataset/images/train) LBL_DIR Path(dataset/labels/train) OUT_DIR Path(dataset_resplit) VAL_RATIO 0.2 def batch_of(filename: str) - str: parts filename.split(_) for p in parts: if p.lower().startswith(batch): return p.lower() # 取 batch03 作为分组标识 return filename[:6] # 兜底没有batch字段时按文件名前6字符 groups {} for img in IMG_DIR.glob(*.jpg): g batch_of(img.name) groups.setdefault(g, []).append(img) all_groups list(groups.keys()) random.shuffle(all_groups) val_group_count max(1, round(len(all_groups) * VAL_RATIO)) val_groups set(all_groups[:val_group_count]) out_img_val OUT_DIR / images / val out_lbl_val OUT_DIR / labels / val out_img_val.mkdir(parentsTrue, exist_okTrue) out_lbl_val.mkdir(parentsTrue, exist_okTrue) for g in val_groups: for img in groups[g]: lbl LBL_DIR / (img.stem .txt) if not lbl.exists(): print(f孤儿图跳过移动: {img.name}) continue shutil.move(str(img), str(out_img_val / img.name)) shutil.move(str(lbl), str(out_lbl_val / lbl.name)) print(fval 命中 {len(val_groups)} 批共 {len(list(out_img_val.glob(*.jpg)))} 张)逻辑说明先按batch字段把图片归组再对组列表随机打乱取前20%的组作为val。这样同一个batch的全部图片要么一起进train要么一起进val连续帧泄漏被掐断在源头。VAL_RATIO按0.2设置符合常见留出比例如果你的数据总量很小比如不到300张可以提到0.3保证val至少有几十张。需要注意脚本执行的是shutil.move会真的移动文件没有后悔药建议先复制整个dataset目录再跑。另外glob只扫了jpg如果图片是png要改成glob(*.png)或者用glob(*)后按后缀过滤。3.4 类别框数量统计三类分布不均时怎么办重切划分之后再统计一次各类别框数量确保val和train的类别分布大致一致from collections import Counter from pathlib import Path counts Counter() for lbl in Path(dataset/images/val).parent.parent.joinpath(labels/val).glob(*.txt): for line in lbl.read_text().splitlines(): parts line.strip().split() if parts: counts[int(parts[0])] 1 for cls in range(3): print(f类别 {cls}: {counts.get(cls, 0)} 个框)如果某一类框的数量只有其他类的五分之一比如裂纹缺陷在3类里只占8%那么模型大概率会把它学成「漏检专业户」。应对办法按优先级排序第一去标注源头补充这类样本宁可少不要偏第二对少数类做离线增强复制粘贴小缺陷到不同背景上生成新标注第三训练时调高focal loss的gamma让模型更难忽略少数类。数据均衡这件事在齿轮缺陷场景里特别值得花时间因为产线上的合格品远多于缺陷品缺陷框天然稀少属于典型的长尾分布。4. 把YOLOv5训练跑通data.yaml、超参数与失败信号4.1 data.yamltrain/val指向images目录不是labelsYOLOv5训练前要准备一个data.yaml描述数据路径和类别名。写法如下# gear_defect.yaml path: . # 数据集根目录相对于执行train.py的位置 train: dataset/images/train val: dataset/images/val nc: 3 names: 0: pitting # 示例类别名实际以你的数据集说明为准 1: crack 2: broken最容易犯的错是把train和val指到labels目录。YOLOv5约定data.yaml里写的是图片路径标签路径由代码内部自动替换获取。如果你写的是dataset/labels/train训练会直接报找不到图片。path字段是相对路径的锚点YOLOv5会把path和train拼接成完整路径所以path: .表示从当前命令行所在目录出发。在Windows上如果路径带反斜杠或盘符建议直接用绝对路径省心。names的key必须从0连续递增0、1、2不能写1: xxx开头否则类别索引错位。类别名用英文或拼音不要用中文否则控制台和results.csv里的显示会乱码。4.2 训练命令与yolov5超参数里真正值得先动的三个环境配好、数据就位之后训练命令本身并不复杂python train.py \ --data gear_defect.yaml \ --weights yolov5s.pt \ --img 640 \ --batch 16 \ --epochs 100 \ --hyp data/hyps/hyp.scratch-low.yaml \ --name gear_v1 \ --patience 20参数选择--weights yolov5s.pt用官方预训练权重做迁移学习显存小于6G就换yolov5n.pt。--img 640是速度和精度的平衡点如果齿轮裂纹只有几个像素宽可以试--img 1280代价是显存占用和训练时间大约翻4倍先用640跑通流程再说。--batch按显存调8到32之间不用刻意追求大batch。--workers 4在Linux没问题Windows下workers大于0经常触发DataLoader报错老老实实设成0虽然慢但稳定。--patience 20表示val指标连续20个epoch不提升就早停对齿轮这种小数据集很实用不用硬等100个epoch跑完。超参数不要在命令行里一条条传而是改hyp.scratch-low.yaml里的值。三个必调项lr0默认0.01齿轮数据集通常只有几百到几千张图样本少、batch小0.01偏激进容易震荡改成0.005或0.003更稳。mosaic默认1.0意思是每张训练图都由4张图拼接而成。如果齿轮缺陷是小目标mosaic会把一个完整齿轮切成四块缺陷也跟着被切没建议改成0.5实在跑不动就设0。fl_gamma默认0.0这是focal loss的gamma参数。3类缺陷框数不平衡时改成1.5让难分的少数类获得更高的损失权重。额外说一句如果你看过yolov8训练自己的数据集教程会发现YOLOv5这套目录格式和data.yaml在ultralytics的yolov8、yolov11上也能直接喂命令换成yolo detect train datagear_defect.yaml modelyolov8s.pt epochs100但超参数改法和yolov5完全不同别把hyp里的值照搬过去。4.3 训练过程中该盯哪几个信号训练启动后打开另一个终端盯着results.csvtail -f runs/gear_v1/results.csv这个文件每行记录一个epoch的loss和指标重点看三列train/box_loss、val/box_loss、metrics/mAP_0.5。正常情况train/box_loss平滑下降val/box_loss先降后升是过拟合信号配合--patience会自动早停。mAP0.5在训练后期还在缓慢爬升说明没收敛可以加epochs。如果出现train loss降、val loss从第一个epoch就涨这不是超参问题是数据划分泄漏或val里有脏标注回头检查第2、3章的内容。训练会自动保存两个权重文件best.pt是val mAP最高的时刻last.pt是最后一个epoch。训练中断了想续跑用--resume runs/gear_v1/weights/last.pt这个机制会恢复学习率调度器比手动换weights重新训要稳得多这是我屡试不爽的后悔药。5. 避坑排查3类齿轮缺陷训练最容易翻车的5个场景5.1 类别编号从1开始模型把三个类认成四个类现象训练不报错但跑完看results.png里的混淆矩阵出现一个空行或空列推理时偶尔输出类别编号3val的mAP始终上不去。原因标注工具或转换脚本从1开始编号txt里写的是1、2、3而data.yaml的names从0开始YOLOv5按0到2解析编号3被当成了不存在的第四类训练时的类别数变成4。解决先确认最大类别编号find dataset/labels -name *.txt -exec awk {print $1} {} | sort -n | tail -1正常输出是2输出3说明编号整体偏移。用Python批量减一from pathlib import Path for p in Path(dataset/labels).rglob(*.txt): lines p.read_text().splitlines() fixed [] for line in lines: parts line.split() if not parts: continue cls int(parts[0]) - 1 # 1,2,3 - 0,1,2 if cls 0: print(f非法类别 {parts[0]}: {p}: {line}) continue parts[0] str(cls) fixed.append( .join(parts)) p.write_text(\n.join(fixed) \n)如果日志里出现非法类别说明数据本身混用了两套编号标准有一部分从0开始、一部分从1开始这种只能回到标注源头修别指望脚本一把梭。5.2 val里有空标签文件mAP被背景误检拉低现象训练正常但val的mAP曲线抖动剧烈一个epoch 0.9下一个掉到0.7又弹回来precision尤其不稳定。原因val目录里有良品齿轮图没有缺陷标签txt是0字节。YOLOv5对这类图预测时只要输出任何一个框都算误检precision被无意义地拉低早停也跟着误判。解决训练前统计并处理空标签find dataset/labels -name *.txt -size 0 | wc -l如果只有几张删掉空标签和对应图片即可。如果有几十张且都是刻意采集的良品图别删把它们单独放到dataset/background目录训练时通过data.yaml的background相关配置或直接在val里保留——但要明确知道这些背景图会压低precision这是真实业务分布的一部分不是数据错误。真正要警惕的是「本来有缺陷但标注遗漏」造成的空标签那种图留在val里会白白给模型送分。5.3 训练集验证集数据泄漏val mAP高得不敢信现象val的mAP0.5冲上0.97precision和recall都异常顺滑但换一批新拍的齿轮图实测mAP直接掉到0.5到0.6。原因连续帧泄漏同一个齿轮的不同角度帧同时进了train和valval变成开卷考试。类似地如果采集时同一缺陷在train和val里各出现一次模型等于背过答案。解决回到第3章的按批次重切脚本。更严格的做法是再跑一遍感知哈希去重对train和val的图片算dhash或phash相似度大于0.9的图从val里剔除。对齿轮这种表面纹理特征明显的物体连续帧的哈希相似度经常在0.95以上这个指标比肉眼扫图可靠得多。我在第3章末尾提过别嫌麻烦这一条值得做。5.4 假jpg与EXIF旋转训练到一半崩在第300张图现象训练进度走到某个epoch突然报RuntimeError: image must be uint8或cv2.error中断在某一批数据上重启后换个随机种子又好了时好时坏。原因数据里混了RGBA或CMYK的png改成jpg后缀的假jpg或者是相机直出的图带了EXIF方向标记OpenCV的imread不解析EXIF图是旋转的但标注框没跟着转坐标对不上。解决训练前统一清洗一遍图片转成RGB的纯JPEGfrom PIL import Image from pathlib import Path for p in Path(dataset/images).rglob(*): if p.suffix.lower() not in (.jpg, .jpeg, .png): continue img Image.open(p) if img.mode ! RGB: print(f转RGB: {p}, mode{img.mode}) img img.convert(RGB) img.save(p, JPEG, quality100)convert(RGB)会把EXIF方向信息丢弃这对YOLOv5反而是好事——OpenCV读图本来就不认EXIF你不在训练前洗掉推理时同样一张图在不同软件里看到的方位就不一样标注框和图像内容错位。跑这段代码之前记得先备份整个images目录覆盖写原图这件事没有任何后悔药。5.5 归一化越界与过小框loss变成inf小目标AP永远为零现象训练前几百步loss正常某一步突然变成inf之后再也没回来或者某类缺陷的AP0.5始终是0。原因标注框超出了图像边界归一化坐标出现负数或大于1YOLOv5在做anchor匹配时算出极端值梯度爆炸。小裂纹框只有1到2个像素在640缩放下特征图下采样到20x20时直接消失模型根本看不到。解决写一个清洗函数把越界框转成角点坐标再裁剪顺便滤掉过小的框def clamp_yolo_line(line: str, min_size: float 0.005) - str | None: cls, x, y, w, h line.strip().split() x, y, w, h map(float, (x, y, w, h)) # 转成角点坐标再裁剪避免中心点表示在边界处失真 x1 max(0.0, x - w / 2) y1 max(0.0, y - h / 2) x2 min(1.0, x w / 2) y2 min(1.0, y h / 2) if x2 - x1 min_size or y2 - y1 min_size: return None # 太小直接丢掉 return f{cls} {(x1 x2) / 2:.6f} {(y1 y2) / 2:.6f} {x2 - x1:.6f} {y2 - y1:.6f}为什么先转成角点再clip中心点加宽高的表示在裁剪边界时必须同时满足左侧和右侧不出界直接clamp中心点和宽高会改变框的几何语义得到错误的框。min_size0.005对应640尺度下约3个像素小于这个值的框在深层特征图上已经不可见留着只会增加损失噪声。跑完清洗后再用第2章的越界检查脚本扫一遍确保没有漏网。6. 用val.py验证mAP可信度漏检图筛查与人工抽验6.1 训练后用val.py快速筛查漏检图训练结束只看mAP数字远远不够我习惯跑一次完整的val评估把预测结果输出成文件再逐张图查漏检python val.py \ --data gear_defect.yaml \ --weights runs/gear_v1/weights/best.pt \ --save-txt --save-conf --project runs/gear_v1_val # 找出val里没有任何预测输出的图片 for f in dataset/labels/val/*.txt; do base$(basename $f) [ -f runs/gear_v1_val/labels/$base ] || echo 漏检: $base done--save-txt会把每张图的预测结果写到runs/gear_v1_val/labels目录下文件名和真实标签同名。循环里每张有真实标签的图如果找不到对应预测文件说明模型在这张图一个框都没输出。对齿轮缺陷检测来说漏检率超过10%基本不能用不用再看mAP。--conf-thres默认0.25如果现场对漏检更敏感可以降到0.1再跑一遍用更多误检换召回对比两次漏检图差异能直观看到置信度阈值的影响。6.2 人工抽验的土办法拼图比曲线更早发现问题mAP可以被脏标签抬高——比如某个缺陷漏标注了模型在val上预测出它反而算误检val指标惩罚了「正确的行为」。所以我对mAP的信任度排序永远是人工抽验大于漏检率大于mAP数字。具体做法是把val里置信度最低的20张预测图拼成一张大图人眼扫一遍重点看两类情况一是模型把齿轮表面纹理误判成缺陷二是真实裂纹处没有框。用montage或者Python的PIL都能做关键是把「置信度最低的预测」筛出来看因为mAP只统计大于阈值的框低置信度区间的错误不会影响mAP但会在现场部署时全部暴露。我第一次拿这类齿轮缺陷数据集训练时没做任何检查就开跑val mAP出了0.95以为自己调参功力见长后来换了一台新设备采集的齿轮数据mAP掉到0.6才意识到是连续帧泄漏和空标签文件把指标喂饱了。踩过这个坑之后我把数据检查的脚本固定成了一套入口动作先查目录和标签格式再查划分泄漏再清洗越界框和假jpg最后才轮到train.py。这个过程看起来琐碎但恰恰是决定模型能不能落地的关键。希望帮到你。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑