简介本资源面向工业质检方向的算法学习者与工程开发者提供一套可直接复现的YOLOv5焊缝质量好坏检测方案解决焊缝缺陷识别中数据准备与模型落地的问题。包内已包含训练完成的检测权重并附PR曲线、loss曲线等训练过程记录便于评估模型表现。数据集采用labelimg标注的钢材缺陷检测样本图片为jpg格式标签同时提供xml与txt两种格式并分文件夹存放方便直接接入YOLO训练流程。压缩包共约2000个文件以1134张jpg图像、1134个xml标注和1135个txt标签为主整体约222.96MB。配套PyQt界面支持检测图片、视频以及调用摄像头并提供相应选择项适合课程设计、毕业设计或工业检测原型验证。目前已有987人学习下载可帮助读者快速完成从数据到界面演示的完整闭环。1. 焊缝质检为什么值得用 YOLOv5 重做一遍产线上做焊缝质检的老师傅都懂肉眼盯弧光、盯气孔、盯咬边一个班下来眼睛发花漏检率还压不住。这套资源就是冲着这个场景来的一份已经训练好的 YOLOv5 焊缝质量好坏检测权重配一份 labelImg 标注的钢材缺陷数据集再搭一个 PyQt 界面能直接跑图片、视频和摄像头。它不是让你从零炼丹而是把「数据—权重—推理—界面」这条链路一次性铺好适合两类人一类是想快速验证焊缝缺陷检测可行性的工艺或设备工程师另一类是拿它当毕设或课程设计底座的学生。数据集里图片是 jpg标签同时给了 xml 和 txt 两套省掉格式转换那一步权重和 PR 曲线、loss 曲线都在能直接看训练收敛情况。下面我按「资源是什么—怎么跑起来—坑在哪—怎么进阶」的顺序拆开讲中间该抄的代码、该改的参数都落到具体位置。2. 数据集与权重拆包xml/txt 双标签怎么对齐2.1 数据集目录结构与标签格式拿到压缩包先别急着训练第一步是把目录结构看清楚。这套数据集的核心是图片和两套标签并存xml 是 labelImg 直接产出的 Pascal VOC 格式txt 是 YOLO 训练要用的归一化坐标格式。常见做法是把它们放在同级目录下用文件名做关联比如dataset_00294.jpg对应dataset_00294.xml和dataset_00294.txt。正文里列出的那串dataset_00294.txt、dataset_0079.txt就是 txt 标签的样本说明数据是按编号散列存放的不是按类别分文件夹。我一般会先跑一段脚本统计一下图片数、标签数、类别分布确认没有孤儿标签有 txt 没 jpg或者空标签。焊缝好坏检测通常是二分类或几分类类别名在classes.txt或data.yaml里定义先确认类别顺序因为 YOLO 的 txt 标签里 class_id 是数字顺序错了整个训练就废了。import os from collections import Counter img_dir images lbl_dir labels_txt imgs {os.path.splitext(f)[0] for f in os.listdir(img_dir) if f.endswith(.jpg)} lbls {os.path.splitext(f)[0] for f in os.listdir(lbl_dir) if f.endswith(.txt)} print(图片数:, len(imgs)) print(标签数:, len(lbls)) print(有图无标签:, len(imgs - lbls)) print(有标签无图:, len(lbls - imgs)) cls_counter Counter() for name in lbls: with open(os.path.join(lbl_dir, name .txt)) as f: for line in f: line line.strip() if line: cls_counter[line.split()[0]] 1 print(类别分布:, cls_counter)这段脚本干三件事集合差集找出不配对的样本逐行读 txt 统计每个 class_id 出现次数。参数上img_dir和lbl_dir按你实际解压后的路径改类别分布如果严重倾斜比如某一类只有个位数训练时就要考虑过采样或者调cls损失权重否则模型会偏向多数类焊缝「好」的样本全判对、「坏」的全漏掉。2.2 权重文件与训练曲线怎么读权重一般是best.pt和last.pt两个best.pt是验证集指标最好的那一轮推理和部署都用它last.pt是最后一轮用来续训。资源里附带的 PR 曲线和 loss 曲线不是装饰是判断这份权重能不能直接用的依据。PR 曲线看的是每个类别的 precision-recall 权衡曲线下面积AP越接近 1 越好loss 曲线看训练有没有发散或者过拟合如果验证 loss 在中后期往上翘说明过拟合了这份权重在产线新图片上大概率翻车。读曲线时重点看两个地方一是 recall 在高 precision 区间掉得快不快焊缝缺陷检测里漏检recall 低比误检更致命因为漏掉的坏焊缝可能直接流到下一道工序二是看类别之间的 AP 差距如果「坏」这一类 AP 明显低于「好」说明缺陷样本太少或者特征不明显得回去补数据。我一般会把results.png和PR_curve.png一起看再配合confusion_matrix.png确认有没有把好坏两类搞混。2.3 用 val.py 快速验证权重可用性在动 PyQt 界面之前先用官方val.py跑一遍验证集确认权重和数据集对得上。这一步能提前暴露类别数不匹配、路径写错、图片损坏这些问题比在界面里调试快得多。python val.py \ --weights best.pt \ --data data/weld.yaml \ --img 640 \ --batch 16 \ --task val \ --save-json--weights指向你的best.pt--data是数据集配置文件里面要有train、val、nc、names四个字段nc必须和权重里的类别数一致不一致会直接报 shape 错误。--img 640是推理分辨率要和训练时一致训练用 640 推理用 320 会导致小缺陷漏检。--save-json会输出每个图的检测结果 json方便你写脚本比对。跑完看输出的 mAP0.5 和 mAP0.5:0.95如果 mAP0.5 低于 0.6这份权重在产线上基本没法直接用得考虑重训或者补数据。3. PyQt 界面接入图片、视频、摄像头三路推理3.1 界面结构与信号槽设计PyQt 界面这块核心是把 YOLOv5 的推理封装成一个独立线程主线程只管 UI不然推理一卡界面就假死这是新手最容易踩的坑。界面一般分三块左侧是输入源选择图片/视频/摄像头单选或下拉中间是显示区QLabel 或 GraphicsView右侧或底部是结果输出和置信度阈值滑块。信号槽的设计思路是按钮点击触发start_detect槽函数槽函数根据当前选择创建对应的推理线程线程通过pyqtSignal把带框的帧或结果图发回主线程刷新显示。常见做法是把推理逻辑写成一个DetectThread(QThread)类run()里循环读帧、调模型、画框、emit 信号。摄像头用cv2.VideoCapture(0)视频用cv2.VideoCapture(path)图片直接cv2.imread走一次推理。三路输入的区别只在取帧方式推理和画框逻辑可以复用同一个函数这样代码不会散。3.2 推理线程与画框代码下面这段是推理线程的核心骨架把模型加载、取帧、推理、画框、发信号串起来。注意模型只加载一次放在__init__里不要每帧都 load否则帧率会掉到个位数。import cv2 import torch from PyQt5.QtCore import QThread, pyqtSignal class DetectThread(QThread): frame_signal pyqtSignal(object) # 发回带框的帧 result_signal pyqtSignal(str) # 发回文本结果 def __init__(self, source, weights, conf0.25, iou0.45): super().__init__() self.source source # 0 表示摄像头或图片/视频路径 self.conf conf self.iou iou self.running True # 只加载一次模型 self.model torch.hub.load(ultralytics/yolov5, custom, pathweights, force_reloadFalse) self.model.conf conf self.model.iou iou def run(self): if isinstance(self.source, int): cap cv2.VideoCapture(self.source) elif self.source.lower().endswith((.jpg, .png, .jpeg)): img cv2.imread(self.source) self._infer_and_emit(img) return else: cap cv2.VideoCapture(self.source) while self.running and cap.isOpened(): ret, frame cap.read() if not ret: break self._infer_and_emit(frame) cap.release() def _infer_and_emit(self, frame): results self.model(frame) annotated results.render()[0] # 画框后的 numpy 图 self.frame_signal.emit(annotated) # 统计检测到的类别和数量 labels results.pandas().xyxy[0][name].tolist() self.result_signal.emit(f检测到: {labels}) def stop(self): self.running False self.wait()conf是置信度阈值默认 0.25焊缝缺陷检测里如果误检多就往上调到 0.4~0.5漏检多就往下调到 0.15~0.2这个参数在界面上做成滑块实时传进来最好。iou是 NMS 的 IoU 阈值默认 0.45焊缝缺陷框如果挨得近调低一点能减少框被合并。results.render()[0]返回的是 BGR numpy 数组直接转 QImage 显示。results.pandas().xyxy[0]拿到的是检测框表格包含 xmin、ymin、xmax、ymax、confidence、class、name文本结果就从这里出。3.3 主窗口与三路输入切换主窗口负责把按钮、显示区、线程串起来。图片模式点按钮选文件视频模式选文件后循环播放摄像头模式直接开设备 0。切换输入源时要先stop()掉上一个线程不然两个线程同时抢摄像头会报设备占用。from PyQt5.QtWidgets import QMainWindow, QPushButton, QLabel, QVBoxLayout, QWidget, QFileDialog from PyQt5.QtGui import QImage, QPixmap import cv2 class MainWindow(QMainWindow): def __init__(self): super().__init__() self.thread None self.label QLabel(显示区) self.btn_img QPushButton(检测图片) self.btn_video QPushButton(检测视频) self.btn_cam QPushButton(打开摄像头) self.btn_stop QPushButton(停止) layout QVBoxLayout() for b in [self.btn_img, self.btn_video, self.btn_cam, self.btn_stop]: layout.addWidget(b) layout.addWidget(self.label) container QWidget() container.setLayout(layout) self.setCentralWidget(container) self.btn_img.clicked.connect(self.detect_image) self.btn_video.clicked.connect(self.detect_video) self.btn_cam.clicked.connect(self.detect_camera) self.btn_stop.clicked.connect(self.stop_thread) def _start(self, source): self.stop_thread() self.thread DetectThread(source, weightsbest.pt, conf0.25, iou0.45) self.thread.frame_signal.connect(self.update_frame) self.thread.result_signal.connect(self.update_result) self.thread.start() def detect_image(self): path, _ QFileDialog.getOpenFileName(self, 选图片, , Images (*.jpg *.png)) if path: self._start(path) def detect_video(self): path, _ QFileDialog.getOpenFileName(self, 选视频, , Videos (*.mp4 *.avi)) if path: self._start(path) def detect_camera(self): self._start(0) def stop_thread(self): if self.thread: self.thread.stop() self.thread None def update_frame(self, frame): rgb cv2.cvtColor(frame, cv2.COLOR_BGR2RGB) h, w, ch rgb.shape qimg QImage(rgb.data, w, h, ch * w, QImage.Format_RGB888) self.label.setPixmap(QPixmap.fromImage(qimg).scaled(self.label.size())) def update_result(self, text): self.setWindowTitle(text)_start里先停旧线程再起新线程这是避免资源冲突的关键。update_frame里QImage的构造参数ch * w是每行字节数写错会导致图像花屏这是 PyQt 显示 OpenCV 图像的经典坑。update_result这里简单塞到标题栏实际项目里可以放个 QTextEdit 滚动显示。摄像头模式传0如果机器有多个摄像头就传 1、2 试。4. 避坑与排查焊缝检测落地常见的五个翻车点4.1 现象界面点检测没反应控制台报 shape 错误原因权重里的类别数和data.yaml里的nc不一致或者模型加载时torch.hub.load去联网拉最新代码网络不通就卡住。解决先确认nc和权重匹配再把torch.hub.load的force_reload设为 False最好把 yolov5 仓库整个下到本地用path参数指向本地目录别依赖在线拉取。4.2 现象摄像头打开后帧率极低画面卡成幻灯片原因每帧都调results.render()和pandas()pandas 转换开销大或者模型跑在 CPU 上没走 GPU。解决推理时加self.model.to(cuda)没有 GPU 就降分辨率到 320pandas()只在需要文本结果时调画框用results.render()就够别每帧都转 DataFrame。4.3 现象检测框位置偏移框比实际缺陷大一圈原因训练时的图片尺寸和推理尺寸不一致YOLOv5 内部会做 letterbox 填充如果推理时手动 resize 破坏了长宽比坐标映射就错。解决推理统一走results.render()不要自己 resize 后再画框如果必须自己处理按 letterbox 的缩放比例反算坐标。4.4 现象好焊缝被大量误判为坏误检率居高不下原因置信度阈值设太低或者训练集里「好」样本的多样性不够模型没见过正常焊缝的各种形态。解决把conf从 0.25 提到 0.4 以上先压误检再回去补「好」类样本尤其是不同光照、不同角度、不同焊缝宽度的正常图别只补缺陷图。4.5 现象视频检测到一半程序崩溃报内存错误原因QImage引用了 numpy 数组的内存但 numpy 数组是局部变量函数返回后被回收QImage 就成了野指针。解决update_frame里把 rgb 数组 copy 一份再构造 QImage或者用QImage(rgb.data, ...).copy()确保内存生命周期覆盖显示过程。5. 进阶把置信度阈值做成实时可调并验证漏检率界面跑通之后真正决定这套东西能不能上产线的是置信度阈值和漏检率的平衡。我一般会在界面上加一个 QSlider范围 0.05 到 0.95实时把值传给推理线程的self.model.conf这样工艺人员可以边看边调找到误检和漏检都能接受的甜点。滑块联动很简单valueChanged信号连到一个槽槽里改self.thread.model.conf value / 100.0注意线程安全改属性本身是原子的不用加锁。光调还不够得有个量化验证的方法。拿一批已知好坏的焊缝图比如 100 张好、100 张坏在不同 conf 下跑一遍统计漏检率坏的判成好的比例和误检率好的判成坏的比例画一条曲线。下面这段脚本就是干这个的输出每个阈值下的两个指标。import cv2, torch, os model torch.hub.load(ultralytics/yolov5, custom, pathbest.pt) good_dir, bad_dir test/good, test/bad thresholds [0.1, 0.2, 0.3, 0.4, 0.5, 0.6] def is_bad(img_path, conf): model.conf conf r model(img_path) labels r.pandas().xyxy[0][name].tolist() return bad in labels # 假设坏类类别名为 bad for t in thresholds: miss sum(1 for f in os.listdir(bad_dir) if not is_bad(os.path.join(bad_dir, f), t)) false_alarm sum(1 for f in os.listdir(good_dir) if is_bad(os.path.join(good_dir, f), t)) print(fconf{t:.2f} 漏检率{miss/len(os.listdir(bad_dir)):.2%} 误检率{false_alarm/len(os.listdir(good_dir)):.2%})跑完你会看到一条典型的权衡曲线conf 越低漏检越少但误检越多conf 越高反过来。焊缝场景里我一般把漏检率压到 2% 以下误检率容忍到 10% 左右因为误检可以人工复检漏检直接流出去就是质量事故。这个脚本的is_bad里类别名要换成你数据集里实际的坏类名good_dir和bad_dir按你的测试集路径改。还有一个容易被忽略的点摄像头推理时的光照和训练集差异。产线现场的光照往往比数据集里的更极端反光、阴影、弧光干扰都会让模型表现掉一截。我的习惯是拿现场摄像头截一批图人工标一小部分做一次增量微调哪怕只补几十张漏检率也能明显下来。微调时学习率调小--lr0 0.001左右冻结 backbone 只训 head几轮就够别把原权重训崩了。从那以后我每次拿到一份现成权重都强制先跑一遍 val.py 看 mAP再用现场图做一次小样本验证最后才敢往界面上接。这套流程帮我省过好几次「权重看着漂亮、现场一跑全废」的后悔药。希望帮到你。本文还有配套的精品资源点击获取