ARTICLE DETAIL

资讯详情

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

X射线底片焊缝缺陷检测:从数据体检到YOLOv8训练全流程

X射线底片焊缝缺陷检测:从数据体检到YOLOv8训练全流程 简介面向目标检测与工业质检方向的研究者和算法工程师这份数据集聚焦X射线底片焊缝缺陷检测场景弥补高质量标注数据不足。压缩包约39.56MB含约2000个文件主要包含JPEG图片、xml与txt三种类型分别对应原始图像、VOC格式标注和YOLO格式标注另附train/val/test划分文件与说明文档可便捷地接入常见检测框架。数据集覆盖6类焊缝缺陷——裂纹、未熔合、未渗透、空隙、夹渣、未融化共4766个矩形标注框图片清晰、未经增强标注框与类别信息完整便于直接进行模型训练与评估。已有355人学习下载。资源包聚焦数据本身不做模型精度保证适合用作焊缝缺陷检测算法研究、验证集构建及对比实验的数据基础。1. X射线底片焊缝缺陷检测数据集2647张图能做什么、够不够用焊接质检老师傅靠肉眼在X射线底片上逐张评片要看气孔、夹渣、未熔合、未焊透、裂纹、咬边六类缺陷一天几百张看下来眼睛发花标准也难统一。这个目标检测数据集把2647张X射线底片图像连同缺陷标注打包在zip里正好用来训练自动评片模型把老师傅的经验变成可批量复用的算法。2647张的量级在目标检测里不算大但对X射线底片这类获取成本高的工业数据足够验证YOLO类模型的检测路线也能积累一套针对灰度底片的预处理和调参方法。适合刚上手目标检测、想拿真实工业数据练手的同学也适合做无损检测自动评定的工程人员。2. 拿到zip先做数据体检类别分布、标注格式与底片特点决定后面一切很多人拿到zip直接解压开就开始训练训到一半发现类别名对不上、框位置错位甚至同一张图既在训练集又在验证集。X射线底片数据集尤其要小心因为同一焊缝往往被切成多张底片各张底片的成像条件不同标注习惯也未必统一。所以我拿到后第一件事不是写训练脚本而是先把数据和标注体检一遍。2.1 六类焊缝缺陷先认全标签名与X射线底片上的形态先对齐领域常识。焊缝X射线底片评片按无损检测的常见习惯缺陷类型分六种气孔porosity底片上呈圆形或椭圆形暗斑中心黑度大、边缘清晰有时单个出现有时密集成群。夹渣slag inclusion形状不规则的暗色块边界不如气孔圆润可以单独出现也可以连成链状。未熔合lack of fusion母材与焊缝金属未熔合底片上多为直线或长条状暗线常沿坡口边缘分布。未焊透incomplete penetration焊缝根部未焊透呈连续或断续的直线暗带位置基本固定在焊缝中心根部。裂纹crack细长曲折的暗线端部尖锐是六类里最危险的缺陷也最容易漏检。咬边undercut底片上沿焊缝边缘出现的条状暗区和背景有明确分界。打开数据集后先找到类别名配置文件无论是data.yaml里的names还是标注工具的classes.txt都对着这六类过一遍名字映射。常见命名可能是英文全称也可能是porosity、slag这类缩写或中文拼音。如果names里冒出一个other或defect多半是标注时把不好归类的框收进了杂项训练前要想清楚这类要不要保留。保留会让模型把这些框当成独立类别来学不保留则要在转换脚本里跳过。2.2 解压与结构判断先看目录树再判断标注是VOC还是COCO还是YOLO拿到zip先别急着在图形界面里双击解压直接在终端里解出来看结构unzip 目标检测6类X射线底片焊缝缺陷检测数据集2647张.zip -d weld_defect cd weld_defect find . -maxdepth 2 -type d这样做能看到顶层目录布局。如果出现JPEGImages和Annotations两个目录说明是VOC格式标注是xml出现annotations目录且里面是单个json文件说明是COCO格式出现images和labels两个平级目录且labels下每个图像同名txt说明已经直接是YOLO格式。无论用LabelImg存VOC、Labelme转COCO还是其他工具导出YOLO格式目录结构能对上后续转换思路都一样。图像是jpg还是png还是tif也很关键。TIF格式的底片图像往往尺寸很大动辄几千乘几千像素直接整图训练后面一定会遇到显存问题需要切块训练。再用文件数量对账防止解压漏文件find . -type f \( -name *.jpg -o -name *.png -o -name *.tif \) | wc -l find . -name *.xml | wc -l图像数量应该接近2647标注文件数量应该等于图像数量。对不上时先查是不是有的底片图没有对应标注这在工业数据集里常见——某些底片确实没有缺陷标注时直接跳过了。但目标检测训练要求每张图都有标注文件要么补空标注要么把这类图从训练集剔除不要硬留。2.3 用Python做数据体检类别频次、框面积分布和灰度统计格式和数量确认后写个小脚本做三类统计。这决定了后续要不要加权、要不要切块、要不要做灰度归一化import os import numpy as np import xml.etree.ElementTree as ET # VOC格式示例COCO/YOLO换相应解析器统计口径相同 xml_dir Annotations img_dir JPEGImages cls_count {} box_areas [] img_gray_mean [] for xml_name in os.listdir(xml_dir): if not xml_name.endswith(.xml): continue tree ET.parse(os.path.join(xml_dir, xml_name)) root tree.getroot() for obj in root.iter(object): name obj.findtext(name).strip() bnd obj.find(bndbox) x1 float(bnd.findtext(xmin)); y1 float(bnd.findtext(ymin)) x2 float(bnd.findtext(xmax)); y2 float(bnd.findtext(ymax)) cls_count[name] cls_count.get(name, 0) 1 box_areas.append((x2 - x1) * (y2 - y1)) # 顺便统计灰度均值抽样前200张即可 img_name root.findtext(filename) img_path os.path.join(img_dir, img_name) if img_name and os.path.exists(img_path) and len(img_gray_mean) 200: import cv2 img cv2.imread(img_path, cv2.IMREAD_GRAYSCALE) img_gray_mean.append(img.mean()) print(类别框数量分布:, cls_count) print(框面积百分位 p10/p50/p90:, np.percentile(box_areas, [10, 50, 90]).astype(int)) print(抽样图像灰度均值:, np.mean(img_gray_mean).round(1))逻辑说明脚本遍历VOC xml逐框累计每个类别的框数量统计框面积分位数并抽样200张图像算灰度均值。三个输出分别对应三个决策点。参数说明类别分布如果出现极端不平衡比如气孔三千多框而裂纹只有几十框训练时必须设置类别权重或对少样本做复制增强框面积p90如果仍然很小说明整个数据集偏小目标训练时的imgsz要加大或采用切块训练灰度均值如果波动大预处理时要做按图的标准化而不是固定除以255。3. 把标注转成YOLO格式并跑通训练从XML/JSON到yolov8落地体检完就可以动手了。常见做法是以YOLOv8为主线训练它在中小规模数据集上收敛快预训练权重好找一条命令行就能完成训练和评估。下面按数据划分、格式转换、训练参数三步走。3.1 按底片编号划分数据防止同一焊缝的切片串到验证集X射线底片数据集的隐藏陷阱在数据划分。一张环形焊缝可能拍多条底片每条底片的图像之间存在连续过渡如果把图像随机分到train和val模型在验证集上看到的可能是训练集里同一焊缝的相邻段表现好的原因只是记住了上下文下现场就露馅。所以划分要按底片编号分组。import os, random from collections import defaultdict img_files [f for f in os.listdir(images) if f.lower().endswith((.jpg, .png, .tif))] # 假设文件名像 weld001_p1.jpgweld001是底片编号 groups defaultdict(list) for f in img_files: prefix f.split(_)[0] groups[prefix].append(f) keys list(groups.keys()) random.seed(42) random.shuffle(keys) n len(keys) train_keys set(keys[:int(n*0.8)]) val_keys set(keys[int(n*0.8):int(n*0.9)]) test_keys set(keys[int(n*0.9):]) with open(train.txt, w) as f: for k in train_keys: for img in groups[k]: f.write(os.path.abspath(os.path.join(images, img)) \n) # val.txt / test.txt 同理逻辑说明分组键是底片编号而不是单张图确保同一底片的所有切片进同一个集合。随机种子固定是方便别人复现你的划分否则每次运行结果都不同训练对比就没有意义。参数说明8:1:1是目标检测常见比例类别极不平衡或底片数量少时可以改成7:2:1把验证集留足。划分完后用wc -l train.txt val.txt test.txt核对总数应该和图像总量一致多一行少一行都要回头查。3.2 编写XML转YOLO的转换脚本坐标归一化与类别映射YOLO训练需要的是每个标注框归一化的中心点坐标和宽高VOC的xml里存的是像素角点格式要对齐import os import xml.etree.ElementTree as ET CLASSES [porosity, slag, lack_of_fusion, incomplete_penetration, crack, undercut] # 这个顺序必须和data.yaml里names的顺序完全一致 def voc_to_yolo(xml_path, out_dir, img_w, img_h): tree ET.parse(xml_path) root tree.getroot() lines [] for obj in root.iter(object): name obj.findtext(name).strip() if name not in CLASSES: print(f跳过未知类别: {name} in {xml_path}) continue cls_id CLASSES.index(name) bnd obj.find(bndbox) x1 float(bnd.findtext(xmin)); y1 float(bnd.findtext(ymin)) x2 float(bnd.findtext(xmax)); y2 float(bnd.findtext(ymax)) x1 max(0, x1); y1 max(0, y1) x2 min(img_w, x2); y2 min(img_h, y2) if x2 - x1 1 or y2 - y1 1: continue cx (x1 x2) / 2 / img_w cy (y1 y2) / 2 / img_h w (x2 - x1) / img_w h (y2 - y1) / img_h lines.append(f{cls_id} {cx:.6f} {cy:.6f} {w:.6f} {h:.6f}) txt_path os.path.join(out_dir, os.path.splitext(os.path.basename(xml_path))[0] .txt) with open(txt_path, w) as f: f.write(\n.join(lines))逻辑说明把类名先映射成索引再将左上角右下角坐标转换为归一化的中心点和宽高。两个保护细节是关键坐标裁剪处理的是底片数字化时常见的标注越界过滤小框处理的是误标注这两类脏数据不清理训练时会出现难解释的NaN或loss突刺。参数说明如果数据集是COCO格式改法也简单——用pycocotools把ann中的bbox读出来bbox是[x, y, w, h]同样除图像宽高归一化后写txt类别映射改为从JSON的categories数组里取顺序其余逻辑不变。若是原本就带YOLO txt反而要检查txt是否干净尤其是坐标值有没有跑到0到1区间之外。3.3 用YOLOv8跑通训练预训练权重和几个关键参数转换完成后把文件整理成YOLO期望的目录结构weld_defect/ images/train/ images/val/ images/test/ labels/train/ labels/val/ labels/test/再写data.yaml。path指向数据集根目录train和val填相对路径names按CLASSES逐字对应path: /home/user/weld_defect train: images/train val: images/val nc: 6 names: [porosity, slag, lack_of_fusion, incomplete_penetration, crack, undercut]训练命令yolo detect train \ modelyolov8n.pt \ dataweld_defect.yaml \ imgsz1280 \ epochs100 \ batch16 \ patience20 \ device0 \ projectrun_weld \ namebaseline参数说明逐个说modelyolov8n.pt指定COCO预训练权重做迁移学习。焊缝缺陷和COCO里物体形态差异大但预训练骨干网络提取的边缘、纹理能力对灰度底片同样有效收敛速度远快于随机初始化。imgsz1280必须上调。X射线底片分辨率高且缺陷是小目标用默认640会把很多小缺陷压到几个像素直接漏检。显存不够时先减batch不动imgsz。batch16单卡24G跑yolov8n可以接受显存小就减到8或4欠拟合概率远低于imgsz不够导致的漏检。patience20连续20个epoch验证集mAP不升就早停2647张图通常40到60轮就收敛不需要把100轮跑满。训练结束后看run_weld/baseline/weights下生成的best.pt和last.ptbest按验证集mAP挑选后面推理默认用best.pt。4. 训练后怎么评估mAP、混淆矩阵与缺陷漏检回放训练完不要只看loss曲线收工。X射线底片缺陷检测的目标是漏检率精度数字好看不代表现场可用所以评估要多走三步。4.1 先查数据泄漏再谈指标哈希查重加results曲线评估之前先做一次终检。虽然划分时按底片编号做了分组但有些数据集里会出现重复图像或切块重叠用md5把三套集合比对一次最稳import hashlib, os from collections import defaultdict def md5(path): h hashlib.md5() with open(path, rb) as f: for chunk in iter(lambda: f.read(4096), b): h.update(chunk) return h.hexdigest() hash_map defaultdict(list) for root, _, files in os.walk(weld_defect): for fn in files: if fn.lower().endswith((.jpg, .png, .tif)): p os.path.join(root, fn) hash_map[md5(p)].append(p) dups {k: v for k, v in hash_map.items() if len(v) 1} for k, v in dups.items(): print(重复图像:, v)逻辑说明md5相同的文件如果出现在不同集合说明划分有泄漏后面所有指标都不可信。输出为空再往下读指标。指标建议读yolo训练目录里的results.csv而不是只看控制台打印的最后一屏。用pandas画曲线更清楚import pandas as pd import matplotlib.pyplot as plt df pd.read_csv(run_weld/baseline/results.csv) plt.plot(df[epoch], df[metrics/mAP50(B)], labelmAP50) plt.plot(df[epoch], df[metrics/mAP50-95(B)], labelmAP50-95) plt.plot(df[epoch], df[metrics/recall(B)], labelrecall) plt.plot(df[epoch], df[metrics/precision(B)], labelprecision) plt.legend() plt.savefig(weld_curve.png)X射线底片缺陷数据正常的参考区间mAP50在0.6到0.85mAP50-95在0.3到0.5。如果mAP50有0.8而mAP50-95只有0.2说明框边界预测不稳能套住缺陷但套不准。这种情况先查标注框是不是画得过松再考虑加大imgsz。4.2 混淆矩阵比整体mAP重要相似形态缺陷间的误判要单独看焊缝缺陷里裂纹和未熔合在底片上都是线性暗区夹渣和气孔都是圆形暗斑模型很容易在相似形态之间犯错。训练完打开val批次的混淆矩阵图重点看对角线外的高值区域。如果crack被大量识别成lack_of_fusion先别急着改模型回到数据集里对比这两类缺陷的底片裁图确认标注阶段对边界形态的判断是否统一。工业上这两类缺陷的返修策略完全不同混淆代价很高所以要看每类各自的precision和recall不能只看总体mAP。这里我通常把每个类别单独输出一列指标方法是对每类构造一个只含正反例的评估子集用YOLO的val接口按类别统计。如果裂纹类recall低于0.9这轮训练不能算通过需要回到数据增强或阈值策略上继续调。4.3 用预测回放定位漏检置信度阈值和切片推理要配套指标之外还有一个直观手段把验证集和测试集的预测画出来按置信度从低到高逐个翻图。重点看置信度0.2到0.5之间被滤掉的框里面有多少是真缺陷这是漏检的主要来源。推理时建议先降阈值试一轮yolo detect predict \ modelrun_weld/baseline/weights/best.pt \ sourcetest_images \ conf0.15 \ imgsz1280 \ saveTrue参数说明conf从默认0.25降到0.15代价是假阳性变多。在评片场景多标一个气孔让师傅复核的成本远低于漏掉一个裂纹的代价所以阈值可以向recall倾斜。调整后要对测试集重算一遍每类recall目标参考裂纹和未熔合单类recall到0.95以上气孔可以稍微宽松用后续人工复核兜底。5. 常见问题与避坑X射线底片缺陷检测训练的五个大坑同一份2647张的数据不同人跑出来mAP能差20个点差别大多不在模型结构而在数据处理和参数取舍。下面五条是我在这个方向上反复遇到的坑按现象、原因、解决的顺序说。5.1 一放大imgsz就爆显存不是显存不够是整图输入的策略不对现象imgsz从640调到1280后训练直接OOMbatch减到2还在报错。原因X射线底片数字化后多为大尺寸灰度图整图resize到1280平方GPU显存消耗和CPU预处理压力同时上涨模型根本装不下。解决不要整图硬塞。常见做法是先把底片按滑窗切成若干块每块约800x800到1280x1280再以块为单位训练。切块要有重叠步长取窗宽一半即可避免一个缺陷正好被切成两半。切出来的块在划分时保持同一底片编号进同一集合否则又会引入数据泄漏。5.2 训练loss正常但验证集mAP异常低names顺序张冠李戴现象loss正常下降验证集mAP却一直贴着地走画出来的预测框类别全乱。原因data.yaml里的names顺序和转换脚本里的CLASSES不一致。转换脚本里气孔是索引0yaml里却把裂纹排在了第0位模型学习的标签映射是错位的指标自然崩。解决把CLASSES定义成唯一来源data.yaml由它生成不要手敲两遍。训练前抽查几个txt标注的第一列用head -3 labels/val/某图.txt对照yaml names数组逐行数第一行第一列是0就对应names[0]对不上马上改。5.3 气孔类recall低得离谱小目标缺陷靠整图训练必漏现象总体mAP还不错单独看气孔recall只有0.4大量几毫米的针孔都没框出来。原因气孔在3000x3000的底片上可能只占20x20像素直接resize到1280后只剩10个像素不到特征在缩放中丢光。这不是模型不努力是预处理把目标抹掉了。解决切块训练是正路推理端也要配套。推理时用原图分辨率滑窗把所有切片上的预测框映射回整图坐标再做一次跨窗NMS合并。跨窗NMS的IOU阈值取0.3而不是默认0.5因为相邻切片会重复预测同一个缺陷IOU偏高阈值取太大反而会把正确框合并删除。5.4 底片噪声被识别成缺陷灰度不均与胶片颗粒的干扰现象验证集误检集中在底片暗角、灰度跳变区框住的内容看起来像一片糊而不是明确缺陷。原因X射线底片有胶片粒度噪声和曝光不均模型把噪声纹理学成了特征灰度底片上这种误检特别隐蔽。解决在预处理阶段做灰度归一化和背景扣除。常见做法是先把整图灰度按均值方差标准化再用形态学顶帽滤掉大范围亮度不均让局部暗区缺陷凸显出来import cv2 img cv2.imread(weld.tif, cv2.IMREAD_GRAYSCALE) kernel cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (31, 31)) tophat cv2.morphologyEx(img, cv2.MORPH_TOPHAT, kernel) tophat cv2.normalize(tophat, None, 0, 255, cv2.NORM_MINMAX)逻辑说明顶帽操作是原图减去形态学开运算的结果能把大范围的亮度和背景变化滤掉只保留局部小尺度暗区也就是缺陷候选区域。核大小31在几千像素的底片上是经验起点底片分辨率更高时可以适当加大。预处理后重新训练误检会显著下降。5.5 换一个底片厂的数据mAP直接崩不同射线机灰度分布差异太大现象训练集mAP50有0.85换到另一台射线机或另一批底片直接掉到0.3。原因不同射线机、不同胶片冲洗条件下底片的灰度直方图差异非常大。模型学到的灰度质感只适配训练来源换设备后输入分布变了检测能力随之塌陷这也是所谓“训练集效果好、现场翻车”最常见的来源。解决在预处理里加入随机gamma、对比度、灰度偏移作为数据增强增加模型对灰度波动的耐受。更实用的做法是给每个新底片来源保留20张左右底片部署前做一次小样本微调通常几十轮就能拉回来比重新收集标注数据便宜得多。6. 让模型真正参与评片整图切片推理与部署前灰度检查训练完成不等于可以交付。现场输入往往是一整张数字化底片不是训练时的切片所以推理端要和训练端保持一致滑窗推理、坐标映射回原图、跨窗框合并。我习惯把它固化成脚本同时把前面的验证项整理成清单避免换个环境复现又回到“离线mAP高、上线就翻车”的状态。import cv2 import numpy as np from ultralytics import YOLO model YOLO(run_weld/baseline/weights/best.pt) img cv2.imread(field_imgs/weld_03.tif, cv2.IMREAD_GRAYSCALE) h, w img.shape[:2] sw sh 1024 # 滑窗大小 stride 512 # 步长取窗宽一半缺陷跨窗也能被完整覆盖 boxes [] for y in range(0, h - sh 1, stride): for x in range(0, w - sw 1, stride): patch img[y:ysh, x:xsw] patch cv2.cvtColor(patch, cv2.COLOR_GRAY2BGR) res model.predict(patch, conf0.15, imgsz1024, verboseFalse)[0] for box in res.boxes: x1, y1, x2, y2 box.xyxy[0].tolist() boxes.append([x x1, y y1, x x2, y y2, float(box.conf), int(box.cls)]) boxes np.array(boxes) keep cv2.dnn.NMSBoxes( boxes[:, :4].tolist(), [float(b) for b in boxes[:, 4]], score_threshold0.0, nms_threshold0.3 )这段代码展开来说外层循环负责在整张底片上滑窗内层把每个切片喂给模型返回的框坐标加上切片原点偏移后回到整图坐标系最后用NMS合并跨窗产生的重复框阈值0.3配合0.5步长能保留同一缺陷在不同切片里的最高置信度框。部署前还有一个常被我忽略的灰度检查推理前统计整图灰度均值如果和训练集均值相差超过两倍标准差直接输出“低置信度请人工复核”而不是把一堆不可信框交给现场。这个习惯是我拿底片做误检回放时养成的——有一批新底片灰度整体偏暗模型把底片边缘全部识别成夹渣加了灰度检查后才挡住。做缺陷检测模型置信度只代表“这里像缺陷”不代表现场一定可信守住这个边界才能在质检场景里长期用下去。希望帮到你。本文还有配套的精品资源点击获取
返回列表