首页 / 资讯中心 / 文章详情

基于YOLOv8的热轧带钢表面缺陷检测:从NEU-DET训练到产线部署

基于YOLOv8的热轧带钢表面缺陷检测:从NEU-DET训练到产线部署 ★ FEATURED ARTICLE
简介基于YOLOv8的热轧带钢表面缺陷检测项目面向工业质检、计算机视觉研究者及AI爱好者覆盖横向裂缝、纵向裂缝、块状裂缝、龟裂、坑槽、修补网状裂缝等八类常见缺陷。资源包含完整可运行源码、配套标注数据集及从零开始的详细使用教程从环境配置、预训练权重加载、数据增强策略到模型微调、推理测试与mAP等指标评估均有清晰说明可直接复现一条端到端的缺陷检测流水线。压缩包内共2000个文件以txt标注/说明、md教程文档、py训练与推理脚本、yaml模型配置为主另含cpp/h推理模块、xml标记文件以及前端页面样式整体大小74.49MB目录层次明确便于按需查阅和二次开发。目前已有842人学习适合想快速上手YOLO系列并落地工业检测场景的开发者。1. 为什么热轧带钢表面缺陷检测绕不开 YOLOv8从产线痛点说起热轧带钢以每秒十几米的速度通过精轧机组氧化铁皮压入、麻点、划伤这类表面缺陷在人类可见范围里只有零点几秒的窗口。产线上的质检员盯监控屏连续一两小时后漏检率就肉眼可见地上升而在十年前机器视觉方案靠的是阈值分割和手工特征换一条钢种、换一次光照就要重构一遍算法。最近两年基于yolov8实现热轧带钢表面缺陷检测的源码包、数据集、详细使用教程大量出现——YOLOv8 把数据组织、模型训练、部署导出压缩成一套能复现的流程配合东北大学 NEU-DET 这类公开数据集一个人一个晚上就能训出可用的检测模型。对想做产线试点、课题预研或者毕业设计的同行来说这是投入产出比最稳定的入口。接下来不写公式只写怎么把一套代码从解压到跑通再把模型推进到接近产线可用的状态。2. 吃透数据是第一步NEU-DET 六类缺陷、XML 标注与 YOLO 转换脚本2.1 六类缺陷为什么难目标小、纹理相似、背景干扰NEU-DET 是东北大学公开的热轧带钢表面缺陷数据集采样自现场钢带的 200 像素 × 200 像素灰度图共 1800 张六类缺陷各 300 张。类别名按惯例是这么六个crazing网状纹、inclusion表面夹杂、patches斑块、pitted_surface凹坑麻点、rolled_in_scale氧化铁皮压入、scratches划伤。类别数量不多难点在别处。第一缺陷尺度极其不均匀麻点和网状纹在 200×200 的原图上经常只有几十像素宽放到 YOLOv8 的 640 输入里对应面积可能只有十几个像素主检测头几乎看不出特征。第二类间视觉重叠严重rolled_in_scale 和 patches 都是暗色块状区域crazing 和 scratches 都是线条纹理连人工标注都需要经验模型学起来更吃力。第三带钢表面本身有氧化色和照度不均很多背景纹理与缺陷的灰度差不到 20 个像素值模型稍微过拟合就会把背景学成目标。所以这个项目动手之前先打开每类样本看一遍记录哪类缺陷尺寸小、哪类与背景像。这一步直接决定第 3 章里 imgsz 定 640 还是 960、要不要给特定类别单独设置数据增强。很多人一拿到数据集就开训结果 mAP 不低但可视化结果一塌糊涂回头找原因多半是数据理解这一步省掉了。2.2 把 VOC XML 标注转成 YOLO txt一个 40 行的转换脚本NEU-DET 官方提供的标注是 VOC 风格的 XML 文件每个缺陷用 bndbox 保存左上角和右下角。YOLOv8 要的却是另一套格式每个图片对应一个同名 txt每行一个目标写class_id x_center y_center width height并且全部归一化到 0 到 1。这种格式差异是大部分教程里“训练直接报错”的源头。我自己常用的转换脚本如下import os import xml.etree.ElementTree as ET from glob import glob class_names [crazing, inclusion, patches, pitted_surface, rolled_in_scale, scratches] def xml_to_yolo(xml_path, out_dir): tree ET.parse(xml_path) root tree.getroot() img_w int(root.find(size/width).text) img_h int(root.find(size/height).text) lines [] for obj in root.findall(object): name obj.find(name).text if name not in class_names: print(f[skip] {xml_path} 中出现未知类别: {name}) continue cls_id 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) # 过滤掉非法标注框防止训练时损失算出一个 nan if xmax xmin or ymax ymin: print(f[skip] {xml_path} 的 {name} 宽高为 0 或负数) continue x_center ((xmin xmax) / 2) / img_w y_center ((ymin ymax) / 2) / img_h w (xmax - xmin) / img_w h (ymax - ymin) / img_h lines.append(f{cls_id} {x_center:.6f} {y_center:.6f} {w:.6f} {h:.6f}) txt_path os.path.join(out_dir, os.path.basename(xml_path).replace(.xml, .txt)) with open(txt_path, w) as f: f.write(\n.join(lines)) if __name__ __main__: os.makedirs(labels, exist_okTrue) for xml_file in glob(Annotations/*.xml): xml_to_yolo(xml_file, labels)这段脚本只做四件事读图片宽高、遍历 XML 里的 object、归一化坐标、写出 txt。最容易翻车的有两处。一处是宽高取的是 XML size 节点里的 width 和 height而不是实际图片文件尺寸。如果数据包被别人压缩过、缩放过后没有同步修改 XML那么所有框都会整体偏移训练时不会报错但 mAP 永远涨不上去。另一处是 class_names 的顺序第三方数据集可能按 dict 遍历顺序排列和这里不一致。跑完脚本后抽查三个 txt拿一张图把标签画出来看一眼比训练完再看指标效率高得多。2.3 数据划分与 data.yaml训练前必须做对的三个检查标注转完之后下一步是划分训练集和验证集。对 1800 张这种规模的数据常见做法是 8:2 划分也就是 1440 张训练、360 张验证。划分时要先随机打乱再按比例切而不是按文件名顺序简单取前 80%。原始采集视频往往带有连续性同一个钢卷的几十帧图片内容高度相似如果只按顺序切片验证集和训练集可能混进同一卷钢的相邻帧验证指标会比实际好 3 到 5 个点。这是“数据泄漏”做这个项目时特别容易踩。划分完在数据集根目录放一个 data.yaml。以通用目录结构为例path: ./steel_defect_dataset train: images/train val: images/val names: 0: crazing 1: inclusion 2: patches 3: pitted_surface 4: rolled_in_scale 5: scratches这里的三个检查一个都不能省。第一path 用相对路径时训练命令必须在 data.yaml 所在目录执行否则 ultralytics 会把路径拼错报出 “dataset not found”另一个更稳的写法是把 path 改成绝对路径省掉这种环境差异性。第二train 和 val 目录下的 jpg 与 txt 必须同名一一对应任何图片没有 txt 都会让对应样本训练时被跳过或报错——因为 YOLOv8 对全黑标签的样本会直接丢弃。第三正式训练前先跑一个 epochs1 的冒烟测试或者直接用 model.val() 扫一遍验证集让数据加载逻辑跑通有没有坏图、空标签会在几十秒内暴露。一些拿到源码包的人会问教程里给的 train/val 划分脚本是不是直接可用。常见的划分脚本是遍历所有文件名random.shuffle后按 index 写入两个列表再按列表移动文件。注意脚本里的随机种子和复制模式复制而不是移动文件更安全因为后续调试时不需要重复解压原始包。3. 用 YOLOv8 训练热轧带钢表面缺陷模型环境、参数与损失曲线判读3.1 yolov8 环境配置GTX1660Ti 这类低显存显卡能跑吗环境配置是新手卡关最多的地方。我见过一个项目把 ultralytics、torch、cuda 全装了一遍一跑训练就报 libcudnn 错误最后发现是 pip 自动装了一个 CPU 版 torch。规范的流程是先跑nvidia-smi确认显卡驱动支持的 CUDA 版本再从这个对应版本下载 torch 的预编译 wheel最后装 ultralytics。用 conda 建一个 python 3.10 的独立环境会省掉很多依赖冲突特别是 opencv-python 和 numpy 的版本互相打架时独立环境里重装一次就能隔离问题。GTX1660Ti 是 6 GB 显存属于这个任务里真正能验证“低配能不能跑”的典型。跑 yolov8n 和 yolov8simgsz640batch 8 到 16实测都没有问题如果开了 mosaic、mixup 这类增强显存峰值会高一些稳妥起见 batch 设 8。再往下的 4 GB 笔记本显卡建议开 amp 半精度混合训练同时把 mosaic 概率调低或者干脆用云 GPU 把训练跑完本地只做推理。热轧带钢表面缺陷一共就 6 类yolov8s 在 NEU-DET 上通常就能拿到 90 以上的 mAP50没有必要一开始就上 yolov8l 或 x跑得慢不说小显存还容易直接爆掉。3.2 最小可复现训练命令从官方权重到自己的 6 类模型在数据划分完成、环境装好之后训练这一步其实只剩一条命令。用 python 调用是最好 debug 的from ultralytics import YOLO model YOLO(yolov8s.pt) # 官方预训练权重 model.train( datasteel_defect.yaml, epochs150, imgsz640, batch8, device0, patience20, seed42, workers4, cacheFalse, ampTrue, verboseTrue, )几个参数按这样定。epochs150 在 1800 张图的数据集上足够收敛通常 80 到 120 轮 mAP 已经平稳后面的轮次靠 patience20 自动判断——连续 20 轮验证集指标没有提升就提前结束省时间也防止过拟合。imgsz640 和官方权重一致迁移学习效果最好如果对麻点这类小目标不满意可以改成 960代价是显存翻倍、训练时间拉长。batch8 是 6 GB 显存的安全值在 12 GB 或 24 GB 显卡上可以提到 16 或 32批量越大梯度越稳收敛更快。seed42 固定随机种子是复现的底线。cacheFalse 避免 1800 张图全部缓存进内存导致内存不足如果机器内存超过 32 GB可以开 cacheTrue 加速数据加载。训练命令里的关键逻辑是迁移学习。model 指向官方 yolov8s.pt等于把在 COCO 上学到的纹理、边缘特征搬过来再在这 6 类缺陷上微调。第一次 run 时如果数据集很小、标注又不规范直接从头训练会出现起步 loss 就在 3 以上而且降不下去换成带预训练权重的模型loss 在第一个 epoch 就能到 1 左右这就是为什么大部分教程都建议先加载官方权重而不是用 model YOLO(yolov8s.yaml) 从零构建。3.3 损失曲线、mAP 与混淆矩阵怎么判断训练没翻车训练结束后runs/detect/train/ 下会生成 results.png 和 results.csv。results.png 里 12 个小图重点看 6 个train 和 val 各自的 box_loss、cls_loss、dfl_loss以及最后一个 mAP50 和 mAP50-95。三条损失曲线应该整体下降然后趋平。val 损失在中间开始回升是过拟合的典型信号说明模型开始死记训练集val 损失从第一个 epoch 就反复震荡不下降多半是标签类别索引错误或数据里混了坏图。我自己习惯把 results.csv 拉出来用 pandas 画一张更清楚的损失曲线方便发给团队讨论import pandas as pd import matplotlib.pyplot as plt df pd.read_csv(runs/detect/train/results.csv) df df.rename(columnslambda x: x.strip()) cols [train/box_loss, train/cls_loss, train/dfl_loss] fig, axes plt.subplots(1, 3, figsize(14, 4)) for i, col in enumerate(cols): axes[i].plot(df[col], labeltrain) axes[i].plot(df[col.replace(train/, val/)], labelval) axes[i].set_title(col) axes[i].legend() plt.tight_layout() plt.savefig(loss_curve_2024.png)这段脚本把训练和验证的损失折线画到一起一眼就能看出 gap 有多大。判读时记住一个原则mAP50 对热轧带钢缺陷这种目标占地比例较小的任务是最直接的可用性指标mAP50-95 反映的是框的定位精度而 NEU-DET 的标注框本身偏保守所以 mAP50-95 低一点不用急着换大模型。真正要看的是混淆矩阵里哪一类被误分到哪一类比如 patches 被分到 rolled_in_scale说明这两个类别在特征空间太靠近需要去数据里找类别边界样本。4. 推理、导出与 RK3588 部署从验证集到产线边缘4.1 用 best.pt 做图片与视频流推理置信度阈值和 NMS 怎么调训练完成后实际用的是 runs/detect/train/weights/ 下的 best.pt而不是 last.pt。best.pt 是按验证集综合指标挑出来的最优权重。单张图片和视频流的推理代码几乎一样from ultralytics import YOLO model YOLO(runs/detect/train/weights/best.pt) results model.predict( sourcetest_images, # 可以是图片目录也可以是 RTSP 流 imgsz640, conf0.25, iou0.7, max_det50, saveTrue, projectruns/detect, nameinference, ) for r in results: boxes r.boxes for b in boxes: cls int(b.cls[0]) conf float(b.conf[0]) print(f缺陷类别 {model.names[cls]}置信度 {conf:.2f})conf 是第一个要调的参数。带钢表面缺陷的业务逻辑是“漏检一个可能整卷降级”所以阈值宁愿往低调到 0.2但现场如果误检太频繁导致操作员对报警麻木又得往上加到 0.4。iou 控制 NMS 合并目标框的重叠程度默认 0.7 在这个任务里通常不用动除非同一区域出现大量高度重叠的框可以适当往 0.8 调。max_det50 是为了防止某些图上出现几十上百个碎片框干扰后处理逻辑。视频流推理时把 source 换成 RTSP 地址或本地视频路径框架会逐帧读取实测确认source 用视频时每一帧都会执行一次前向不要在循环里重复加载模型否则帧率和显存都撑不住。4.2 导 ONNX、转 RKNN 的注意点部署不是点一下导出训练好之后要上产线一般不走 python 环境而是先导出 ONNX再转到目标平台的推理引擎。最常见的目标平台之一就是 rk3588因为板载 NPU 和相对便宜的功耗很适合带钢质检这类机边部署。导出 ONNX 的命令是yolo export modelruns/detect/train/weights/best.pt formatonnx imgsz640 opset12这步通常一次成功真正的问题在后面。转向 RKNN 要用 RKNN-Toolkit2基本流程是加载 ONNX、设定输入尺寸推荐固定 640×640、执行量化常见做法是 INT8 量化收集一二百张代表性图片做校准集、生成 rknn 模型。这里有三个坑第一YOLOv8 的 ONNX 里包含一些 shape 相关的算子RKNN-Toolkit2 某些版本会报“不支持”常见处理办法是用脚本把后处理从 ONNX 里剥掉只保留三个输出头的导出NMS 放到板端 CPU 上做第二RK3588 的 NPU 对动态 shape 支持得很勉强导出时就把 imgsz 固定不要指望运行时随意改分辨率第三INT8 量化之后 mAP 会掉一点对小目标缺陷尤其明显所以校准集一定要多收集麻点和网状纹这类小目标样本否则量化后的模型现场几乎不可用。我一般会先在 x86 上把 ONNX 用 onnxruntime 跑一遍完整推理确认输出结果和 pytorch 一致再进 RKNN 转换。这一步能筛掉大部分算子兼容问题免得在板子上反复调试。4.3 缺陷报警逻辑不能靠单帧置信度推理模型输出的是“这一帧有没有缺陷”但产线报警需要处理的是“这 3 秒内是否真的连续出现缺陷”。单帧的随机误检和闪烁在实际产线上会造成大量的虚假报警让操作员最终把报警器关掉。常见的做法是加一个滑动窗口计数from collections import deque frame_queue deque(maxlen30) def update_alarm(frame_id, has_defect): if has_defect: frame_queue.append(frame_id) # 最近 15 帧内至少命中 3 次才触发参数按产线速度和缺陷持续时间调 recent_hits sum(1 for f in frame_queue if frame_id - f 15) if recent_hits 3: alarm_on() else: alarm_off()这个逻辑的要点是窗口按帧数而不是秒数定义因为产线帧率固定时帧数就是时间。如果产线速度很快、缺陷在视野内停留时间短可以把窗口调短、命中次数调小如果误检率高则把命中次数调大。用“最近 15 帧内命中 3 次”这样的组合比单纯 conf 阈值可靠得多。加上一个自锁位报警一旦触发就保持直到人工确认复位是产线项目里非常必要的习惯。5. 避坑与常见问题排查训练时最容易翻车的 5 个现场5.1 loss 变成 nan 或训练一开始就报错现象训练第一个 epoch 的 loss 直接是 nan或者直接报 no labels found。 原因最常见的是 data.yaml 里 train 路径写错导致一张图片都没加载上模型在空数据上挣扎第二是 XML 转换时没有过滤掉非法标注框框宽高为 0 或坐标为负forward 时损失函数算出 nan第三是数据集中有损坏的图片opencv 读完返回 None。 解决把 data.yaml 改成绝对路径先用一个 epoch 冒烟测试转换脚本里加if xmax xmin or ymax ymin: continue遍历全部图片逐个 cv2.imread检查 None 的剔除。这三个问题查完一般第 5 分钟就能定位。5.2 小目标缺陷麻点、网状纹一个都检不出现象整体 mAP 不错但看混淆矩阵时 pitted_surface 和 crazing 的 recall 只有 0.3可视化结果完全没框。 原因这两类在原图里只有几个像素宽的纹理经过 YOLOv8 的 stride 32 特征图之后信息几乎丢失加上它们的纹理模式与带钢背景的光照不均非常接近模型倾向学成背景。 解决优先做数据层面的事——把 imgsz 提到 960等于把小目标在图上放大 1.5 倍再对这两类单独过采样或额外增强更激进一点把小目标图片切块裁剪后作为新样本加入训练。不要一上来就改模型结构加小目标检测头热轧带钢的数据量很小结构改动带来的收益通常不如数据增强。5.3 误检太多背景纹理被框成缺陷现象验证集 mAP 挺好一上现场视频氧化色斑和不均匀光照全被框出来。 原因NEU-DET 是实验室采集的静态小块现场的真实图像在光照、拍摄角度、分辨率上和它差一大截。模型学到的是局部纹理模式而不是“缺陷在整体钢板上的上下文”。验证集和现场数据分布不一致指标自然失真。 解决现场采 200 到 500 张真实缺陷图做随机抽帧、降分辨率、亮度抖动后混入训练集做增量训练推理时把 conf 提高到 0.4 以上限制 max_det直接滤掉低置信度背景块。这个套路和做安全帽检测改进、焊缝表面检测时遇到的问题一模一样本质都是数据分布迁移。5.4 换台机器复现不了同样的代码 mAP 差两个点现象同一份代码、同一份数据在另一台 GPU 上跑结果不一样大家开始怀疑是“玄学”。 原因GPU 算子的非确定性、数据加载线程随机性、没有固定随机种子。 解决先固定 seed42设 torch.backends.cudnn.deterministic 并在训练前设置环境变量。要严格复现实验时把 mosaic 和 mixup 增强关掉再跑一次指标差会明显收敛。这套方法适用于所有需要向客户复现结果的交付场景。5.5 显存不足 CUDA out of memory现象训练刚开始一两分钟就报 CUDA out of memory。 原因batch 和 imgsz 组合超过显存上限六 GB 显卡在 batch16、imgsz640 且开启 mosaic 的情况下已经接近极限。 解决batch 从 8 起步imgsz 定 640开 ampTrue 半精度混合训练显存能省一半关掉 mosaic 或者把它的比例调低峰值内存会大幅下降。如果还撑不住就用小模型 yolov8n。记住训练时的显存压力远比推理大本地用不上就拿云 GPU 跑训练把 best.pt 拉回来本地推理一点问题没有。6. 落地验证的一个技巧用漏检率说话而不是只看 mAP6.1 现场验证看三个指标mAP 是模型研发阶段看的产线验证阶段要看三个数漏检率、误检率、处理帧率。漏检率是现场最不能忍的指标按“应该检测出的缺陷事件数”和“实际检出数”的比值算误检率决定操作员的信任度处理帧率决定模型能不能跟上产线速度。这三个数要先定目标再测比如漏检率低于 2%、误检率低于 5%、帧率超过产线速度的 1.5 倍然后拿一段有代表性的现场视频逐帧回放人工标出所有真实的缺陷事件再让模型跑一遍按事件统计而不是按帧统计。6.2 最后一个习惯先看漏掉的是哪几类如果验证集跑下来漏检率超标不要急着调 conf先把漏检的样本按类别统计一遍。多数情况会发现集中在某一类——比如全是麻点或全是细条纹。这时候最有效的不是改通用参数而是回去看这一类的训练样本量和尺度。我曾经把一个项目从 mAP 98 做到现场可用靠的不是换更大的模型而是把现场视频里 300 个漏检样本全部补进训练集又把 imgsz 从 640 调到 960漏检率从 7% 压到了 1% 以下。这也是我最后想分享的一个习惯任何源代码包和数据包拿到手先花半小时看目录结构确认数据组织、标签格式和教程里的命令对得上再开始跑。热轧带钢表面缺陷检测这个方向从解压源码到现场能跑真正花时间的不是训练是数据理解和阈值调整。希望帮到你。本文还有配套的精品资源点击获取
阅读完成 · 觉得有帮助?
咨询建站