ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

焊接缺陷目标检测数据集的YOLOV5目录格式与训练避坑指南

焊接缺陷目标检测数据集的YOLOV5目录格式与训练避坑指南 简介这是一份面向计算机视觉目标检测任务的焊接表面缺陷图像数据集采用YOLOV5标准目录结构省去繁琐的格式转换适合算法工程师、科研人员直接用于模型训练与效果验证。数据集覆盖穿孔、折痕、压痕、内含物等10个常见焊接缺陷类别图像均为2048×1000分辨率的RGB图片并已划分出训练集1836张、验证集458张每张图片均对应一个txt标签文件类别信息一目了然可直接对接主流检测框架。压缩包大小约918MB共2000个文件以txt标注标签为主体另附一个开箱即用的show.py可视化脚本——随机传入一张图片即可绘制并保存边界框方便快速检查标注位置与类别是否正确同时也有助于初学者理解标签格式。目前已有234人浏览学习适合用于焊接缺陷检测相关的实验教学、模型训练或算法对比评测。1. 焊接表面缺陷图像目标检测数据集YOLOV5目录格式没理清训练再快也白搭做焊接质检的工程师拿到目标检测数据集第一反应往往是先跑模型结果在 YOLOV5 目录格式上翻车图像在 images 里标签却用了别的命名验证集分布和训练集重叠类别编号从 1 开始导致损失直接爆掉。这个标题里的数据集合计 10 类别训练集、验证集都已就位看起来“开箱即用”但如果不先把目录规则、类别映射和标签坐标确认一遍训练流程会在最无聊的地方卡住。它适合搞工业视觉、焊缝缺陷筛查以及想用 YOLOV5 把质检模型真正训练起来的人。下面就是我做过几轮后的整理。2. 焊接表面缺陷的10类别与YOLOV5目录格式先弄清数据形态再动手2.1 焊接缺陷10个类别缺陷命名、形态特征与标注框的边界焊接表面缺陷这 10 类在图像里长得并不像 MNIST 那样干净同一类缺陷在不同焊缝、不同光照、不同打磨程度下形态差异非常大。按我接触过的工业质检数据集常见组合是气孔、夹渣、未熔合、未焊透、咬边、焊瘤、飞溅、裂纹、凹陷、烧穿。这里的难点不在“认不认得出来”在于几类缺陷之间视觉边界模糊未熔合和未焊透都呈长条状咬边和凹陷都表现为轮廓中断飞溅和小气孔远看都是碎点。标注规范不一致模型就会在这个数据集上展现出明显的“玄学”行为——训练 loss 掉得很漂亮验证集 AP 却始终上不去。标注框的边界尤其影响训练结果。我一般给标注员定三条规矩第一气孔、夹渣这类近圆形缺陷用紧凑外接矩形框住可见缺陷主体即可不要包进大片背景第二裂纹按整条连续缺陷走长边覆盖裂纹两端短边紧贴裂缝留 2 到 3 个像素余量就够第三飞溅必须单独标绝不能为了省事把一片飞溅点合并成一个框。你可能会觉得这些细节和模型无关实际上 YOLOV5 的 anchor 分配逻辑对框的宽高比很敏感框里背景一多模型学到的特征是“背景缺陷”的混合体换一条新焊缝立刻露馅。还有一个容易被忽略的点类别之间的目标尺度极不平衡。气孔可能只有 10×10 像素裂纹却往往横跨几百像素且长宽比奇大。这种尺度差异直接决定了你后面训练时要不要把输入分辨率从 640 提到 1280也决定了 mosaic 增强要不要关。换句话说拿到数据集的第一件事不是跑训练而是打开 labels 目录把每个类别的框数量、宽高分布统计一遍搞清楚你面对的是“容易检的大目标”还是“必须抠细节的小目标”。2.2 YOLOV5目录格式images、labels、train、val的硬性结构标题里专门强调“YOLOV5目录格式”说明目录结构本身就是一个强约束。标准形态是下面这棵树dataset/ ├── images/ │ ├── train/ │ │ ├── weld_00001.jpg │ │ └── weld_00002.jpg │ └── val/ │ ├── weld_00101.jpg │ └── weld_00102.jpg └── labels/ ├── train/ │ ├── weld_00001.txt │ └── weld_00002.txt └── val/ ├── weld_00101.txt └── weld_00102.txt图像在 images 目录下按 train/val 分开标签则放到 labels 目录下同样结构的子目录里。最关键的一条图片文件名和标签文件名必须完全一致扩展名除外。weld_00001.jpg对应的只能是weld_00001.txt不能多前缀不能改后缀名。如果一个数据集里出现weld_00001.txt和weld_00001.jpg.txt混放的情况那训练时一半图像找不到标签程序静默跳过损失曲线照样往下走但 mAP 就是上不去。每个 txt 文件的内容是纯文本一行标注一个目标每行五个字段字段顺序含义取值说明1类别ID整数从 0 开始最大为 92目标中心点 X 坐标归一化到 0~1除以图像宽度3目标中心点 Y 坐标归一化到 0~1除以图像高度4目标宽度归一化到 0~1除以图像宽度5目标高度归一化到 0~1除以图像高度注意这里全是归一化浮点数不是像素值。有人会把绝对像素中心直接写进 txtYOLOV5 加载时不会立刻报错但坐标全部超出有效范围训练出来的模型基本是废的。另一个常见问题是把类别 ID 从 1 开始写导致第 10 类缺陷的 ID 变成 10超过nc10的合法范围 0~9训练跑到一半直接抛异常。提示检查数据集时先统计所有 txt 里第一个字段的最大值和最小值。最大值只要大于 9基本就是转换脚本没做“类别减 1”。2.3 训练集和验证集为什么这么分加载器按路径找同名文件错一点就漏一个YOLOV5 的数据加载逻辑并不聪明它只是机械地遍历images/train下的所有图片然后去labels/train找同名 txt。问题在于YOLOV5 的 Dataset 实现里遇到缺失标签的图像默认是直接跳过而不是报错。这就形成了最隐蔽的坑images/train有 1000 张labels/train只有 900 个 txt训练过程完全正常没有任何红色提示但那 100 张图实际上根本没参与训练。等模型上线遇到那 100 张图里出现的缺陷形态表现自然一塌糊涂。验证集的作用同样容易被低估。很多人把 val 当成一个“事后评分的工具”实际上在 YOLOV5 训练流程里验证集参与最佳权重选择。每一次 epoch 结束程序都会跑一次 val用验证集的 mAP 决定要不要保留当前权重。如果验证集里有大量未标注图片或者验证图像和训练图像来自同一条焊缝mAP 就失真了。焊接数据常见做法是把一条长焊缝切成多张训练图如果不按焊缝编号而是直接随机切分同一根焊缝的相邻切片会同时进入 train 和 val。训练时模型相当于“见过”同一场景的不同裁切版本验证指标虚高部署到全新工件上立刻现原形。正确的切分逻辑是按来源分组先按焊缝编号或工件编号把样本分成互不相交的组再整组划入训练集或验证集。这样虽然看起来“浪费”了一些可训练样本但验证结果才有参考价值。后续调整增强参数、比较不同模型结构时才不会让数据泄漏干扰判断。3. 整理焊接缺陷数据把标注框转成YOLOV5格式并划分训练集验证集3.1 按缺陷类别分层切分训练集与验证集写一个 split 脚本拿到原始数据后第一步不是转格式而是先把训练集和验证集切好。纯随机切分在焊接数据上不好用因为气孔这类常见缺陷样本多裂纹、烧穿这类少见缺陷样本少随机切分很容易让某一类缺陷在验证集里一个样本都没有。我用的是按类别组合分层的切分方式。import random import shutil from pathlib import Path from collections import defaultdict random.seed(42) # 固定随机种子保证每次切分结果一致 src_imgs Path(raw_images) # 原始图像目录 src_lbls Path(raw_labels) # 原始标签目录txt 格式 train_imgs Path(dataset/images/train) val_imgs Path(dataset/images/val) train_lbls Path(dataset/labels/train) val_lbls Path(dataset/labels/val) for d in [train_imgs, val_imgs, train_lbls, val_lbls]: d.mkdir(parentsTrue, exist_okTrue) # 只匹配 jpg 和 png如果你的数据里有 jpeg/bmp自行补上 img_files list(src_imgs.glob(*.jpg)) list(src_imgs.glob(*.png)) def read_classes(img_path): 返回一张图对应的类别ID集合用于分层 txt src_lbls / (img_path.stem .txt) if not txt.exists(): return {none} with open(txt) as f: return {line.split()[0] for line in f if line.strip()} # 按类别组合分组同组合的图放进同一个桶 buckets defaultdict(list) for img in img_files: classes tuple(sorted(read_classes(img))) buckets[classes].append(img) val_ratio 0.15 for classes, files in buckets.items(): random.shuffle(files) n_val max(1, int(len(files) * val_ratio)) if len(files) 5 else 0 val_set set(files[:n_val]) for img in files: lbl src_lbls / (img.stem .txt) if img in val_set: shutil.copy(img, val_imgs / img.name) if lbl.exists(): shutil.copy(lbl, val_lbls / lbl.name) else: shutil.copy(img, train_imgs / img.name) if lbl.exists(): shutil.copy(lbl, train_lbls / lbl.name)这段脚本的逻辑是先统计每张图包含哪些类别把类别组合完全相同的图放进同一组再在组内按比例切分。这样做的原因是保证验证集覆盖到所有类别组合而不是只保证总体数量比例。代码里有几个参数你要重点理解seed42是固定随机种子没有它你每次运行切分结果都不同后面做对比实验时无法复现val_ratio0.15表示 15% 的数据进验证集工业小数据集我建议留 15%~20% 之间别太少否则 mAP 波动会大得让你怀疑人生当某个组合总数少于 5 张时脚本会把它们全部留在训练集避免验证集只有一两张图导致评估方差过大。这个脚本还有一个检查项没做它没有打印每个类别在训练集和验证集中的具体数量。实际使用时我会在切分完成后加一段统计代码输出每个类别在两侧的样本数。如果发现某个类别验证集数量为 0说明这个类别在数据集中本身就极度稀少后续训练需要针对它做特殊处理而不是盲目开训。3.2 把VOC框转成YOLOV5的归一化txt五个字段一个都不能错焊接缺陷数据集里最常见的标注格式是 PASCAL VOC 的 XML也就是每个目标给xmin, ymin, xmax, ymax绝对像素坐标。转换到 YOLOV5 格式要做两步坐标改为中心点加宽高、全部归一化到 0~1。这里最容易出错的点是忘了除以图像宽高以及类别 ID 没有减 1。import xml.etree.ElementTree as ET from pathlib import Path from PIL import Image # 类别映射表键是XML里的name值是YOLO类别ID0~9 class_map { porosity: 0, slag_inclusion: 1, lack_of_fusion: 2, incomplete_penetration: 3, undercut: 4, overlap: 5, spatter: 6, crack: 7, sagging: 8, burn_through: 9, } xml_dir Path(voc_labels) out_dir Path(labels) out_dir.mkdir(exist_okTrue) for xml_file in xml_dir.glob(*.xml): # 这里假设图像和XML同名且为jpg实际以你的文件名为准 img Image.open(xml_file.with_suffix(.jpg)) W, H img.size lines [] root ET.parse(xml_file).getroot() for obj in root.findall(object): name obj.find(name).text if name not in class_map: # 打印未知类别名防止标注拼写不一致被静默跳过 print(funknown class: {name}) continue bbox obj.find(bndbox) x1 float(bbox.find(xmin).text) y1 float(bbox.find(ymin).text) x2 float(bbox.find(xmax).text) y2 float(bbox.find(ymax).text) # 越界纠正和过小框过滤防止负坐标/零宽高直接拖崩训练 x1 min(max(x1, 0), W - 1) y1 min(max(y1, 0), H - 1) x2 min(max(x2, 0), W - 1) y2 min(max(y2, 0), H - 1) if x2 - x1 2 or y2 - y1 2: continue cx (x1 x2) / 2 / W cy (y1 y2) / 2 / H bw (x2 - x1) / W bh (y2 - y1) / H lines.append(f{class_map[name]} {cx:.6f} {cy:.6f} {bw:.6f} {bh:.6f}) if lines: (out_dir / (xml_file.stem .txt)).write_text(\n.join(lines))转换脚本的核心逻辑就一句话把绝对坐标变成相对坐标。class_map是整个流程里必须严格对照 data.yaml 的地方两边顺序一旦错位训练不会报错但模型学出来的类别全乱套。比如 data.yaml 里names[0]是porosity而这里的class_map里porosity必须也是 0不能出现一边从 0 开始、另一边从 1 开始的情况。越界纠正不是可有可无的防御代码。标注软件里经常出现标注框的一角稍微超出图像边界的情况如果不把坐标 clip 回[0, W-1]和[0, H-1]后续损失计算可能产生 NaN。过小框过滤则是在处理飞溅这类细碎目标时必要的清洗手段宽度或高度小于 2 像素的框要么是误标要么小到模型根本无法学习留着只会给 loss 增加噪点。3.3 写data.yaml路径、类别名、编号必须和labels对得上YOLOV5 训练时真正读取的配置文件是 data.yaml。很多数据集包不会帮你写好这个文件需要你自己生成。一个最小可用的焊接缺陷 data.yaml 长这样# welding_defect.yaml path: /data/welding_dataset # 数据集根目录建议写绝对路径 train: images/train # 相对 path 的训练集图像目录 val: images/val # 相对 path 的验证集图像目录 names: 0: porosity 1: slag_inclusion 2: lack_of_fusion 3: incomplete_penetration 4: undercut 5: overlap 6: spatter 7: crack 8: sagging 9: burn_through注意这里没有写ncYOLOV5 会通过names的条目数自动推断。但如果你显式写了nc: 10必须确保和names数量一致。path字段我强烈建议用绝对路径因为不同机器的相对路径基准不同今天能跑明天换个目录就报文件找不到如果数据集在 Windows 上路径分隔符要统一用正斜杠YOLOV5 在 Windows 下用反斜杠会出莫名其妙的路径拼接问题。names的顺序是硬约束它和标签 txt 里的第一列一一对应。也就是说names[0]对应的必须是你转换脚本里class_map中值为 0 的那个类别。最稳妥的做法是先跑一个检查脚本遍历所有 labels 文件统计每个类别 ID 出现的次数再和 data.yaml 里的names对一遍。这一步能过滤掉 90% 的“训练正常但结果完全不对”的问题。4. 焊接缺陷数据集训练的5个常见问题一条一条排查再跑训练4.1 训练中断报错“class id 超出范围”编号从0还是从1开始现象训练在某个 epoch 中途直接崩掉报错信息里出现类似Label class 10 exceeds nc10的提示。原因标注工具导出时类别编号从 1 开始转换脚本没有减 1气孔对应 1、夹渣对应 2……最后一类烧穿就变成了 10超出合法范围。这个坑在人工标注的数据集里极其常见因为普通人计数自然从 1 开始而 YOLOV5 的类别 ID 从 0 开始。解决先扫描整个 labels 目录找出所有超出范围的标签文件再决定是修脚本还是修数据。from pathlib import Path max_class_id 9 # 因为 nc10合法ID范围是0~9 for label_file in Path(labels).rglob(*.txt): with open(label_file) as f: for line in f: cls_id int(line.split()[0]) if cls_id max_class_id or cls_id 0: print(f{label_file}: bad class {cls_id})这个扫描脚本的作用是把所有异常标签一次性揪出来。修复时不要手工改直接在转换脚本里把class_map[name]改成从 0 开始输出然后重新转换一遍比事后改 txt 靠谱得多。另外某些标注工具导出的类别 ID 可能乱序比如气孔给 5、裂纹给 2这时你要做的是建立“标注名 → 0~9 统一编号”的映射表而不是沿用原始 ID。4.2 验证集mAP一直为0图像里没有目标或者空标签没过滤现象训练损失在降验证集每个类别的 AP 全是 0预测结果里没有任何框。原因常见的是验证集里混入了一批没有标签的图像。YOLOV5 在验证阶段遇到空标签图像时不会报错只是把它作为负样本处理。如果这类图像占比太高模型倾向保守输出验证集上几乎不产生检测框。另一种原因是标签文件扩展名与图像不一致比如图像是.jpeg、标签是.txt加载器按.jpg去找标签一个都找不到。解决把验证集里所有图像和标签逐一对应检查生成空标签列表。python - EOF from pathlib import Path img_set {p.stem for p in Path(images/val).glob(*)} lbl_set {p.stem for p in Path(labels/val).glob(*.txt)} print(images with no label:, len(img_set - lbl_set)) print(labels with no image:, len(lbl_set - img_set)) for name in sorted(img_set - lbl_set): print(missing:, name) EOF这个检查必须在训练前做不要等训练完再回头查。我的习惯是任何数据集到手先跑两个统计脚本一个统计类别分布一个统计“有图没标签、有标签没图”的差值。全部通过后才允许进入训练流程。4.3 气孔、裂纹这种小目标漏检率高YOLOV5默认增强把缺陷越放越小现象训练完成后气孔、飞溅这类小目标的召回率明显偏低裂纹检出一段断一段。原因YOLOV5 默认开启 mosaic 增强它会把四张训练图拼成一张再随机裁剪缩放。焊接小目标本身只有十几个像素经过 mosaic 的随机缩放后可能缩到 3~5 像素模型根本学不到有效特征。除此之外随机仿射变换、HSV 扰动对焊缝这种纹理变化不大的场景影响较小但尺度缩放的影响非常直接。解决训练的第一个版本关掉 mosaic 和部分增强先用原始尺度把数据链路跑通再逐步打开增强对比。python train.py \ --data data.yaml \ --weights yolov5s.pt \ --img 1280 \ --epochs 30 \ --batch 16 \ --device 0 \ --no-mosaic--no-mosaic是 YOLOV5 训练脚本里现成的开关。焊接缺陷检测场景下我一般先用这个配置跑一版作为 baseline然后单独打开 mosaic 再跑一版对比 mAP。如果打开 mosaic 后指标反而下降说明你当前的缺陷尺度经受不住这种增强直接保持关闭或者把 mosaic 的缩放范围改小。输入分辨率从默认的 640 提到 1280 也是专门针对小目标的调整代价是显存占用明显上升batch 16 在 24G 显存上比较稳16G 显存建议 batch 8 或者输入降到 960。4.4 气孔太多、未焊透太少类别不均衡导致训练偏向现象气孔类别的 precision 和 recall 都很高未焊透、烧穿这类稀有缺陷的 AP 在 0 附近徘徊。原因焊接缺陷分布天然不均衡气孔可能占了 60% 的标注框而烧穿只有几十个样本。YOLOV5 默认按图像采样没有内置类别加权机制多数类对梯度贡献大模型倾向于把不确定目标都预测成常见类。解决最简单的做法是在训练前对稀有类做图像级过采样也就是把包含稀有类的图像在训练目录里复制几份。复制不会引入新信息但能提高这些图像被采样到的概率。更细一点的做法是调整--image-weights参数YOLOV5 支持按训练损失动态加权采样能缓解部分不均衡问题不过实际效果因数据集而异。评估时不要只看整体 mAP要看每个类别的 AP尤其关注稀有类。如果稀有类样本量确实少到不够训练优先考虑补充数据而不是指望模型结构变强。4.5 训练集验证集图像内容重叠同一条焊缝既在train又在val现象训练过程中验证 mAP 极高甚至在第一个 epoch 就超过 0.8但模型部署到现场新焊缝上完全不可用。原因数据集中一张长焊缝被切成多张图像切分时没有按来源分组导致同一根焊缝的相邻切片同时出现在训练集和验证集。模型在训练时已经见过验证图像的“邻居”等于把部分验证样本的记忆当成了泛化能力。解决切分数据时必须以焊缝编号或工件编号为单位而不是以图像为单位。如果原始数据里没有提供编号信息可以按文件名前缀分组weld_001_01.jpg、weld_001_02.jpg属于同一条焊缝那么这两张图必须同时进训练集或同时进验证集。3.1 节的脚本在数据文件命名规范的前提下把分组字段加到buckets的 key 里就能实现一个小的改动就是天壤之别。5. 用最小训练配置先验证可用性YOLOV5第一次跑通焊接缺陷数据的技巧5.1 先关增强跑一个20轮的短实验验证数据链路和损失拿到清洗好的焊接缺陷数据集别急着上完整训练配置。我的习惯是先关掉所有影响尺度的增强用短训练把数据链路验证通。这个环节的目的不是追求精度而是确认三件事图像能正常加载、标签坐标都落在有效范围、损失能稳定下降。python train.py \ --data data.yaml \ --weights yolov5s.pt \ --img 1280 \ --epochs 20 \ --batch 16 \ --workers 4 \ --device 0 \ --cache ram \ --no-mosaic--cache ram会把整个数据集缓存到内存里小数据集下训练速度快很多前提是你的内存够大。--workers 4是数据加载线程数Windows 上有时要降到 0 或 2否则会出现 DataLoader worker 崩掉的情况。跑完 20 轮后看runs/train/exp/results.csv里的train/box_loss和val/box_loss如果两者都在下降说明数据链路没问题如果 train loss 下降但 val/box_loss 横盘优先怀疑数据泄漏或标签噪声而不是急着加增强。5.2 用验证集的每个类别AP和PR曲线判断数据集能不能用短训练跑完后真正要花时间看的是每个类别的单独结果。YOLOV5 训练结束后会自动跑验证并保存confusion_matrix.png和PR_curve.png不要把目光停在整体 mAP 上要看每个类别自己的 PR 曲线。曲线右上角越饱满说明这个类别的特征越清晰。如果某个类别在 PR 曲线上几乎没有面积先查验证集里这个类别的样本数量很可能验证集压根没有它确认有样本但 AP 极低那问题出在标签一致性或者目标尺度上需要回看标注框有没有框错位置。我的收尾习惯是把验证集预测结果可视化出来用训练好的权重对 val 目录跑一遍预测然后把带框图片挑出几十张人工扫一遍。这一眼能看出“标注框是否偏低”“小目标是否漏检”“相近类别是否乱标”。说实话这个步骤看起来原始但对焊接缺陷这类标注一致性要求高的任务比任何超参数调优都有效。这个习惯帮我少走了很多弯路省下的时间远比投入的多。希望帮到你。本文还有配套的精品资源点击获取
返回列表