目标检测6类X射线底片焊缝缺陷数据集实战:从标注转换到YOLO训练
2026/9/11 23:10:43 网站建设 项目流程

简介:这是一套面向目标检测任务的中文工业质检数据集,聚焦X射线底片中的焊缝缺陷识别,适合计算机视觉初学者、算法工程师及相关科研人员用于模型训练与评估。数据采用VOC与YOLO两种统一格式,共收录2647张清晰JPEG底片,并配备完整XML与TXT标注,覆盖裂纹、未熔合、未渗透、空隙、夹渣、未融化6类常见缺陷,总计4766个矩形标注框,其中空隙与裂纹样本量较多,便于学习类别不均衡处理与缺陷定位。压缩包约39.56MB,文本类文件共2000个,包含1995个XML标注文件与5个TXT数据集划分清单,可分别用于描述目标位置和按训练、验证、测试集切分数据,结构清晰,能直接接入YOLO、Faster R-CNN等主流检测框架。当前已有355人学习使用,图片清晰且未作额外增强,标注规范准确,能帮助研究者免去从原始底片整理数据的繁琐过程,集中精力进行模型结构设计、调参与效果评估。

1. 目标检测6类X射线底片焊缝缺陷检测数据集2647张:先搞清楚这包数据能干什么

焊接质量在压力容器、管道和钢结构里怎么验?X射线底片(RT胶片)是传统的无损检测手段,但在产线上翻底片、对照标准评级,非常依赖经验。很多检测团队想上自动化,卡住的环节往往是数据:手头有几万张底片,但没标注;或者网上找来的公共数据集要么是数码照片,要么类别对不上。而“目标检测6类X射线底片焊缝缺陷检测数据集2647张.zip”这类资源,解决的是“有现成标注的目标检测数据”这一档需求。2647张不算多,但足够拿来做YOLO系模型的第一轮验证。顺着一个检测工程师拿到这个zip之后最常走的流程,这篇文章会讲清楚:先校验解压、再理清标注格式、转成YOLO训练格式、跑一轮训练,最后把结果可视化并处理细长缺陷漏检的问题。

2. 拿到zip之后:先确认6类缺陷和标注格式,再决定要不要动目录结构

下到的zip通常是一组JPEG或PNG底片图,连带一组标注文件。常见的组织方式有两种:一种把标注放在同名xml或json里,另一种直接按YOLO的txt组织。不要急着改目录,先解压、看全貌、确认图片和标注是否对得上。

2.1 解压与完整性校验:别在训练到一半时才发现eocd缺失

zip文件最常踩的坑,是下载中断后得到一个截断的zip。这类文件在解压时经常报invalid zip archive: could not find eocd,eocd是zip中央目录记录,文件尾部的目录块没下载完整就会出这个错。所以第一步不是解压,而是校验。

# 计算文件哈希,方便和发布方给出的值对比 sha256sum 目标检测6类X射线底片焊缝缺陷检测数据集2647张.zip # 列出zip内容而不解压,先看目录结构 unzip -l 目标检测6类X射线底片焊缝缺陷检测数据集2647张.zip | head -50 # 测试压缩包是否完整 unzip -t 目标检测6类X射线底片焊缝缺陷检测数据集2647张.zip

sha256sum拿到文件指纹,用于比对发布方给的哈希值;即便没有参考值,也能确认下载到的文件在本地没有变化。unzip -l只列内容不解压,适合快速核对包里是否同时有图片目录和标注目录。unzip -t会完整扫描zip中央目录和每个条目,报crc错误说明文件损坏需要重下,报“找不到eocd”基本是下载不完整,换个方式重新拉取再校验。

提示:浏览器默认下载大文件时,最终文件大小和zip列表里的总大小不一致,就不要强行解压。只差几个字节也会导致整包打不开。

2.2 六类缺陷的含义:气孔、夹渣、未焊透、未熔合、裂纹、咬边

X射线底片上的焊缝缺陷,按常见NDT评片习惯,最常被自动化检测模型区分的六类是:气孔、夹渣、未焊透、未熔合、裂纹、咬边。具体的类别英文名和顺序要以包内的classes.txt为准,但理解这6类的形态差异,比直接跑模型更重要,因为后面分析混淆矩阵时,需要知道哪两类容易互相误判。

类别底片上的典型形态检测难点
气孔圆形或椭圆形暗斑,单点或密集分布小目标,密集时难分开
夹渣不规则块状暗影,边界毛糙与气孔形态接近
未焊透沿焊缝中心延伸的连续暗线长条状,宽高比异常大
未熔合焊缝边缘的细长暗线,界限模糊对比度低,框难贴合
裂纹曲折细线,可能有分支细长、方向随机
咬边焊缝边缘凹陷造成的暗带,形状规则容易和未熔合混淆

