☰
YOLOv8钢材缺陷检测:从数据集到Qt GUI完整实现
2026/10/1 12:03:05 网站建设 项目流程

简介:面向工业质检场景的YOLOv8钢材缺陷检测完整方案,整合了训练好的pt模型权重、PR与loss曲线以及可视化PyQt5图形界面,适合需要快速落地缺陷识别或学习检测流程的开发者。压缩包共2000个文件、约94.62MB,其中1400余个txt标签与346张jpg图像构成已标注数据集,xml与txt两种格式分别保存;另有171个py脚本覆盖训练、推理、评估和界面逻辑,yaml配置、UI文件、PDF说明与模型权重等一应俱全,目录划分清晰,便于按模块调取。资源还附有用labelimg标注完成的多种常见钢材缺陷类别图片,支持在GUI中直接检测图片、视频并调用摄像头,也能对照PR曲线与loss曲线评估模型表现,快速判断收敛情况。已有605人学习下载,适合作为从数据标注、模型训练到界面部署的全链路参考,可显著减少重复调参与整理数据的耗时。

1. 钢材缺陷检测为什么值得用YOLOv8做一套带GUI的完整工具

做工业质检的人大概都有过这种体验:产线上每天几千张钢板表面图像,靠人工盯屏幕,眼睛酸了就开始漏检,CRT裂纹、麻点、夹杂这类小目标偏偏又最考验眼力。把YOLOv8训练好的缺陷检测权重接进一个Qt界面,让操作员框选图片就能看到缺陷类型和位置,这不是实验室自嗨,是能直接顶到产线工位上的落地方案。

这条技术路线的核心价值在于:YOLOv8的权重文件只是一个产物,真正决定检测效果的是数据集怎么整理、标注怎么规范、训练参数怎么调,以及最后怎么把模型封装成非技术人员也能用的工具。数据、权重、GUI三件事环环相扣,任何一环偷懒,整套系统都会在真实场景里翻车。本文面向的读者是那些手里有钢材图像、想做缺陷检测但还没跑通全流程的工程师,跟着这条路径走完,你手里会多出一套能用的检测工具,而不是一个孤零零的模型文件。

2. 先搞定数据集:从源图像到YOLOv8训练格式的完整整理流程

2.1 公开钢材缺陷数据集与自建数据的取舍

钢材缺陷检测领域有几个公开数据集可以当起点,最常用的是东北大学发布的NEU-DET,包含1800张热轧带钢表面图像,每张分辨率200x200,覆盖 crazing、inclusion、patches、pitted_surface、rolled-in_scale、scratches这六类缺陷。另一个可选是GC10-DET,包含十类冷轧带钢缺陷,图像尺寸更大、场景更接近工业实际,但标注格式和类别定义需要额外处理。

公开数据集的问题在于:NEU-DET单张图只有200x200,目标占比大、背景单一,训练出来的模型到了产线真实环境会明显掉点。GC10-DET样本量偏少,每类只有几十到一百多张,直接训练容易过拟合。我一般把公开数据当作基准测试集用,真实项目里还是要从现场采集图像,至少覆盖不同光照、不同轧制速度、不同钢种表面状态。

自建数据要控制的变量有四个:拍摄角度固定、光源方向一致、曝光参数统一、图像分辨率不要低于模型输入分辨率。钢材表面是反光材质,光源角度一变,同一处缺陷在图像里的形态差异比缺陷本身的类间差异还要大,这一点在标注阶段就会坑人。

2.2 用Labelme标注并转换为YOLOv8格式的脚本

YOLOv8要求标签文件是txt格式,每行一个目标,格式为class_id x_center y_center width height,坐标全部归一化到0-1。Labelme默认输出JSON多边形,需要写脚本转换。这个转换脚本是整个数据管线里第一个容易出错的环节,坐标归一化计算和类别映射必须仔细,下面给出一个我常用的转换脚本:

import json import os import glob from pathlib import Path def labelme_to_yolov8(json_path, output_dir, classes): os.makedirs(output_dir, exist_ok=True) with open(json_path, 'r', encoding='utf-8') as f: data = json.load(f) img_w = data['imageWidth'] img_h = data['imageHeight'] txt_lines = [] for shape in data['shapes']: label = shape['label'] if label not in classes: print(f"[跳过] 标签 {label} 不在类别表: {json_path}") continue class_id = classes.index(label) points = shape['points'] # 计算多边形外接矩形 xs = [p[0] for p in points] ys = [p[1] for p in points] x_min, x_max = min(xs), max(xs) y_min, y_max = min(ys), max(ys) # 归一化到0-1,YOLO格式 x_center = ((x_min + x_max) / 2) / img_w y_center = ((y_min + y_max) / 2) / img_h box_w = (x_max - x_min) / img_w box_h = (y_max - y_min) / img_h # 边界裁剪防止坐标越界 x_center = max(0, min(1, x_center)) y_center = max(0, min(1, y_center)) box_w = max(0, min(1, box_w)) box_h = max(0, min(1, box_h)) txt_lines.append(f"{class_id} {x_center:.6f} {y_center:.6f} {box_w:.6f} {box_h:.6f}") if txt_lines: txt_path = Path(json_path).stem + '.txt' with open(os.path.join(output_dir, txt_path), 'w') as f: f.write('\n'.join(txt_lines)) classes = ['crazing', 'inclusion', 'patches', 'pitted_surface', 'rolled-in_scale', 'scratches'] json_files = glob.glob('labelme_json/*.json') for jf in json_files: labelme_to_yolov8(jf, 'yolov8_labels', classes) print(f"转换完成,共处理 {len(json_files)} 个JSON文件")

这个脚本的核心是把Labelme的多边形标注转换成外接矩形框。注意钢材缺陷里有一部分是细长条状,比如划痕和裂纹,外接矩形会引入大量背景噪声。坐标归一化计算里,中心点坐标是(x_min + x_max) / 2 / img_w,有些新手会误写成直接除以宽,这就是框位置漂移的常见原因。

如果标注的形状接近多边形而非矩形,直接转外接框会导致检测框过大。此时可以选择用旋转矩形标注,但YOLOv8原生不支持旋转目标,要么接受外接框的误差,要么换成YOLOv8-OBB变体。对钢材表面缺陷来说,大多数缺陷形态趋近椭圆或长条,外接框造成的IoU损失在可接受范围内。

2.3 数据集划分与类别平衡:训练集、验证集、测试集的正确分法

数据集划分看似简单,但钢材缺陷数据集有个典型陷阱:同一张钢板表面连续的图像之间特征高度相似,如果不按来源分组直接随机划分,训练集和验证集会出现数据泄漏,验证指标虚高,上了产线立刻现原形。

我一般按图像来源分组划分,而不是逐张随机打乱。如果一批图像来自同一卷钢带的不同位置,就把它们分到同一个集合里。对于NEU-DET这种公开数据,直接随机划分问题不大,因为每张图独立采集;现场采集的数据必须按卷、按批次划分。

划分脚本建议写到文件里保存映射关系,方便复现。python随机数种子固定,random.seed(42),防止每次划分结果不同导致实验结果对比失真。还要检查每个集合里每一类的样本数,特别是小样本类别(如rolled-in_scale往往占比很低),如果测试集里某一类只有几张图,指标波动会很大,模型调参时会被噪声干扰。

3. 训练出自己的缺陷检测权重:参数、损失曲线与模型选型

3.1 环境配置:Ubuntu 20.04 CPU版本也能跑通的最小方案

YOLOv8的环境配置门槛不高,但坑不少。采CPU版本在Ubuntu 20.04上跑是可行的,训练慢但能跑;推理速度在单张200x200的图上CPU也就一两百毫秒,完全够GUI演示使用。下面是CPU环境的最小配置路径:

conda create -n yolo python=3.9 conda activate yolo pip install ultralytics pip install torch torchvision --index-url https://download.pytorch.org/whl/cpu

这里有个关键点:CPU版本的PyTorch一定要用--index-url指定CPU源,直接pip install torch默认拉取的是CUDA版本,虽然CUDA版的在纯CPU机器上也能跑,但体积大且依赖NVIDIA驱动,装完之后经常因为驱动版本问题导致导入报错。

ultralytics包会连带装上opencv、matplotlib、pandas等依赖。如果机器的Python环境里已有老版本的numpy,装完ultralytics后建议重新检查依赖版本,numpy版本不匹配会导致训练时矩阵运算报错,这是YOLOv8环境搭建里的高频问题。

3.2 训练命令与必调参数详解:batch size、imgsz、epochs怎么设

训练前先准备好数据集目录结构,YOLOv8要求images/train、images/val、labels/train、labels/val四目录,配合一个data.yaml文件指定路径和类别名。data.yaml里的路径建议写绝对路径,相对路径在换目录跑训练时容易找不到文件。

train: /home/user/steel_defect/images/train val: /home/user/steel_defect/images/val test: /home/user/steel_defect/images/test nc: 6 names: ['crazing', 'inclusion', 'patches', 'pitted_surface', 'rolled-in_scale', 'scratches']

训练命令用官方CLI即可,但参数需要根据自己的数据规模调整:

yolo detect train data=steel_defect.yaml model=yolov8s.pt epochs=120 imgsz=640 batch=16 lr0=0.01 patience=20

逐个拆解参数:

  • model=yolov8s.pt是yolov8s的预训练权重,在COCO上训练过,迁移到钢材数据时能更快收敛;默认yolov8n.pt性能太弱,yolov8m及以上在CPU上训练时间翻倍。起步建议用yolov8s。
  • imgsz=640不能改小。NEU-DET原图才200x200,很多人习惯性设成320,省显存但会丢失小目标细节。钢材缺陷里的麻点和夹杂都是小目标,分辨率至少640起步。
  • batch=16在CPU上跑会较慢;有NVIDIA显卡时batch大小一般按显存2G对应8来算,显存8G上batch可以16-32,调大能加速训练但超过某个阈值后精度不再提升反而可能震荡。
  • lr0=0.01是默认值,小数据集建议降到0.005,不然前几个epoch loss可能直接飙到NaN。patience=20表示20个epoch内验证指标没提升就早停,避免资源浪费。

训练结束后权重文件存在runs/detect/train/weights/best.pt和last.pt,best是验证集指标最好的,last是最后一次迭代的。导出权重时只带best.pt就行。

3.3 画损失函数曲线图:判断模型收敛还是过拟合

YOLOv8训练日志里的loss曲线是判断训练状态的一手数据。训练完成后results.png会生成在训练输出目录里,包含train/val的box_loss、cls_loss、dfl_loss和mAP曲线。但只用官方图不够,我习惯自己画一版带平滑处理的loss曲线,方便对比不同超参实验:

import pandas as pd import matplotlib.pyplot as plt for exp in ['runs/detect/train', 'runs/detect/train2']: results = pd.read_csv(f'{exp}/results.csv') epochs = results['epoch'] smooth_window = 5 train_box_loss = results['train/box_loss'].rolling(smooth_window).mean() plt.plot(epochs, train_box_loss, label=exp.split('/')[-1]) plt.xlabel('Epoch') plt.ylabel('Box Loss') plt.title('Train Box Loss Comparison') plt.legend() plt.grid(True) plt.savefig('loss_comparison.png', dpi=150)

画曲线的意义在于看趋势:train loss和val loss同步下降说明模型在学习;val loss先降后升、train loss还在降,就是典型的过拟合信号,此时应该加早停、调大patience重新训,或者去检查标注质量而不是盲目加epoch。钢材缺陷数据集普遍偏小,过拟合是常态,早停是止损手段。

3.4 权重选择与导出:best.pt转ONNX与INT8量化的边界条件

