
简介面向焊缝质量检测的YOLOv5实战资源提供训练好的模型权重并附PR曲线与loss曲线适合工业质检人员、计算机视觉学习者快速上手或迁移应用。压缩包内共2000个文件以jpg钢材缺陷图像、txt与xml标注文件为主两类标签分别保存在独立文件夹中便于按需选用不同标注格式资源整体约222.96MB。同时提供PyQt图形界面支持对图片、视频及摄像头画面进行实时焊缝检测交互直观。数据集已用LabelImg完成标注可直接用于模型训练或评估。目前已有986人学习下载适合需要完整闭环方案的研究者与工程师参考。1. 焊缝质量检测这件事为什么我选了 YOLOv5 而不是直接目检焊缝好不好看焊工老师傅拿手电照一眼就知道但把这份经验变成可复用的代码是另一回事。这套资源解决的就是这个训练好的 YOLOv5 权重、双格式标注数据集、带 PR 曲线和 loss 曲线的训练结果外加一个 PyQt 界面能对图片、视频和摄像头画面直接检测焊缝好坏。坦白说模型本身不算新奇值钱的是把「数据 训练 界面」串成了一条能跑的链路。适合正在做工业质检项目、需要快速出 demo 的学生或工程师也适合想在 YOLOv5 上完整走一遍训练到部署流程的人。2. 数据集与标注格式xml 和 txt 双标签的整理与验证2.1 为什么同一份数据要保留 xml 和 txt 两种标注用 labelimg 标注过数据的人都知道labelimg 默认保存的是 Pascal VOC 格式的 xml 文件而 YOLOv5 训练时读的是 txt 格式的标注文件每一行对应一个目标框格式是class_id x_center y_center width height坐标值归一化到 0~1。这套资源里把两种格式都保留了xml 存在一个文件夹txt 存在另一个文件夹。保留双格式不是顺手为之实际工程里很有用。xml 适合做数据回看和二次标注可视化调试时直接读 xml 画框很方便txt 则直接喂给 YOLOv5 训练省去每次训练前都要转换的步骤。而且一旦某个类别需要重标或合并有 xml 在手就不用重新标一遍改完 xml 再转一次 txt 就行相当于留了后悔药。从文件名能看出标注方式比如 dataset_00294.txt、dataset_0079.txt 这种图片是对应的 jpg。实际去核对的时候需要确保每个 jpg 都有两个标签文件这一条我后面会细说容易翻车。2.2 目录结构梳理与标签内容检查先把数据集目录结构理清楚。拿到手之后我一般先跑一段命令看全貌tree -L 2 dataset/ # 期望看到类似结构 # dataset/ # ├── images/ # │ ├── dataset_0017.jpg # │ └── ... # ├── xml_labels/ # │ ├── dataset_0017.xml # │ └── ... # └── yolo_labels/ # ├── dataset_0017.txt # └── ...这段命令的作用是把数据集的目录层级打出来确认图片和两种标签是否分门别类放好。如果文件夹命名不完全一致也没关系训练脚本里可以用路径拼接处理但至少要知道实际目录长什么样。接着抽查一个 xml 标签的内容确认类别名。打开一个 xml 文件后重点看object节点下的name字段。焊缝检测通常就两类good 和 bad也可能是 weld_good、weld_bad 这类命名。类别名的写法直接决定后面 data.yaml 里 nc 参数和 names 列表怎么填这里错了后面全部白干。同时看一眼bndbox的坐标范围是否在图片尺寸内如果有负数或超出宽高的坐标说明标注时出了手滑问题训练时会出现 loss 异常甚至直接报错。对应的 txt 标注格式长这样每一行五个数字分别是类别 id、归一化中心 x、归一化中心 y、归一化宽、归一化高0 0.512345 0.483210 0.187654 0.132901 1 0.623456 0.541208 0.214321 0.156789这个文件里第一列是类别 ID注意这里用的是 ID 不是名字。YOLO 训练时只认数字 IDID 的对应关系就是 data.yaml 里 names 列表的下标names 列表第几项对应 ID 几。如果 xml 里第一类叫 good第二类叫 bad那 names 就要写[good, bad]ID 0 对应 goodID 1 对应 bad。2.3 用脚本做训练集与验证集划分训练之前必须划分 train 和 val而且划分方式比大多数人想象的更有讲究。如果直接把所有图片随机按 8:2 划分大概率会把同一个工件不同角度的照片同时分进训练集和验证集结果 PR 曲线特别好看一到现场检测就露馅。我一般按图片编号前缀分组后再划分或者直接保证同一编号的图片只出现在一个集合里。用脚本做划分最方便import os import random import shutil from sklearn.model_selection import train_test_split image_dir dataset/images all_images [f for f in os.listdir(image_dir) if f.endswith(.jpg)] # 按前缀编号分组避免同一工件图片被切到两个集合里 image_ids list(set([f.split(_)[1].split(.)[0] for f in all_images])) train_ids, val_ids train_test_split(image_ids, test_size0.2, random_state42) def write_split(file_list, out_file): with open(out_file, w) as f: for img in file_list: img_path os.path.join(image_dir, img) f.write(img_path \n) train_imgs [f for f in all_images if f.split(_)[1].split(.)[0] in set(train_ids)] val_imgs [f for f in all_images if f.split(_)[1].split(.)[0] in set(val_ids)] write_split(train_imgs, train.txt) write_split(val_imgs, val.txt) print(ftrain: {len(train_imgs)}, val: {len(val_imgs)})这段代码的关键在于image_ids这行它把图片文件名里的编号部分抽出来用编号而不是单张图片作为划分单位这样一个工件所有角度都只会进训练集或验证集。random_state42是固定随机种子保证每次运行划分结果一致方便复现。write_split函数把图片的绝对路径写入 txtYOLOv5 的 data.yaml 里可以直接引用这个路径列表文件。2.4 标签一致性校验脚本这是数据集环节最容易被忽略的一步。xml 和 txt 是两套标注经过人工修改后很可能出现不同步比如 xml 里改了类别名但 txt 忘了改或者某张图只有 xml 没有 txt。训练时 YOLOv5 只会去找 txt缺文件或者文件内容为空轻则跳过该图重则直接中断训练。import os import xml.etree.ElementTree as ET image_dir dataset/images xml_dir dataset/xml_labels yolo_dir dataset/yolo_labels for img_name in sorted(os.listdir(image_dir)): if not img_name.endswith(.jpg): continue base img_name.replace(.jpg, ) xml_path os.path.join(xml_dir, base .xml) yolo_path os.path.join(yolo_dir, base .txt) if not os.path.exists(xml_path): print(f[missing xml] {base}) continue if not os.path.exists(yolo_path): print(f[missing yolo] {base}) continue # 检查 xml 中每一个目标是否在 txt 中有对应数量的行 tree ET.parse(xml_path) root tree.getroot() xml_obj_count len(root.findall(object)) with open(yolo_path) as f: yolo_lines [line.strip() for line in f.readlines() if line.strip()] if xml_obj_count ! len(yolo_lines): print(f[count mismatch] {base}: xml{xml_obj_count}, yolo{len(yolo_lines)})这段脚本会扫描每一张图片对应的两个标签文件主要查两件事文件是否存在、目标数量是否一致。xml 里object节点的数量应该等于 txt 的标注行数不一致就说明两个标签不同步。打印结果会直接告诉你哪张图出了问题修起来目标明确。这类问题如果不在训练前清掉训练到一半报错再回来查就非常被动。3. 训练与权重落地从 data.yaml 到 PR 曲线与 loss 曲线3.1 data.yaml 与模型配置的对应关系YOLOv5 训练前要准备一个 data.yaml它是数据集的「入口文件」告诉训练器类别数、类别名和数据集路径。焊缝检测这个场景data.yaml 写起来不复杂但有一个坑很多人第一次都会踩路径是相对路径还是绝对路径。# data.yaml train: ./dataset/train.txt val: ./dataset/val.txt nc: 2 names: [good, bad]train和val指向前面生成的 train.txt 和 val.txt 文件里面每一行是图片的绝对路径或相对路径。nc是类别数量这里只有焊缝良好和焊缝缺陷两类所以是 2。names列表的顺序决定了标注 ID 的语义ID 0 对应goodID 1 对应bad必须和标注文件 txt 里的第一列数字对应上。有个细节值得注意如果你直接训练时报错说找不到图片或标签优先检查 train.txt 里的路径是否从启动训练命令的工作目录出发指向图片。我习惯把 train.txt 里的路径写成绝对路径一次到位避免因为换终端、换工作目录导致路径失效。YOLOv5 官方文档里常用相对路径但实际工程里绝对路径更省心特别是数据集挪过位置之后。3.2 训练命令与关键超参数YOLOv5 的训练入口是train.py命令行启动方式一般长这样python train.py \ --data data.yaml \ --cfg models/yolov5s.yaml \ --weights yolov5s.pt \ --epochs 100 \ --batch-size 16 \ --imgsz 640 \ --device 0--cfg指定模型结构这份资源里选的是 yolov5s属于 YOLOv5 系列里速度和精度比较均衡的一档适合焊缝检测这种对实时性有一定要求的工业场景。--weights yolov5s.pt是预训练权重从 COCO 数据集训练出来的通用特征作为起点迁移到焊缝数据上可以明显加快收敛也能在小数据集上稳住训练过程。如果你不想用预训练权重可以改成--weights 但数据量不够大时从头训很容易欠拟合。--epochs 100和--batch-size 16是要根据数据集大小和显卡显存调的两个数。数据量在几百张级别时100 个 epoch 基本够用批量大小受显存限制16 跑不动就降到 8再不行 4。--imgsz 640是输入分辨率YOLOv5 默认就是 640焊缝这种目标不算特别小的场景不用改改大了精度提升有限但显存占用翻倍。--device 0指定 GPU 编号机器没 GPU 时改成--device cpu训练会慢很多但能跑通。部分超参也可以通过--hyp指定一个 yaml 文件覆盖但焊缝检测不是竞赛场景默认超参已经够用。那些在博客、论坛里被反复调的 anchor 尺寸、mosaic 概率等在小数据集上差别不大不建议一上来就动。3.3 PR 曲线与 loss 曲线的读法训练结束后runs/train/exp目录下会出现PR_curve.png和results.png这两个文件是判断模型好坏最直接的依据。results.png里包含训练和验证两套 loss 曲线重点看 val 的 box_loss 和 cls_loss 是否持续下降、最终是否平稳。如果 val loss 在后半段开始回升而 train loss 还在降那就是过拟合需要回退到更低 epoch 的权重或者加强数据增强。PR_curve.png展示的是 precision 和 recall 的权衡关系曲线越靠近右上角说明模型越理想。焊缝检测这种场景缺陷漏检比误报严重得多因为漏掉一个坏焊缝可能直接导致结构失效所以看曲线时重点看 recall 保持在较高水平时 precision 能有多少。如果曲线在 recall 0.9 附近 precision 就掉到 0.7说明模型倾向于多框出来一些区域来避免漏检这在当下场景是可以接受的反而比极端追求 precision 更实用。3.4 权重文件的选择best.pt 和 last.pt 的区别训练完之后runs/train/exp/weights下会有两个文件best.pt和last.pt。best.pt是验证集指标最优的那个历史权重last.pt是最后一个 epoch 的权重。常规选择是用 best.pt 做后续部署因为它代表的是模型在验证集上表现最好的状态但如果你的数据集划分有随机性best.pt 可能对验证集过拟合实际测试时 last.pt 反而更稳。我的习惯是两者都留着先用 best.pt 测一轮现场数据效果不理想再换 last.pt 对比哪个好留哪个。权重文件的后续使用分两条路一条是直接用 PyTorch 的 .pt 文件跑推理另一条是转成 ONNX 或 TensorRT 做加速。PyQt 界面里直接用 .pt 文件最省事不需要额外装推理引擎实时性要求不高的场景足够了。4. PyQt 界面实现图片、视频、摄像头三种检测模式4.1 界面功能与整体结构这套资源的 PyQt 界面主要解决一个需求让不写代码的人也能用训练好的模型做检测。界面逻辑很简单三个入口对应三种数据来源检测图片、检测视频、调用摄像头。每个模式都对应一个线程处理避免界面卡死。界面结构上核心是一个 QMainWindow上面放按钮和显示区域。按钮触发文件选择对话框或摄像头启动逻辑选完文件后把路径传给推理线程推理结果再通过信号传回界面刷新显示。检测结果的绘制直接复用 YOLOv5 自带的绘图函数把标注框和置信度画到原图上再显示。4.2 核心推理代码封装模型推理部分可以封装成一个类PyQt 界面和命令行测试都能共用import torch import cv2 from pathlib import Path class WeldDetector: def __init__(self, weights_path, devicecpu, conf_thres0.25): self.model torch.hub.load(ultralytics/yolov5, custom, pathweights_path, force_reloadFalse) self.model.conf conf_thres self.model.device device def detect_image(self, img_path): results self.model(img_path) detections results.pandas().xyxy[0] # 转为 pandas DataFrame return detections def detect_frame(self, frame): results self.model(frame) rendered results.render()[0] # 绘制标注框后的图像 return renderedtorch.hub.load这里要注意一点path参数指向训练好的 .pt 权重文件force_reloadFalse表示优先用本地缓存的模型代码不重复下载。conf_thres是置信度阈值低于这个值的检测框会被过滤掉焊缝场景我一般设 0.25~0.3 之间调太高容易漏检太低则会出现大量误报。detect_image接收图片路径直接推理返回一个表格格式的检测结果包含 xyxy 坐标、置信度和类别名。detect_frame接收的是摄像头或视频解码出来的单帧图像注意这里不能用路径了必须是一帧 ndarray 数据。results.render()会把画好框的图像返回出来直接用于界面显示。4.3 摄像头模式的关键实现线程与帧率控制摄像头检测最容易出的问题是界面假死。把推理放在 Qt 主线程里视频流一卡按钮点不动窗口直接显示「未响应」。正确的做法是开一个 QThread 专门做采集和推理把结果通过信号发回主线程刷新。import threading import cv2 from PyQt5.QtCore import QThread, pyqtSignal class CameraThread(QThread): change_pixmap_signal pyqtSignal(object) def __init__(self, detector): super().__init__() self.detector detector self.running True def run(self): cap cv2.VideoCapture(0) while self.running: ret, frame cap.read() if not ret: break rendered self.detector.detect_frame(frame) self.change_pixmap_signal.emit(rendered) cap.release() def stop(self): self.running False self.wait()这段代码的核心是change_pixmap_signal信号它在run方法里不断发出新的检测结果帧主线程里连接到槽函数更新界面。cv2.VideoCapture(0)打开默认摄像头如果摄像头被占用或编号不对ret 会是 False循环会退出。stop方法通过置位running标志来中断循环然后等待线程回收避免程序退出时崩溃。摄像头模式还有一个帧率控制的细节模型推理速度如果低于摄像头采集速度会导致视频流越来越慢。解决方案是跳帧处理比如每 3 帧只推理 1 帧中间 2 帧直接显示原始画面或者干脆把输入帧缩小到推理尺寸。这个场景不需要每帧都检测真实质检场景里跳过几帧完全不影响结果判断。4.4 界面槽函数与数据流串联界面部分的核心逻辑是把按钮点击、文件选择、线程启动和结果显示串起来。这里给出一个最小可用示例from PyQt5.QtWidgets import QApplication, QFileDialog, QLabel, QPushButton, QVBoxLayout, QWidget class MainWindow(QWidget): def __init__(self, detector): super().__init__() self.detector detector self.image_label QLabel() self.btn_image QPushButton(检测图片) self.btn_image.clicked.connect(self.on_click_image) layout QVBoxLayout() layout.addWidget(self.image_label) layout.addWidget(self.btn_image) self.setLayout(layout) def on_click_image(self): path, _ QFileDialog.getOpenFileName(self, 选择图片, , Image Files (*.jpg *.png)) if path: img cv2.imread(path) rendered self.detector.detect_frame(img) h, w, ch rendered.shape bytes_per_line ch * w qimg QImage(rendered.data, w, h, bytes_per_line, QImage.Format_RGB888).rgbSwapped() self.image_label.setPixmap(QPixmap.fromImage(qimg))这个槽函数的作用是点击按钮后弹出文件选择框选到图片后调用检测器的detect_frame方法完成推理再把绘制了检测框的 ndarray 转换为 Qt 能显示的QImage最后通过setPixmap把结果显示到界面上。rgbSwapped()这一步不能漏OpenCV 默认是 BGR 通道顺序Qt 要显示 RGB不转换的话画面颜色会偏蓝橙。检测视频和摄像头两个模式的数据流与图片类似区别只在于视频是逐帧读取、摄像头是实时流。资源自带的 PyQt 界面已经把这些串好了你需要改的通常只有权重的路径、置信度阈值和类别名对应的显示文字。5. 避坑与常见问题焊缝检测最容易翻车的五个点5.1 数据集文件名不匹配导致训练中断现象训练跑到一半报错提示找不到某张图的标注文件或者直接卡在加载数据集的阶段反复打印警告。原因图片和标签文件命名不一致比如图片叫 dataset_00294.jpg标签却叫 dataset_294.txt还有一种情况是标签文件路径里含中文YOLOv5 的 dataloader 在 Windows 下对中文路径支持不友好。解决训练前先跑一遍 2.4 节的校验脚本把所有缺失和不匹配的文件列出来统一批量重命名。路径不要带中文数据集目录放到纯英文路径下这是最省事的做法。5.2 类别 ID 错位导致检测结果完全反了现象模型能出框但 good 显示成 badbad 显示成 good置信度还特别高。原因data.yaml 里的 names 顺序和标注 txt 里的类别 ID 对应错了。labelimg 标注时类别 A 是 ID 0类别 B 是 ID 1但 data.yaml 里写成了[bad, good]ID 0 就变成了 bad所有标注全部错位。解决核对标注文件和 data.yaml 的对应关系最简单的方式是拿一张已知答案的图跑一次推理看输出类别是否和预期一致。不一致就把 names 列表调换重新训练。这类错误不会报错只会静默地给出错误结果特别危险。5.3 PR 曲线很漂亮但实际检测效果差现象验证集上的 PR 曲线接近完美mAP 也有 0.95 以上但拿到现场新拍的焊缝图片上检测框乱飘或者在缺陷区域框不出来。原因数据集划分按图片随机打散同一个焊缝的不同角度照片同时出现在训练集和验证集里相当于开卷考试。模型实际上记住了这些特定视角的特征而不是学习了「什么是好的焊缝」和「什么是坏的焊缝」。解决重新划分数据集按 2.3 节那样以工件编号为单位划分保证同一个工件的所有图片只出现在训练集或验证集中。划分完再看 PR 曲线如果 mAP 明显下降那反而是真实水平的反映调整方向也更有依据。5.4 PyQt 摄像头检测界面卡死现象点击「摄像头检测」后界面立刻变灰无响应几秒后系统提示强制关闭。原因摄像头采集和模型推理都跑在 Qt 主线程里推理一次要几百毫秒期间界面无法处理任何事件表现就是假死。解决把推理移到独立线程用 QThread 加信号槽的方式把结果传回主线程更新界面。具体实现参考 4.3 节的代码核心是不要在主线程里做任何耗时操作。5.5 权重文件与代码版本不匹配报 KeyError现象加载训练好的 .pt 权重时报 KeyError或者模型能加载但推理结果明显异常。原因YOLOv5 不同版本之间存在差异比如 6.0 和 6.1 的模型结构模块名不一样用新版本源码加载旧版本权重会报键不匹配。还有一种情况是用第三方改过的 YOLOv5 训练出的权重拿到原版里推理同样会出问题。解决固定 YOLOv5 源码的版本用git checkout切到和训练时一致的提交再推理。资源里训练好的权重对应的源码版本要确认清楚界面里的torch.hub.load也需要指定一致的版本否则就是玄学报错。6. 进阶用法把权重部署到摄像头实时检测前的三点优化先讲推理加速。PyQt 界面里直接加载 .pt 走的是 PyTorch 动态图推理在 CPU 上每帧可能耗时 300~500 毫秒摄像头画面会明显卡顿。常见的做法是导出 ONNX 再用 OpenCV 或 ONNX Runtime 推理速度提升明显。import torch model torch.hub.load(ultralytics/yolov5, custom, pathbest.pt) model.model.eval() dummy_input torch.randn(1, 3, 640, 640) torch.onnx.export( model.model, dummy_input, best.onnx, opset_version12, input_names[images], output_names[outputs], dynamic_axes{images: {0: batch}}, ) print(export done)这段导出代码里dynamic_axes把 batch 维设为动态这样导出的 ONNX 在推理时 batch 可以不为 1。opset_version用 12 是兼容性比较好的选择在 OpenCV 的dnn模块里可以直接加载。如果你的机器有 NVIDIA GPU还可以进一步用 TensorRT 做 fp16 推理延迟能压到 10 毫秒以内但配置过程相对繁琐需要确认显卡型号和 CUDA 版本。再说图像预处理。焊缝图片有一个特点亮度不均和反光严重特别是金属表面的弧光会干扰检测。我的习惯是在检测前先对灰度图做 CLAHE 自适应直方图均衡能明显提升暗部焊缝的检出率而不影响亮部特征。这个处理可以用 OpenCV 一行完成加在 detect_frame 里就行代价是每帧增加几毫秒的耗时但现场实际效果提升明显。最后是置信度阈值和 NMS 参数的配合。不要把conf_thres一直固定在 0.25现场环境噪声大的时候调高到 0.35误报会少很多检测率不足时调低到 0.15多框一些区域再人工复检。资源里的界面默认阈值一般设 0.25这个值在室内实验环境下够用但到了车间现场光照变化剧烈务必跑几组真实画面后再定阈值否则就是拿模型靠猜。另外我每次换数据集或者调完参数都会先跑一遍那三张最有代表性的现场图片一张好的焊缝、一张坏的焊缝、一张正常但有反光的焊缝。如果这三张都过了再开摄像头实拍。从那以后不管是自己做项目还是帮别人移植这套流程我都会强制走一遍这三个检查数据划分是否按工件隔离、类别 ID 是否对齐、阈值是否在现场重新标定过。希望帮到你。本文还有配套的精品资源点击获取