这6类里,气孔和夹渣是“块状”缺陷,未焊透、未熔合、咬边是“线状”缺陷,裂纹则既细长又带分叉。块状缺陷适合普通anchor框,线状缺陷经常是极端宽高比,训练时不考虑这一点,召回率会明显偏低。这个判断直接决定了后面imgsz和评估指标怎么设。

2.3 目录结构确认:图片和标注的命名要对得上

这类数据集的目录,常见是这样的:

焊缝缺陷数据集/ ├── images/ │ ├── 00001.jpg │ ├── 00002.jpg │ └── ... ├── annotations/ │ ├── 00001.xml │ ├── 00002.xml │ └── ... └── classes.txt

也有可能把标注合并放在一个COCO格式的instances_train.json里。两种处理路径不同:XML(Pascal VOC)适合先转JSON再转YOLO txt;COCO json可以直接用脚本转YOLO。目录结构和上面不一致时,先人工打开两三个xml或json看字段名,不要硬套脚本。

图片和标注是否逐一对上,是这类数据的第二个常见坑。有些包xml数量比图片多或少,有些是扩展名大小写不一致(jpgJPG),还有些底片图是16位灰度tif,用PIL直接打开会报模式不支持。拿到标注后的第一件事应该是写一个对照脚本,把两边文件名集合求差集。

import os image_dir = "images" annot_dir = "annotations" imgs = {os.path.splitext(f)[0] for f in os.listdir(image_dir)} anns = {os.path.splitext(f)[0] for f in os.listdir(annot_dir) if f.endswith(".xml")} print("只有图片没有标注:", len(imgs - anns), sorted(imgs - anns)[:10]) print("只有标注没有图片:", len(anns - imgs), sorted(anns - imgs)[:10]) print("真正能用的配对:", len(imgs & anns))

这段代码用文件主名做key,分别提取图片集合和标注集合,主名相同才配对成功。差集结果直接告诉你2647张里有多少能进训练流程。如果标注按trainvaltest分成多目录,把脚本里的路径改成对应目录即可。差几十张不影响训练主体,但不查清楚,训练时会报img not found,很难定位。

3. 把标注统一转成YOLO格式:从xml和coco到txt的脚本与参数说明

3.1 为什么最终都要落成YOLO格式的txt

YOLO系训练器(YOLOv5/v8及ultralytics全家桶)默认从文本标签读框,一行一个目标,每行5个值:class_id x_center y_center width height,全部除以图片宽高做了归一化。Pascal VOC的xml是绝对像素坐标,COCO的json也是绝对像素坐标加全局类别列表,训练器不直接认这两种格式,所以至少要执行一次转换。

选YOLO txt而不是反向转COCO,是因为YOLO流程最省事:不需要写dataset继承类,也不需要覆盖__getitem__,txt放进labels/目录后配一个yaml就能启动训练。后面想换Faster R-CNN或DETR,再写个逆转换脚本把txt转回COCO json,成本也不高。

3.2 VOC xml转YOLO txt的最小脚本

import os import xml.etree.ElementTree as ET class_names = ["gas_pore", "slag", "incomplete_penetration", "lack_of_fusion", "crack", "undercut"] class2id = {name: i for i, name in enumerate(class_names)} def xml2yolo(xml_path, out_path): tree = ET.parse(xml_path) root = tree.getroot() size = root.find("size") w = float(size.find("width").text) h = float(size.find("height").text) lines = [] for obj in root.findall("object"): name = obj.find("name").text if name not in class2id: continue box = obj.find("bndbox") x1 = float(box.find("xmin").text) y1 = float(box.find("ymin").text) x2 = float(box.find("xmax").text) y2 = float(box.find("ymax").text) cx = (x1 + x2) / 2 / w cy = (y1 + y2) / 2 / h bw = (x2 - x1) / w bh = (y2 - y1) / h lines.append(f"{class2id[name]} {cx:.6f} {cy:.6f} {bw:.6f} {bh:.6f}") with open(out_path, "w") as f: f.write("\n".join(lines))

逻辑说明:ET.parse读xml,从size节点取图片宽高用于归一化;object节点里取namebndbox的四个角点坐标。中心点坐标和宽高都除以图片宽高,转成0到1的比值。类别映射写在class_names列表里,顺序一旦定下就不要改,因为YOLO类别id是按列表顺序编码的,中途改顺序会让已生成的txt语义错乱。

参数说明:class_names应与包内classes.txt一致,如果给出的顺序和这里不同,以包内为准,只改这一处。落在类别外的名字会被跳过而不是报错,这是有意为之:个别标注里可能混入suspiciousunclear这类备注性目标,跳过比让训练崩掉更合理。