训练完的best.pt是PyTorch格式,如果只给Qt的Python端用,直接加载就行。但想把推理提速,尤其在CPU上,建议转成ONNX再用ONNX Runtime推理,速度能提升一倍左右。转换命令很简单:

yolo export model=best.pt format=onnx imgsz=640 opset=12

转ONNX时有个坑必须提:opset默认值因torch版本而异,如果Qt GUI用的ONNX Runtime版本较旧,opset太高会不识别。碰到加载报错时先看ONNX Runtime版本号,再按版本把opset降到11或12,兼容性就解决了。

量化到INT8能进一步提速,但钢材缺陷检测对小目标敏感,INT8量化掉点比较明显。实测NEU-DET六类数据上mAP50大概会从0.87降到0.79左右。产线上要的是稳定检出率,不是最高帧率,所以不到万不得已不要上INT8。FP16精度损失小得多,但如果CPU推理不支持FP16加速,还要再转回FP32。

4. 训练与部署常见问题排查:一个坑一个坑地填

4.1 显存溢出:OOM的三类原因与对应解法

训练时最经典的翻车就是CUDA out of memory。第一类是batch设太大,8G显存硬扛batch=64,不用想直接爆。解决方式是batch=16起步,逐步往上加,显存占用用nvidia-smi实时观察。第二类是imgsz设置过大,很多人想提高精度把imgsz调到1280,显存占用是640的四倍,除非显存充足否则不建议。第三类是开了过多的dataloader worker。

worker数量设置成4-8就够,设成16反而可能因为内存瓶颈导致OOM,报错里经常会混着CUDA错误。

4.2 标注文件格式错了模型也能训练但结果全错

YOLOv8对标注格式很宽容,坐标超范围、格式错位,训练照样能跑起来。等指标出来了才发现框位置完全错位,这是最折腾人的一种隐性错误。用下面的脚本做训练前置检查:

python -c " from ultralytics.data.dataset import YOLODataset ds = YOLODataset('data.yaml', imgsz=640) print('数据集规模:', len(ds)) "

能跑通不代表没问题。正确做法是随机抽几张训练图像,把标注框画在原图上人工确认:

from ultralytics import YOLO from PIL import Image import matplotlib.pyplot as plt import matplotlib.patches as patches model = YOLO('yolov8s.pt') img_path = 'images/train/0001.jpg' results = model.predict(img_path, conf=0.5) img = Image.open(img_path) fig, ax = plt.subplots(1) ax.imshow(img) for box in results[0].boxes.xyxy.tolist(): x1, y1, x2, y2 = box rect = patches.Rectangle((x1, y1), x2-x1, y2-y1, linewidth=2, edgecolor='red', facecolor='none') ax.add_patch(rect) plt.savefig('check_annotation.png')

这个检查只花一分钟,但能过滤掉绝大多数标注格式问题。

4.3 类别不均衡:小类别mAP普遍比大类别低10个点的处理思路

钢材缺陷数据集几乎必然存在类别不均衡问题。NEU-DET里六类各300张是均衡的,但现场采集数据往往某类几千张、某类只有几十张。你在YOLOv8训练参数里会看到class weights的选项,默认会根据类别频率自动加权,但效果有限。

我常用的处理顺序是:先按原始分布训练一个版本,看各类别的mAP;对明显偏低的少数类,先去数据增强上找办法(copy-paste增强、离线翻转);还是不行再考虑扩充数据。YOLOv8的mosaic增强本身对新数据友好,小类别样本少时,mosaic会把不同图拼在一起,少数类样本参与训练的频次被动增加,这个特性对小类别是有利的,不建议关掉。

4.4 Qt加载模型时的平台插件错误

用PyQt或PySide加载YOLOv8后在嵌入式Linux或者缺少显示服务器的环境运行,出现qt.qpa.plugin: could not find the Qt platform plugin "linuxfb"这种报错的情况很常见。这不是YOLO的问题,是Qt缺少对应平台插件。在桌面Ubuntu上用xcb插件,在板端用linuxfb或eglfs插件。

