简介:一份面向钢材焊接质量检测的YOLOv5深度学习资源,适合有一定目标检测基础的开发者、质检算法工程师或相关专业学生。资源已包含训练完成的焊缝质量检测模型,可直接用于识别焊缝好坏,同时附带有PR曲线、loss曲线等训练过程评估结果,便于理解模型性能。数据集部分提供约1134张JPG格式标注图片,并同时包含对应的XML与TXT两种标签格式,方便在不同检测框架中切换使用。压缩包内共2000个文件,以JPG图像、XML与TXT标注文件为主要组成,整体大小约222.96MB,结构明确便于下载后快速进入实验。此外,资源还搭载了基于PyQt的图形界面,支持对图片、视频以及摄像头实时画面进行检测,操作直观,能够满足从模型验证到演示落地的常见需求。目前已有985人学习,适合用来快速搭建焊接质量检测原型系统。
1. 焊缝质量检测为什么选择 YOLOv5 而非传统视觉
在钢结构和管道预制车间,焊缝质量检测长期依赖人工目检或超声波抽检,效率低且容易漏检。用深度学习目标检测来做焊缝质量好坏判别,不是把图片丢进网络那么简单,难点在于工业现场的光线变化、焊缝形状差异,以及缺陷样本本身稀疏。YOLOv5 在这类任务里是投入产出比最高的选择:单阶段检测速度快,能部署到普通显卡,社区资料多,训练自己的数据集也很成熟。这里拆解的这份「YOLOv5焊缝质量好坏检测」资源,包含训练好的权重、已用 LabelImg 标注好的钢材缺陷检测数据集(图片为 JPG,xml 与 txt 双格式标注分别保存),以及 PR 曲线、loss 曲线等训练评估产物,还有一个能检测图片、视频和调用摄像头的 PyQt 界面。对正在做检测类毕设或工业视觉课题的开发者来说,这套东西可以直接拿来跑通流程,再按自己的数据微调。
2. 数据集构成与 xml/txt 双标注格式解析
2.1 目录规划与文件对应关系
拿到资源后,先不要急着训练,把数据集结构理清楚。资源里的图片全部是 JPG,标注文件分两套:一套是 LabelImg 导出的 Pascal VOC 格式 xml,另一套是 YOLO 训练需要的 txt。资源里给出的 dataset_00294.txt、dataset_0079.txt、dataset_0017.txt 这类文件,就是 YOLO 格式的标签文件,每个 txt 与同名 JPG 一一对应。常见做法是把它们按训练集和验证集分开,目录结构如下:
dataset/ ├── images/ │ ├── train/ │ │ └── dataset_0017.jpg │ └── val/ ├── xml_annotations/ # LabelImg 导出的 Pascal VOC 格式 │ ├── dataset_0017.xml │ └── ... ├── yolo_labels/ # YOLO 格式 txt,与 images 一一对应 │ ├── dataset_0017.txt │ └── ... └── weld.yaml # YOLOv5 训练所需的数据配置这套目录并不是强制要求,但 YOLOv5 的 train.py 默认就是按 images 和 labels 两个根目录去找数据的,labels 的路径会把 images 替换为 labels,后缀 .jpg 替换为 .txt。如果你把 xml 直接放在 labels 目录里,训练脚本会读不到数据。所以 xml 单独存一个文件夹做存档,yolo_labels 作为训练用的 labels,这是最不容易出错的方案。检查对应关系时,可以用下面这个命令快速找出没有标签的图片。
for img in dataset/images/train/*.jpg; do base=$(basename "$img" .jpg) [ -f dataset/yolo_labels/$base.txt ] || echo "missing label: $img" done这个循环遍历训练图片,basename 去掉扩展名后去 yolo_labels 里找同名 txt,找不到就把图片路径打印出来。训练前跑一遍这个命令,可以避免因为漏标注导致训练时图片被跳过,或者更隐蔽的“图片有但标签为空”导致 loss 异常。
2.2 XML 与 TXT 标签的坐标换算
LabelImg 默认可以输出两种格式,VOC 格式把目标位置存成绝对像素坐标,YOLO 格式存成相对图片宽高的归一化坐标。一份典型的 xml 长这样:
<annotation> <folder>images</folder> <filename>dataset_0017.jpg</filename> <size> <width>1920</width> <height>1080</height> <depth>3</depth> </size> <object> <name>defect</name> <bndbox> <xmin>312</xmin> <ymin>245</ymin> <xmax>867</xmax> <ymax>342</ymax> </bndbox> </object> </annotation>这里<name>是类别名,按你标注时的实际类别替换,缺陷名称不要带空格和中文,YOLO 对名称里的字符比较敏感。xml 里记录的是左上角和右下角的像素坐标,而 YOLO 的 txt 记录的是归一化后的中心点坐标和宽高,直接这样写:
0 0.3073 0.2718 0.2891 0.0898第一个数字是类别 id,从 0 开始;后面四个数是 x_center、y_center、width、height,都除以了图片的宽高。如果类别不止一个,每个目标单独一行。训练时 YOLOv5 会按行解析,发现某一行不足五个数或者坐标超出 [0,1] 就会报警。
如果需要自己把 xml 转成 txt,我一般会在整理数据集时用一段小脚本,避免手工换算出错。
import xml.etree.ElementTree as ET def xml_to_yolo(xml_path, class_names, out_path): tree = ET.parse(xml_path) root = tree.getroot() w = float(root.find('size/width').text) h = float(root.find('size/height').text) lines = [] for obj in root.findall('object'): name = obj.find('name').text if name not in class_names: continue # 跳过不在类别表中的目标 cid = class_names.index(name) box = obj.find('bndbox') xmin = float(box.find('xmin').text) ymin = float(box.find('ymin').text) xmax = float(box.find('xmax').text) ymax = float(box.find('ymax').text) x_center = (xmin + xmax) / 2.0 / w y_center = (ymin + ymax) / 2.0 / h bw = (xmax - xmin) / w bh = (ymax - ymin) / h lines.append(f"{cid} {x_center:.6f} {y_center:.6f} {bw:.6f} {bh:.6f}") with open(out_path, 'w', encoding='utf-8') as f: f.write('\n'.join(lines))函数里 class_names 是类别列表,顺序必须与后面训练用的 weld.yaml 中 names 的顺序完全一致。xml_path 是输入文件,out_path 是输出 txt 的路径。注意读取宽高时我用了 float,因为有的标注工具会写出带小数点的尺寸。如果某些 xml 坐标解析出来是负数或者中心坐标大于 1,说明标注越界了,这种目标直接跳过比强行归一化更安全。
2.3 数据集质量检查与训练/验证划分
训练目标检测模型之前,数据质量检查比调参更重要。最常见的坑是类别严重不均衡:比如“合格焊缝”样本占 90%,“缺陷焊缝”只占 10%,模型很容易把缺陷漏掉。我一般会先用一段脚本统计每个类别的框数量分布。
import glob counts = {} for txt_path in glob.glob('dataset/yolo_labels/*.txt'): with open(txt_path) as f: for line in f: cid = line.split()[0] counts[cid] = counts.get(cid, 0) + 1 print(counts)这段代码遍历所有 YOLO txt,把每一行的第一个字段即类别 id 统计出来。如果发现某个类别数量特别少,优先考虑做数据增广,或者从视频帧里抽更多负样本,而不是一上来就调学习率。焊缝检测里很多缺陷在灰度上差别很小,增广时不要用太强的颜色扰动,容易把真实缺陷的颜色特征洗掉。
数据准备好后做划分,训练集和验证集一般按 8:2 或 9:1。注意划分时要保证同一张图不会同时出现在训练和验证里,尤其是连续拍摄的焊缝图片有很强的相似性,最好按文件夹或视频段划分,而不是纯随机打散。简单的划分可以这样:
mkdir -p dataset/images/train dataset/images/val dataset/labels/train dataset/labels/val python - <<'PY' import glob, random, shutil, os imgs = glob.glob('dataset/images/*.jpg') # 实际按你的存放路径调整 random.seed(42) random.shuffle(imgs) for i, img in enumerate(imgs): txt = img.replace('.jpg', '.txt').replace('images', 'yolo_labels') if i < int(len(imgs) * 0.8): shutil.move(img, 'dataset/images/train/') shutil.move(txt, 'dataset/labels/train/') else: shutil.move(img, 'dataset/images/val/') shutil.move(txt, 'dataset/labels/val/') PY这段脚本先用 random.shuffle 打乱图片列表,前 80% 放进 train,后 20% 放进 val。replace 操作把图片路径里的 images 换成 yolo_labels,从而找到同名 txt。注意如果你的目录名不完全对应,需要按实际情况修改替换规则。这里 seed 固定为 42 保证可复现,方便后面和训练日志对照。
3. 训练自己的焊缝数据集:参数配置与曲线判读
3.1 数据配置 yaml 与超参数选择
YOLOv5 训练的第一步是准备一个数据配置文件 weld.yaml。这个文件告诉训练脚本图片路径、验证集路径、类别数和类别名,内容非常简单。
train: dataset/images/train val: dataset/images/val nc: 2 names: ['weld_ok', 'weld_defect']上面例子里的 names 用 weld_ok 和 weld_defect 占位,实际使用时要替换成你标注时的类别名,且索引顺序必须和 txt 标注里的类别 id 一一对应。只要这里写错一个顺序,模型就会把类别标签学反,而且损失曲线看起来完全正常,这就是很多人在验证集上 mAP 很高但实际部署却发现错判的原因。
训练命令一般从 yolov5 根目录执行,最基本的调用是:
python train.py --weights yolov5s.pt --data weld.yaml --hyp hyp.scratch-low.yaml --epochs 100 --batch-size 16 --img 640这里把常用参数整理成一张表。
| 参数 | 作用 | 建议取值 |
|---|---|---|
| --weights | 预训练权重路径,填空字符串表示从头训练 | yolov5s.pt 或最接近工况的权重 |
| --data | 数据集 yaml 文件 | weld.yaml |
| --hyp | 超参数文件 | hyp.scratch-low.yaml 或 hyp.scratch.yaml |
| --epochs | 训练轮数 | 100~300,样本少时可加大 |
| --batch-size | 每次迭代样本数,受显存限制 | 8、16、32 |
| --img | 输入图片尺寸,训练时会 resize | 640 |
| --patience | 验证指标不提升多少轮就早停 | 50 |
| --device | GPU id 或 CPU | 0 或 cpu |
焊缝检测这类目标尺寸差异不大的任务,img 从 640 开始就足够。如果焊缝在画面里非常小,比如整幅图里焊缝宽度只有几十像素,考虑把 img 提到 1280 并配合更大感受野,但显存消耗会成倍上涨。超参数文件里主要关注 lr0、lrf、momentum 和 weight_decay,正常场景不要动 momentum,只用 hyp.scratch-low 就能稳定收敛。需要说明的是,如果你的 GPU 只有 6GB 显存,batch-size 16 可能爆显存,换成 8 同时把 img 降到 480 通常也能跑,只是精度会略降。
3.2 训练产物:权重、PR 曲线、loss 曲线怎么读
训练结束后,所有产出都在 runs/train/exp 下,如果多次训练会有 exp2、exp3 等。关键产物如下:
runs/train/exp/ ├── weights/ │ ├── best.pt │ └── last.pt ├── PR_curve.png ├── P_curve.png ├── R_curve.png ├── F1_curve.png ├── confusion_matrix.png └── results.pngbest.pt 是验证集上综合指标最好的权重,部署时优先用它;last.pt 是训练结束时那一轮的权重,如果后期过拟合严重,last.pt 往往比 best.pt 更差。PR_curve.png 是 Precision-Recall 曲线,它不需要指定置信度阈值,而是把所有阈值下的精度和召回率画成一条曲线。曲线越靠近右上角,说明模型在保持高精度的同时还能把所有目标找出来,曲线下的面积就是 AP。多类别时会把每个类别的曲线画出来,所有类别 AP 的平均值就是 mAP。
results.png 是 YOLOv5 自动生成的训练过程曲线,里面包含 box_loss、obj_loss、cls_loss 和 mAP 的变化趋势。读这条曲线时,不要只盯着训练集上的 loss,训练 loss 降得再漂亮,验证集上不降就是白训。看曲线的顺序应该是:先看 val/obj_loss 是否下降,再看 mAP_0.5 是否上升,最后才看训练 loss 的收敛情况。obj_loss 是目标置信度损失,它代表模型对“这里有没有目标”的判断误差;box_loss 是边界框回归损失,管框得准不准;cls_loss 是分类损失,管类别分得对不对。
3.3 从 loss 曲线判断是否过拟合
过拟合在数据量不大的焊缝检测里非常常见,典型表现是训练 loss 持续下降,验证 loss 在某个 epoch 掉头向上,同时 mAP 开始波动。下表给出几个直接可用的判据。
| 现象 | 诊断 | 处理 |
|---|---|---|
| train loss 下降,val loss 也下降 | 正常收敛 | 继续训练 |
| train loss 下降,val loss 不再降 | 容量足够,已接近上界 | 提高数据多样性或减少模型容量 |
| train loss 继续降,val loss 上升 | 过拟合 | 增加增广、增大 weight_decay、提前停止 |
| cls_loss 高但 obj_loss 低 | 类别混淆 | 检查 names 顺序,查看混淆矩阵 |
| mAP 曲线震荡剧烈 | 学习率偏高 | 等间隔降低 lr0 或启用余弦退火 |
我一般会在 epoch 60 左右看一眼 loss 曲线,如果 val/box_loss 已经连续 20 轮没有更新,说明模型可学的东西已经学完,继续硬跑纯属浪费电。YOLOv5 自带早停机制,patience 参数设成 50 即可,它会监控验证集 mAP,连续 50 轮不提升就自动结束。注意早停触发后 best.pt 是历史上最好的权重,不受最后一轮影响。
4. PyQt5 界面集成:图片、视频、摄像头三条检测链路
4.1 加载自定义权重与推理函数
训练好的 best.pt 要集成进界面,第一步是把模型的加载和推理封装成独立类,这样三个检测入口都能复用同一套逻辑。推荐直接用 ultralytics/yolov5 的 hub 接口加载本地权重,代码量最少。
import cv2 import torch class WeldDetector: def __init__(self, weights_path, conf=0.25, iou=0.45): self.model = torch.hub.load('ultralytics/yolov5', 'custom', path=weights_path, force_reload=False) self.model.conf = conf self.model.iou = iou def detect(self, bgr_img): results = self.model(bgr_img) rendered = results.render()[0] return rendered, resultstorch.hub.load 的第一个参数是 repo 名,custom 表示加载本地训练好的权重,path 指向 best.pt。如果你的机器没有外网,第一次加载会把仓库下载到缓存,离线时可以在本地 clone 一份 yolov5 源码,再把第一个参数换成本地路径,例如 torch.hub.load('./yolov5', 'custom', path='best.pt', source='local')。model.conf 和 model.iou 分别控制置信度阈值和 NMS 阈值,后面界面里的滑块就改这两个属性。
detect 方法接收 OpenCV 读进来的 BGR 图像,返回两个结果:rendered 是画完框的 BGR 图像,results 是原始 YOLO 结果对象,可以从中取类别名、置信度或目标框坐标。注意 results.render() 返回的是一个列表,取 [0] 是因为输入只有一张图,批量推理时会返回多张。这里不要直接对显示图像做缩放后再喂给模型,YOLOv5 内部会做 letterbox 填充,你只需要保证输入是 numpy 数组即可。
4.2 三种输入源的处理方式
图片检测最简单,QFileDialog 选到路径后,cv2.imread 读进来直接喂给 detector.detect,然后借助 QImage 把 numpy 数组转成 QPixmap 显示。视频和摄像头本质上都是视频流,只是打开方式不同,一个是文件路径,一个是设备索引。
from PyQt5.QtCore import QThread, pyqtSignal import cv2 class DetectThread(QThread): frame_ready = pyqtSignal(object) def __init__(self, detector, source): super().__init__() self.detector = detector self.cap = cv2.VideoCapture(source) def run(self): while self.cap.isOpened(): ret, frame = self.cap.read() if not ret: break rendered, _ = self.detector.detect(frame) self.frame_ready.emit(rendered)这里的 source 可以是视频文件路径,也可以是摄像头索引。cv2.VideoCapture(0) 表示使用第一个摄像头,如果电脑插了外置 USB 摄像头,索引可能变成 1 或 2,实测发现部分摄像头对读取延迟很敏感,最好在打开前加两帧预热,否则前几帧是黑的。把推理放在 QThread 里是因为 GPU 推理和 OpenCV 读取都会阻塞主线程,如果直接在界面槽函数里做,窗口会失去响应。
在主界面里,线程通过信号 frame_ready 把画完框的图像送回来。为了控制视频播放速度,我一般在主线程用 QTimer 定时触发读取,而不是在 run 里死循环;摄像头场景则必须用线程持续读取,否则帧缓冲会越积越多,画面延迟越来越大。摄像头用线程还可以在 closeEvent 里调用 thread.quit 和 thread.wait 保证退出时不崩溃。对于标签显示,可以在界面上放两个 QLabel,一个显示原图,一个显示检测结果,方便对比。
4.3 界面设计中的常见坑
把模型、线程和界面接起来后,实际运行最先遇到的坑往往不是模型精度,而是环境与资源问题。下表整理了几个高频问题。
| 现象 | 原因 | 解决办法 |
|---|---|---|
| 打开摄像头后界面卡死 | 推理占用了 GUI 线程 | 改为 QThread + 信号槽 |
| 检测结果框颜色不对 | OpenCV BGR 与 Qt RGB 混用 | 显示前用 cv2.cvtColor 转为 RGB |
| 第一次 detect 特别慢 | 模型加载和 warmup 耗时 | 应用启动时先加载模型并跑一张空图 |
| 视频检测帧率低 | 每帧都做 letterbox 和 GPU 推理 | 降低 img 尺寸,或用 ONNX Runtime |
| 摄像头显示有延迟 | 读帧与推理同步阻塞 | 读帧线程和推理线程解耦 |
BGR 转 RGB 是最容易漏的一步:cv2.imread 读出来是 BGR,QImage 构造时 format 用 Format_RGB888 会显示颜色发蓝。正确的转换是在传信号之前或之后执行 cv2.cvtColor(rendered, cv2.COLOR_BGR2RGB)。另外,界面上的置信度滑块最好直接绑定 detector.model.conf,槽函数里只做 setText 显示,不要重新加载模型。
5. 验证与进阶:用 val.py 复现指标并调优置信度
5.1 在验证集上复现精度指标
拿到权重以后,第一件事不是在界面上点两张图看效果,而是在自己的验证集上跑一遍官方验证脚本,复现 mAP。这样可以排除随机干扰,判断这份权重是否真的可靠。
python val.py --weights runs/train/exp/weights/best.pt --data weld.yaml --img 640 --conf-thres 0.25 --iou-thres 0.45val.py 会输出每个类别的 Precision、Recall 和 mAP50,同时生成一个检测结果的 txt 文件。关注 mAP50 和 mAP50-95 这两个指标:mAP50 是 IoU 阈值 0.5 下的平均精度,适合判断目标有没有被框到;mAP50-95 对框的精度更严格,适合衡量边框贴合度。用这份输出和训练时 results.png 里的曲线对照,能快速发现数据目录是否搞错。
5.2 根据 PR 曲线调整界面置信度阈值
PR 曲线调阈值时不要只看默认的 conf=0.25。曲线上的每个点对应一组置信度阈值,左上区域表示高阈值,右下区域表示低阈值。焊缝质量检测里,漏掉一个缺陷比多画一个框的风险大得多,所以我会把 conf 降到 0.1,并同步把 iou 从 0.45 提到 0.5。这样同一道焊缝即使出现两个重叠框,也会被 NMS 合并,不会在界面上看到重复报警。如果你更在意误检率,比如自动判定流水线要求不合格品必须复核,conf 则调到 0.4 以上。调完后一定要在含光照变化的视频序列上实测,静止图片上 PR 曲线表现好,不代表动态场景下判断稳定。对焊缝这种亮度集中的图像,还可以对输入做一次直方图均衡化再送进模型,部分场景能额外提升召回率。
本文还有配套的精品资源,点击获取