3.3 COCO json转YOLO的写法差异

如果下到的是COCO格式,转换入口不是遍历xml文件,而是读annotations/instances_train.json。两个关键差异:COCO的类别id是全局字典,通常不是从0开始连续编号;图片文件名要去images列表里查。

import json with open("annotations/instances_train.json") as f: coco = json.load(f) # 从categories字段重建类别映射 cat_id2name = {c["id"]: c["name"] for c in coco["categories"]} img_id2info = {} for img in coco["images"]: img_id2info[img["id"]] = img for ann in coco["annotations"]: img_id = ann["image_id"] cat_name = cat_id2name[ann["category_id"]] x, y, bw, bh = ann["bbox"] # coco的bbox是左上角x,y和宽高

注意这里给的是骨架:COCO的bbox字段是[x, y, width, height],与VOC的xmin ymin xmax ymax不同,转换时要多做一步换算。实际跑的时候按图片分组后再批量写txt,避免逐条打开文件。还要检查ann["area"]是否为0,COCO标注里偶尔会有面积为0的空框,转换后出现width=0的非法行,训练时报错的位置又很隐蔽,建议在转换时直接过滤。

3.4 转换后必做的数据校验

转完txt不是终点,验证标签合法性优先级很高。常见问题:坐标越界(归一化后小于0或大于1)、宽高为负、空txt文件数量异常多。用一段很短的检查脚本兜底:

import os label_dir = "labels" bad = [] for f in os.listdir(label_dir): if not f.endswith(".txt"): continue with open(os.path.join(label_dir, f)) as fh: for line in fh: parts = line.split() if len(parts) != 5: bad.append((f, "字段数不对", line.strip())) continue cls = int(parts[0]) cx, cy, bw, bh = map(float, parts[1:]) if cls < 0 or cls > 5 or cx < 0 or cx > 1 or cy < 0 or cy > 1: bad.append((f, "越界", line.strip())) print("非法行数量:", len(bad)) for item in bad[:20]: print(item)

这段检查三件事:字段数必须是5;类别id必须在0到5之间(6类数据集;类别数不同自行调整上界);中心点坐标必须在0到1。宽高上下界故意没写死,因为切片检测时偶尔会生成宽高恰好等于1的整图框,给宽高留一点容忍度更实用。如果查出一大批越界,先怀疑图片宽高和xml里的宽高不一致导致的系统性偏差,而不是逐行改。

提示:转换脚本里x2 - x1算出来是0甚至负数时,通常不是标注规范问题,而是xml里xmax < xmin。这类目标会让loss忽高忽低,直接过滤掉最省心。

4. 用YOLOv8训练自己的数据集:数据yaml与关键参数设置

4.1 先了解目标检测流程里数据层的要求

YOLO训练流程是:读yaml配置 → 扫描train和val目录 → 每张图找同名txt做target → DataLoader增强 → 模型前向 → 计算loss。所以准备阶段只需要做到“目录规整、名字对齐、txt合法”,其余交给框架。2647张的规模属于中小数据集,能训出一个可用的检测器,但依赖预训练权重和合理的增强参数。

常见目录规整方式:

dataset/ ├── images/ │ ├── train/ │ └── val/ ├── labels/ │ ├── train/ │ └── val/ └── data.yaml

4.2 数据yaml的具体写法

path: /data/weld_dataset # 数据集根目录,绝对路径优先 train: images/train val: images/val # test: images/test nc: 6 names: ["gas_pore", "slag", "incomplete_penetration", "lack_of_fusion", "crack", "undercut"]

path用绝对路径最稳,因为训练脚本的工作目录不一定在数据集目录下。trainval是相对path的路径,框架会拼成/data/weld_dataset/images/trainnc必须和names长度一致,类别顺序要和转换脚本里的class_names完全一致;顺序不一致时不会报错,但框和类别名会错位,这类错误不看PR曲线很难发现。

4.3 切分train和val时的关键点

如果zip里只有一份图片加标注、没有自带划分,需要先切分。2647张对焊缝检测来说偏少,val比例取15%到20%,大约400到530张。不要因为图少就给5%,val集太小导致mAP波动大,看不出模型真实水平。切分时按“底片来源”分层,而不是纯随机。

import os import random import shutil random.seed(42) img_files = sorted(os.listdir("images")) # 按文件名前缀分组,前缀相同视为同一张底片的切片 grouped = {} for f in img_files: prefix = f.split("_")[0] # 例如 001_1.jpg 和 001_2.jpg 归同一组 grouped.setdefault(prefix, []).append(f) groups = list(grouped.values()) random.shuffle(groups) val_size = max(1, int(len(groups) * 0.2)) val_groups = groups[:val_size] train_groups = groups[val_size:] for g in train_groups: for f in g: shutil.move(os.path.join("images", f), "images/train/") shutil.move(os.path.join("labels", f.replace(".jpg", ".txt")), "labels/train/") for g in val_groups: for f in g: shutil.move(os.path.join("images", f), "images/val/") shutil.move(os.path.join("labels", f.replace(".jpg", ".txt")), "labels/val/")