解决方式是检查Qt插件的安装路径,把PYTHONPATH指向插件目录,或者设置环境变量强制指定插件:export QT_QPA_PLATFORM=linuxfb。如果用的不是嵌入式平台,常规桌面环境下应该设置QT_QPA_PLATFORM=xcb。

4.5 训练中断后续跑:断点续训与结果对比

训练到第80个epoch时断电,前面的算力全白费。YOLOv8训练时会定期保存last.pt,续训的命令是:

yolo detect train data=steel_defect.yaml model=runs/detect/train/weights/last.pt epochs=120 imgsz=640 batch=16 resume=True

有个坑要提醒:续训和重新训练的loss曲线没法直接对比,因为epoch计数不连续,结果文件会继续append而不是覆盖。要严谨地评估最终效果,还是用model.val()在验证集上跑一轮拿标准指标,val逻辑独立于训练过程,结果可复现。

5. 用Qt把权重包成GUI工具:推理逻辑、界面搭建与验证技巧

5.1 技术选型:PyQt5 + YOLOv8推理,还是C++ Qt + ONNX Runtime

GUI外壳的选择有两条路径。Python路线是PyQt5/PySide6 + ultralytics库或ONNX Runtime,开发速度快、迭代方便,适合产线试验和快速验证;C++路线是Qt Widgets + ONNX Runtime C++ API,部署体积小、启动快,适合要交付可执行exe的正式项目。

对于钢材缺陷检测这个场景,推荐Python路线。原因有三:YOLOv8的Python接口成熟,处理图像前后滤波、形态学操作都方便;PyQt的信号槽机制和Python线程搭配合理,不会因为回调阻塞UI;后期要接PLC、OPC UA等产线通信协议,Python的生态更省事。C++路线的优势在性能,但钢材缺陷检测的推理频率不需要很高,Python完全能顶住。

5.2 最小可用的Qt推理界面:加载图片、框选缺陷、导出结果

下面是一个基于PyQt5 + ultralytics的最小编程实现,界面包含图片展示区和缺陷结果显示区:

import sys from PyQt5.QtWidgets import (QApplication, QMainWindow, QLabel, QPushButton, QVBoxLayout, QHBoxLayout, QWidget, QFileDialog, QTextEdit) from PyQt5.QtGui import QPixmap, QImage from PyQt5.QtCore import Qt from ultralytics import YOLO import cv2 import numpy as np class SteelDefectGUI(QMainWindow): def __init__(self): super().__init__() self.setWindowTitle('钢材缺陷检测系统') self.model = YOLO('best.pt') self.init_ui() def init_ui(self): central = QWidget() self.setCentralWidget(central) # 图片显示区域 self.image_label = QLabel() self.image_label.setAlignment(Qt.AlignCenter) self.image_label.setMinimumSize(640, 480) # 结果文本区域 self.result_text = QTextEdit() self.result_text.setReadOnly(True) # 按钮 self.open_btn = QPushButton('打开图片') self.open_btn.clicked.connect(self.open_image) self.save_btn = QPushButton('导出检测结果') self.save_btn.clicked.connect(self.save_result) # 布局 layout = QVBoxLayout() layout.addWidget(self.image_label) layout.addWidget(self.result_text) btn_layout = QHBoxLayout() btn_layout.addWidget(self.open_btn) btn_layout.addWidget(self.save_btn) layout.addLayout(btn_layout) central.setLayout(layout) def open_image(self): path, _ = QFileDialog.getOpenFileName( self, '选择图片', '', 'Image Files (*.png *.jpg *.jpeg *.bmp)' ) if path: self.detect(path) def detect(self, path): # 读取图像并推理 img = cv2.imread(path) results = self.model(img, conf=0.5) # 画框 annotated = results[0].plot() h, w, ch = annotated.shape bytes_per_line = ch * w qimg = QImage(annotated.data, w, h, bytes_per_line, QImage.Format_RGB888).rgbSwapped() self.image_label.setPixmap(QPixmap.fromImage(qimg)) # 显示检测结果文本 text = f'共检测到 {len(results[0].boxes)} 个缺陷\n' for box in results[0].boxes: cls_id = int(box.cls[0]) conf = float(box.conf[0]) x1, y1, x2, y2 = box.xyxy[0].tolist() text += f'类型: {self.model.names[cls_id]}, 置信度: {conf:.2f}, 位置: ({x1:.0f},{y1:.0f})-({x2:.0f},{y2:.0f})\n' self.result_text.setText(text) def save_result(self): # 保存当前标注图像和结果文本 pass app = QApplication(sys.argv) window = SteelDefectGUI() window.show() sys.exit(app.exec_())

