
简介YOLOv8钢材缺陷检测资源包整合了训练好的模型权重、LabelImg标注的数据集和可交互的PyQt界面程序面向工业质检、计算机视觉学习者及需要快速落地缺陷检测的开发者解决从数据标注、模型推理到界面演示的关键环节。压缩包共2000个文件、约94.62MB核心内容包括1403个txt标签、346张jpg样本图、171个Python脚本以及yaml配置、pt权重、ui界面等目录按功能划分便于直接查阅与二次开发。模型已训练完成并附带PR曲线、loss曲线等训练过程分析能够直接用于钢材缺陷分类与定位界面端支持图片检测、视频检测和摄像头实时检测大幅降低使用门槛。资源目前已有605人学习下载适合需要完整方案参考、希望快速复现训练与部署流程的读者。1. YOLOv8钢材缺陷检测一篇文章把数据集、权重到Qt界面的活全干完车间里高速钢带一卷接一卷地过质检员的眼睛盯在屏幕上一盯就是几个小时——这就是YOLOv8钢材缺陷检测要接手的活。把轧制氧化皮、裂纹、夹杂、麻点、划痕、斑块这六类缺陷从画面里框出来背后依赖三样东西一个干净的数据集、一份训练好的缺陷检测权重、一个能让人点开就用的Qt界面GUI。这一篇按“数据集转换→训练权重→Qt界面→部署验证”的顺序拆开讲每步都给能直接抄的命令和代码也把损失不降、界面卡死、转RKNN精度掉这些坑提前标出来。适合正在搭桌面检测工具的一线工程师也适合刚拿到真实数据集想跑通全流程的入门者。2. 用NEU-DET搭建钢材缺陷数据集VOC转YOLO格式脚本与6类缺陷说明2.1 为什么选NEU-DET1800张图、六类缺陷、公开可复现做钢材表面缺陷检测数据集绕不开NEU-DET。这是东北大学公开的热轧带钢表面缺陷数据集共1800张灰度图每张200×200像素覆盖六类缺陷crazing网状裂纹、inclusion夹杂、patches斑块、pitted_surface麻点/凹坑、rolled-in_scale轧制氧化皮、scratches划痕。六类各300张看起来均衡实际训练时感受完全不同——裂纹和斑块的纹理都偏碎划痕和氧化皮都带细长条模型很容易把相似类混在一起。NEU-DET的标注是VOC格式的XML每个XML对应一张PNG里面用bndbox记录目标框。YOLOv8训练要求的标注是txt文本每行一个目标格式为“类别id 中心x 中心y 宽 高”坐标都归一化到0~1。所以项目落地第一步不是训练而是把XML批量转成YOLO格式并保证类别顺序和后续data.yaml里的names完全一致。这个顺序一旦乱了训练出来的权重就等于废的。2.2 VOC标注转YOLO txt转换脚本与参数说明转换脚本我一般这样写用ElementTree解析XML图像宽高直接用cv2读取不依赖XML里可能缺失的size节点。这样做更稳因为有些版本的NEU-DET标注文件里size字段不完整而图像本身一定在。import os import cv2 import xml.etree.ElementTree as ET # 类别顺序必须和之后的 data.yaml 保持一致不能随意调整 CLASSES [crazing, inclusion, patches, pitted_surface, rolled-in_scale, scratches] def voc_to_yolo(xml_path, img_dir, label_dir): tree ET.parse(xml_path) root tree.getroot() img_name root.find(filename).text img_path os.path.join(img_dir, img_name) # 直接读图拿宽高比信任 XML 里的 size 更可靠 img cv2.imread(img_path) if img is None: print(f图片读取失败: {img_path}) return h, w img.shape[:2] lines [] for obj in root.findall(object): name obj.find(name).text if name not in CLASSES: print(f跳过未知类别: {name} in {img_name}) continue box obj.find(bndbox) x1 int(box.find(xmin).text) y1 int(box.find(ymin).text) x2 int(box.find(xmax).text) y2 int(box.find(ymax).text) # 归一化到 0~1中心点和宽高都除以图像宽高 cx (x1 x2) / 2.0 / w cy (y1 y2) / 2.0 / h bw (x2 - x1) / float(w) bh (y2 - y1) / float(h) lines.append(f{CLASSES.index(name)} {cx:.6f} {cy:.6f} {bw:.6f} {bh:.6f}) txt_name img_name.replace(.png, .txt) with open(os.path.join(label_dir, txt_name), w) as f: f.write(\n.join(lines)) # 批量处理 Images 和 Annotations 两个目录 xml_dir NEU-DET/ANNOTATIONS img_dir NEU-DET/IMAGES label_dir NEU-DET/labels os.makedirs(label_dir, exist_okTrue) for xml_file in os.listdir(xml_dir): if xml_file.endswith(.xml): voc_to_yolo(os.path.join(xml_dir, xml_file), img_dir, label_dir)这段代码有几个参数要留意。CLASSES的顺序是整个项目的基准后面data.yaml里的names必须一字不差地照抄坐标转换用的是图像真实宽高而不是XML里的size字段避免读到脏数据宽高比直接用float转避免整数除法把框算成0。跑完检查一下labels目录随便打开一个txt第一列应该是0~5的整数后面四列都是0到1之间的小数。如果出现大于1的值十有八九是XML里bndbox的坐标单位不统一比如有的图标注直接用了绝对值像素。2.3 数据集划分与增强train/val怎么切才不翻车NEU-DET只有1800张划分比例建议8:1:1切完训练集1440张、验证集180张、测试集180张。注意一个坑不能直接对整个文件夹随机切因为六类缺陷分布虽然各300张但每张图里可能有多个类别、多个目标全局随机可能导致某个类别在验证集里只剩十几张导致评估结果波动大。我一般先按图片名排序再固定随机种子确保每次跑出来的划分一致。更保险的做法是统计每张图包含的类别按类别做分层抽样保证验证集和测试集里六类都齐。划分之后把训练集和验证集的图片路径分别写进train.txt和val.txt供YOLOv8的data.yaml引用或者直接把图片按目录放好data.yaml里写目录路径即可。数据增强方面NEU-DET原图只有200×200直接resize到640会损失细裂纹这类小目标的细节。常见做法是开启mosaic增强让模型在拼接图里学习小目标上下文同时把mosaic的缩放范围调小一点避免缺陷被缩到几个像素。增强是给模型“加难度”防止过拟合但难度加过头小目标直接消失训练反而更难收敛。这个度要在前面20个epoch里观察loss曲线再决定别一上来把增强拉满。3. 训练YOLOv8缺陷检测权重环境、命令与三个必调参数3.1 环境准备GTX1660Ti也能跑的最小配置训练YOLOv8缺陷检测权重环境配置不算复杂。Python 3.8以上装PyTorch再装ultralytics库一条pip命令搞定。显卡是GTX1660Ti这种6G显存的卡完全能跑只是模型尺寸要选对yolov8n权重约6M参数yolov8s约11M6G显存跑yolov8s在640输入下batch调到8是可以的batch调16就会OOM。如果手上只有CPU也能跑但一个epoch要等很久建议直接用yolov8n加降低输入尺寸。权重选择上我建议从COCO预训练权重开始也就是yolov8n.pt。虽然COCO里没有钢材缺陷类别但模型已经学好了边缘、纹理、形状这些底层特征迁移到缺陷检测上收敛速度明显更快。彻底从零训练不是不行但NEU-DET只有一千多张图从零训很容易过拟合效果还不见得好。预训练权重相当于给模型一个“已经会看图的底子”这也是缺陷检测这类小数据集项目最常见的做法。3.2 写data.yaml并跑通第一次训练命令与参数数据准备好之后先写data.yaml。这个文件告诉ultralytics去哪里找图、类别叫什么。路径建议写绝对路径因为CLI命令在不同目录下执行时相对路径容易找错。# data.yaml path: D:/steel_defect/NEU-DET # 数据集根目录 train: images/train # 训练集相对路径 val: images/val # 验证集相对路径 nc: 6 # 类别数必须是6 names: 0: crazing 1: inclusion 2: patches 3: pitted_surface 4: rolled-in_scale 5: scratches第一次训练命令可以这样写yolo detect train \ datadata.yaml \ modelyolov8n.pt \ epochs150 \ imgsz640 \ batch8 \ device0 \ ampTrue几个参数值得说明。imgsz640是训练输入尺寸NEU-DET原图200×200模型会把图拉大到640再训练小目标细节会被插值放大但不会丢失太多如果显存紧张降到512也能跑。batch8是6G显存下的稳妥值闭眼调到16大概率OOM。ampTrue开启混合精度训练显存占用大约能降三分之一训练速度也更快1660Ti这种卡必须开。epochs先设150配合patience20早停如果验证集mAP连续20轮不涨会自动停不会被无效训练耗时间。训练中断了也不用从头来。继续训练的命令是yolo detect train resumeTrue modelruns/detect/train/weights/last.ptlast.pt是每个epoch结束都会存的检查点resume会从最近一次保存的位置继续训练进度、优化器状态都会恢复。我习惯每50个epoch手动看一眼loss曲线确认方向没跑偏再让它接着跑。3.3 从损失曲线判断训练状态损失不降时先查这四项训练完成后runs/detect/train目录下会有results.png、results.csv和weights文件夹。results.png里画了box_loss、cls_loss、mAP50等曲线。判断训练状态主要看两条训练集box_loss是不是稳步下降验证集mAP50是不是在往上走。如果loss降了但mAP不涨说明过拟合了如果两个都不动先查标注。我想强调的是“先查标注再调参数”。损失不降时我按这个顺序排查第一随机抽三张训练图的txt和原图叠加可视化看框是否贴目标、类别id是否正确第二确认data.yaml里的names顺序和转换脚本里的CLASSES顺序一致这个错了模型白训第三看学习率lr00.01是从COCO预训练权重继续训的常用值如果是从零开始训练建议降到0.001第四把mosaic关掉跑10个epoch做基准测试如果关掉后loss明显下降说明增强强度太大模型学不过来了。看损失曲线的具体数值时不用纠结loss绝对值降到多小不同数据集、不同增强配置下loss的量级差别很大。我更关注的是曲线的形态平稳下降、没有突然跳变、验证loss和训练loss之间的差距没有越拉越大。这里不是玄学而是“loss降了但mAP不动”和“loss都不降”是两类完全不同的排错路径先分清楚是哪一类再动手改。4. 用Qt做缺陷检测界面PyQt5布局、QThread推理与相机接入4.1 界面怎么做显示区、控制面板与结果表格训练出best.pt之后下一步就是把它装进一个能给人用的界面里。这里的“qt界面gui”常见方案是用PyQt5或PySide6写桌面程序因为Python生态和ultralytics配合最顺不用跨语言调接口。界面布局我一般分为三块左侧是图像/视频显示区用QLabel控件显示检测结果帧右侧是控制面板放权重选择下拉框、置信度阈值和IOU阈值调节旋钮、图片/视频/相机切换按钮以及一个检测结果表格底部是状态栏实时显示当前FPS和处理耗时。布局的要点是显示区要能自适应缩放。QLabel显示QPixmap时用scaled(self.label.size(), Qt.KeepAspectRatio, Qt.SmoothTransformation)保持宽高比避免图像被拉伸变形。控制面板里的置信度阈值默认设0.25IOU阈值默认0.45这两个值和训练时模型默认的NMS参数保持一致界面里调了阈值能立刻看到检测结果变化这是调参最快的方式。结果表格可以用QTableWidget每行显示一个检测框的类别、置信度、坐标。表格没必要实时刷新太频繁视频流检测时每帧都刷新表格会让界面闪烁我一般只更新最后10条结果或者只在单张图片检测时刷新表格。4.2 把推理塞进QThread主线程不卡的写法这是整个Qt界面项目里最容易翻车的地方。如果直接把model.predict()写在按钮的槽函数里点一下检测界面立刻转圈卡死窗口拖不动严重时系统直接提示“未响应”。原因很简单推理是耗时操作放在GUI主线程里会阻塞Qt的事件循环界面刷新、鼠标响应全部停摆。解决方法是把推理放进QThread子线程通过信号把结果传回主线程更新界面。import cv2 from PyQt5.QtCore import QThread, pyqtSignal from ultralytics import YOLO class DetectWorker(QThread): # 用 object 类型传递图像和检测结果避免 PyQt 类型转换问题 frame_ready pyqtSignal(object, object) def __init__(self, model_path, conf0.25, iou0.45): super().__init__() self.model YOLO(model_path) self.conf conf self.iou iou self.cap None self.running True def set_capture(self, cap): self.cap cap def run(self): while self.running and self.cap is not None: ret, frame self.cap.read() if not ret: break # 推理必须在子线程里做画框也在这里完成 results self.model.predict( frame, confself.conf, iouself.iou, imgsz640, verboseFalse ) boxes results[0].boxes for b in boxes: x1, y1, x2, y2 map(int, b.xyxy[0].tolist()) cls_id int(b.cls[0]) label results[0].names[cls_id] cv2.rectangle(frame, (x1, y1), (x2, y2), (0, 255, 0), 2) cv2.putText(frame, label, (x1, y1 - 10), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0, 255, 0), 2) # 把画好框的帧发回主线程主线程只负责显示 self.frame_ready.emit(frame, results) self.msleep(1) # 让出 CPU避免子线程占满 def stop(self): self.running False self.wait()这段代码有几个关键参数。frame_ready用object而不是QImage或QPixmap是为了避免大图像在信号传递时反复转换格式直接把numpy数组emit过去主线程收到后再转QImage显示msleep(1)让子线程每帧之间稍微让出事件循环防止CPU被占满stop()里先置running为False再调wait()等待线程真正结束否则程序退出时线程还挂在后台窗口关了进程却不退。主线程这边的槽函数收到frame_ready信号后把BGR转RGB再转QImage然后setPixmap显示。不要在槽函数里再做任何耗时操作显示完就返回。def update_frame(self, frame, results): rgb cv2.cvtColor(frame, cv2.COLOR_BGR2RGB) h, w, ch rgb.shape qimg QImage(rgb.data, w, h, ch * w, QImage.Format_RGB888) pixmap QPixmap.fromImage(qimg) self.label.setPixmap(pixmap.scaled( self.label.size(), Qt.KeepAspectRatio, Qt.SmoothTransformation))这里最需要注意的是QImage的bytesPerLine参数也就是代码里的ch * w。如果这个参数不写对图像显示出来会斜着撕裂这是QImage最常见的坑。另外rgb.data指向的numpy数组可能在函数结束后被回收所以QImage最好用copy()持有数据或者保证frame_ready信号的帧生命周期足够长。我在实际项目里遇到过偶发花屏最后就是加了一层copy()解决的。4.3 图片、视频、相机三路输入cv2与QImage的格式衔接界面要支持三种输入单张图片、视频文件、USB相机或工业相机。图片最简单QFileDialog选图后cv2.imread读进来直接交给Worker推理一次结果帧显示出来。视频和相机是连续的帧流需要起一个循环读帧的线程把每帧交给推理。视频文件用cv2.VideoCapture(path)打开相机的区别只是入参不同USB相机用cv2.VideoCapture(0)或设备索引号工业相机如果用的是海康MVS SDKSDK的回调线程里会推帧注意不要在SDK回调里直接做模型推理回调里只做帧转发把帧放到队列里再由推理线程取走。这样做的原因是SDK回调线程的优先级和时序不受我们控制直接在里面推理会导致帧率不稳、推理耗时忽高忽低。格式衔接上整个流程里要统一用BGR顺序cv2读出来是BGR模型内部会做RGB转换画框时又回到BGR最后界面显示时一次性转RGB。不要在中间环节反复转换转一次是必要的转多了颜色会偏而且白费CPU。如果发现检测框位置对但颜色明显不对先怀疑是不是哪一步多转了一次RGB。5. 避坑钢材缺陷检测从训练到上界面最常见的5个问题5.1 现象损失函数画出来像心电图mAP卡在0.3训练几十个epoch后loss曲线忽高忽低mAP50始终在0.3附近上不去。原因通常不是网络结构问题而是标注数据出了问题。我见过最多的是两种情况一是XML转txt时坐标没有除以图像宽高txt里出现了大于1的坐标二是类别id和names顺序对不上模型一直用“错误的标签”在学习。解决先打开一张训练图的txt看内容第一列必须是0~5的整数后四列必须是0~1的小数。再把txt画回原图可视化确认框的位置贴不贴目标。如果是坐标问题重新跑转换脚本如果是类别顺序问题把data.yaml的names顺序和转换脚本CLASSES顺序统一然后重新训练。这一步检查最多花10分钟比在参数上瞎调半天值多了。5.2 现象斑块和裂纹总被模型混成一类验证集上这两类的precision很低原因crazing和patches在纹理上都是碎块状200×200原图里人眼都容易看花加上两类缺陷的形状、灰度分布高度重叠模型很难用浅层特征区分。解决从三个方向入手。第一给这两类增加数据增强强度用copy-paste把少数类目标复制到其他图上增加样本多样性第二模型换大一点yolov8s或yolov8m的深层特征能学到更多纹理差异比yolov8n效果明显第三如果还是不行考虑用crop切块训练把原图放大后再切块让模型看到缺陷的局部细节。这个问题的本质是类别相似度太高不要指望调loss函数能解决数据和模型容量才是关键。5.3 现象GTX1660Ti训练时显存不足OOM训练命令刚跑起来就报RuntimeError: CUDA out of memory。原因很直白batch太大、输入尺寸太大、模型参数量超出6G显存。有的项目组为了追求精度直接上yolov8x1660Ti根本带不动。解决按优先级调参先把batch降到8再不行把imgsz从640降到512模型从yolov8s降到yolov8n。ampTrue一定要开着混合精度能省下大量显存。还可以把workers降到4以下减少数据加载时额外占用的显存。NEU-DET原图只有200×200imgsz512对这个小数据集完全够用没必要死守640。5.4 现象Qt界面一推理就转圈卡死点“开始检测”按钮后窗口立刻无响应鼠标变成转圈状态标题栏显示“未响应”。原因是推理代码直接写在按钮的槽函数里阻塞了Qt的事件循环界面刷新、鼠标事件全部排队等待看起来就是卡死。解决把推理逻辑全部搬进QThread子线程通过信号把结果发回主线程。注意两个细节子线程里不能直接操作任何UI控件包括setText、setPixmap都不行否则会报“QObject::setParent: Cannot set parent, new parent is in a different thread”这类错误程序关闭时先调用worker.stop()把线程停下来再关闭窗口不然Qt会报“QThread: Destroyed while thread is still running”严重时程序退出后进程还在后台占着GPU。5.5 现象转ONNX后精度掉、检测框偏移best.pt用着没问题导出ONNX后精度掉了好几个点框的位置明显偏移。常见原因是导出时的输入尺寸和训练时不统一或者导出后预处理归一化方式和PyTorch不一致。YOLOv8训练和推理默认都做letterbox图像会填充成正方形导出后推理时漏了letterbox步骤框自然全部偏掉。解决导出和推理时imgsz必须一致导出命令里写了640推理时也要640推理前做同样的letterbox填充推理后把坐标按填充比例映射回原图导出后用onnxruntime跑同一张图对照PyTorch的输出先确认输出一致再去转RKNN。这一步是部署前的最后一道保险别跳过。6. 把best.pt部署到RK3588INT8量化与同一组图的精度验证6.1 pt转onnx再转rknn核心代码与量化参数模型在PC上能跑只是第一步真正上线往往要部署到边缘设备。RK3588是工业视觉项目里很常见的载体它的NPU能跑INT8量化模型部署路线固定为pt→onnx→rknn三步。导出onnx用ultralytics自带命令yolo export modelbest.pt formatonnx imgsz640然后用rknn-toolkit2转rknn量化这一步有几个参数决定模型最终效果。from rknn.api import RKNN rknn RKNN() # mean/std 必须是模型导出时的预处理参数YOLOv8 默认归一化到 0~1 rknn.config(mean_values[[0, 0, 0]], std_values[[255, 255, 255]], target_platformrk3588) rknn.load_onnx(modelbest.onnx) rknn.build(do_quantizationTrue, datasetquant_dataset.txt) rknn.export_rknn(best.rknn)量化时的dataset.txt里每行写一张图片路径我一般从验证集里抽200张覆盖六类缺陷的图。量化数据集太少模型容易“偏科”对没见过的缺陷表现很差太多也没必要几百张足够校准激活值的分布。mean和std这里用的0和255和YOLOv8默认的归一化一致如果自己改过预处理这里必须同步改否则量化后输出全是错的。6.2 验证方法用同一组图对比PyTorch与RKNN的检出量化完成后拿同一组图片分别跑PyTorch模型和RKNN模型对比检测框和置信度差异。这个对比不能只看一两张我习惯挑30张图要求六类缺陷每类至少出现5次统计量化前后每类的mAP50差异。差异在3个点以内属于正常范围超过5个点就要检查是否量化数据集和模型预处理不一致。RKNN的输入是RGB顺序而cv2读出来是BGR这个顺序搞反了检测出来的置信度会明显偏低。部署代码里记得先做cv2.cvtColor转为RGB再做letterbox最后送入NPU输出结果解析时按“中心坐标宽高”还原成框再映射回原图。这一步和之前ONNX推理的坐标映射逻辑完全一致可以直接复用。6.3 我的验收习惯项目做完后做性能验收时我会同时记录三组数据单帧推理耗时、CPU占用、画面在界面上的显示延迟。只盯推理耗时不够——有时候推理只要30毫秒但画框、转图像、信号传递加一起多花了50毫秒用户体感就是卡顿。我的经验是先在界面上用单张图片跑通全流程确认显示、表格、阈值调节都正常再接视频流最后再换相机每一步都单独验证别一上来就全接上出了问题都不知道该看哪一层。我现在做新项目第一步永远是先锁数据集和类别顺序再碰界面和部署顺序反了后面全是返工。希望帮到你。本文还有配套的精品资源点击获取