很多X射线底片数据集在拍摄时,是一张大底片切成多个片段命名(001_1.jpg001_2.jpg),同一张底片的切片之间纹理和缺陷分布高度相似。纯随机切分会让同一底片的片段同时出现在train和val里,虚高mAP,后期评估失真。按前缀分组的本质是把“底片”作为最小不可分单位,保证val集在来源上与train集隔离。

4.4 训练命令与参数含义

yolo detect train \ data=/data/weld_dataset/data.yaml \ model=yolov8s.pt \ epochs=100 \ batch=16 \ imgsz=640 \ device=0 \ workers=8 \ optimizer=SGD \ lr0=0.01 \ patience=20

model=yolov8s.pt从COCO预训练权重起步,对2647张的小数据集很重要,从零训练大概率欠拟合。epochs=100配合patience=20,20个epoch没有改进就提前停。batch=16在12GB显存上下较稳妥,显存不够降8,不建议小于4,否则BN层统计量抖动。imgsz=640是训练分辨率,底片缺陷偏小时可以升960,显存和速度压力大就保持640。workers在Windows上设过高容易DataLoader报错,建议4到8。optimizer=SGD配合lr0=0.01是小数据集上比较稳的组合;换AdamW时学习率要降到0.001量级,否则前期loss震荡。

参数推荐值调整依据
epochs1002647张下100轮足够收敛,超参由patience兜底
batch1612GB显存可用16,不足降8或4
imgsz640小目标占比高时升960,显存受限保持640
device0单卡GPU训练;CPU训练速度不可接受
workers8Windows上建议4,避免DataLoader线程报错

注意:显存不够时优先级是降batch、再降imgsz、最后换更小模型。换yolov8n.pt能省显存,但裂纹这类弱纹理目标检出率下降非常明显。

4.5 容易忽略的两个训练坑

第一,原始底片如果是大分辨率灰度图(比如4000×3000),直接缩到640训练会丢失裂纹的像素特征。常见做法是滑动窗口切片,切成512或640的子图再训练。切片会改变标注坐标,要在标注对齐之后再切,否则框错位。第二,6类缺陷样本量几乎不可能是均匀的,气孔往往最多,裂纹最少。训练完要逐类看mAP,别只看整体值。类别不平衡在数据层面的补救是过采样少数类图片,比改loss更直观可控。

5. 结果验证与细长缺陷召回优化:从PR曲线到切片检测

训练结束后,runs/detect/train/下会生成混淆矩阵、PR曲线、F1曲线和一批验证集预测图。我按三层顺序排查。

第一层看整体指标的置信度。优先看每个类别的results.csv里的mAP50-95(B),那是COCO风格的综合精度。对焊缝底片这种大尺寸目标,mAP50比mAP50-95更实用,因为标注框边界本身有人为误差,框贴合度的微小差别不该被过度计罚。mAP50(B)到0.8以上说明类别基本分得开;mAP50-95(B)明显偏低,多半是框回归不准而不是分类错。

第二层看混淆矩阵,重点观察气孔和夹渣、未焊透和未熔合这两对。同是暗色块状或线状缺陷,标注员在底片上本来就存在误标可能,模型分错不一定全是模型问题。回去翻原图,确认这两类样本形态是否真的可分。如果原图上确实长得一样,就接受这组混淆,而不是盲目堆模型复杂度。

第三层是挖漏检。焊缝缺陷里最难的是裂纹:细长、对比度低、方向不固定。对这类目标,提升召回最有效的手段不是换backbone,而是切片检测。推理时把输入图切成重叠块分别推理再合并,常见做法是切512×512、重叠128像素,最后用NMS合并重叠框。这个过程yolo predict不好做细粒度控制,通常要自己写推理循环。

最后落一个可复现的小技巧:可视化细长缺陷时不要只看预测图,把GT框和预测框画在同一张图,设line_width=1,否则裂纹这种细长目标会被粗框完全盖住。判断细长缺陷框的质量,看框和缺陷中心线的平均夹角比看IoU更直接;框宽高比和GT差异大,说明回归头没学到长条目标形态,优先检查训练时是否用了过大的mosaic增强。长条目标在mosaic拼接时会被切得七零八落,干扰学习,关掉mosaic=0重训一轮,通常能让细长缺陷的框质量上一个台阶。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询