这个代码实现了核心流程:加载image、调用模型推理、在界面上画出检测框并显示每个缺陷的类型和置信度。detect方法里的results[0].plot()是官方提供的可视化方法,内部已经把框、标签、置信度画好了,不必自己用OpenCV画,减少出错环节。QImage构造时用了Format_RGB888加上rgbSwapped(),因为OpenCV的通道顺序是BGR而QImage默认RGB,这个细节不处理,图像色偏会很明显。

5.3 把推理放到子线程:防止界面卡顿的两种做法

直接在按钮回调里调用模型推理,图片一到界面就卡死,直到推理完成才恢复。在CPU推理时尤其明显。PyQt的解决方案是QThread或线程池,推理任务放子线程,UI线程只负责更新标签。官方推荐的QThread实现方式:

from PyQt5.QtCore import QThread, pyqtSignal class DetectWorker(QThread): finished = pyqtSignal(object, object) def __init__(self, model, img_path): super().__init__() self.model = model self.img_path = img_path def run(self): img = cv2.imread(self.img_path) results = self.model(img, conf=0.5) annotated = results[0].plot() self.finished.emit(annotated, results)

主界面的open_image改为创建worker并连接finished信号。这里有一个注意点:model对象在子线程里使用时要确保加载发生在worker首次运行前,否则多线程并发加载权重的报错会让你怀疑人生。模型加载放主线程初始化阶段做,worker只做推理。

另一个方案是连接摄像头做实时检测,用QTimer周期性触发检测。钢材产线上有些场景需要相机连续拍摄,可以扩展open_image逻辑读取视频流或IP相机,检测结果叠加后显示在界面上,这个就按实际需求去扩展。

5.4 验证与验收:单张检测延迟、漏检率统计与K折交叉验证

GUI做好了,最终要回答两个问题:这个模型在真实数据上的漏检率多高?单张检测延迟多少?漏检率比mAP更能反映产线价值,因为mAP是IoU阈值下的综合统计,而漏检率关心的是该检出的缺陷有没有被框出来。

验证方案至少包含三部分:第一,在之前划分的test集合上跑model.val(data=steel_defect.yaml),记录每一类的mAP50和mAP50-95;第二,把test集里所有检测失败(漏检或错检)的案例整理成表格,逐张看是标注问题还是模型能力问题;第三,记录CPU和GPU不同条件下的单张推理延迟,如果GUI交互延迟超过2秒,体验就开始变差。

进阶一点可以按缺陷类别做分层统计,比如裂纹是最需关注的缺陷,单独看裂纹的召回率。有一类常见问题是模型把划痕误检为裂纹,实例间距大、形态纤细,这类混淆在钢材缺陷里经常发生,分析混淆矩阵就能定位是哪两类在打架。

我个人的习惯是每个方案交付前都固定跑一遍同一套test集,记录mAP、漏检率、推理延迟三个数字。模型换过、参数调过、GUI改过之后,这组数字就是判断有没有退化的基准。这套流程坚持下来,后面再做别的工业检测项目,复制这套框架会非常快。希望这些经验对你有帮助。

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

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

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

立即咨询