
简介基于YOLOv8的热轧带钢表面缺陷检测完整实践包面向工业视觉初学者、算法工程师及钢铁质检人员聚焦横向裂缝、纵向裂缝、块状裂缝、龟裂、坑槽等八类常见缺陷的自动定位与分类。压缩包共含两千个文件其中txt标注与说明文件最多配合md教程笔记、py训练推理脚本、yaml模型配置及cpp/h等C部署示例整体约74.49MB目录布局清晰便于按模块查阅和二次开发。已有八百四十一人学习特别适合系统掌握YOLOv8在工业质检中的完整落地流程。内含可运行的源码、带标注数据集和详细使用教程覆盖环境搭建、数据预处理、模型训练调参、mAP评估与推理演示等关键环节并整理常见问题与排错思路可帮助读者从零构建检测系统快速迁移到自身产线场景提升质检效率与自动化水平。1. 基于YOLOv8的热轧带钢表面缺陷检测跑通它到底要过哪几关热轧产线上钢坯以每秒十几米的速度穿过精轧机组相机把带钢表面拍成一张张灰度图系统必须在几百毫秒内判断有没有裂纹、麻点、氧化铁皮压入。基于YOLOv8实现热轧带钢表面缺陷检测是目前工业表检里把“算法基线”打起来最常见的做法也是这类“源码数据集详细使用教程”压缩包真正能落地的前提。压缩包把代码、数据和文档都给齐了但实际动手时环境、标签格式、训练参数这三关才是拦路虎模型本身反而是最不花时间的那部分。这套流程适合刚接手钢板表检任务、想先在公开数据集上拿到基线或准备把产线自采数据训练成可用模型的工程师。它能让你最快速度搭起一条可以复现的链路但不会替你解决数据分布不均衡和现场光照问题。2. 为什么缺陷检测选YOLOv8六个缺陷类别、网络结构与现场约束2.1 热轧带钢缺陷长什么样六类缺陷的图像特征热轧带钢表面缺陷检测数据集里最常见的是东北大学公开的NEU表面缺陷数据库很多压缩包里的数据都是它的镜像或在其基础上补充的。这个数据集把缺陷分成六类crazing是细碎裂纹呈龟裂纹路对比度不高边界模糊inclusion是夹杂通常表现为暗色块状和氧化皮容易混淆patches是斑块成片灰度突变边缘不规则受光照影响大pitted_surface是麻点表面像橘子皮或砂纸密集小坑rolled-in_scale是氧化铁皮压入形状不规则的暗斑scratches是划伤呈细长亮线长宽比经常超过10:1。这六类缺陷有个共同点类内差异大类间又有重叠。比如rolled-in_scale和patches在灰度图上非常相似现场工人都有可能看走眼scratches在200x200的小图上就是一条亮线可能横跨整幅画面。如果模型要同时处理“大块斑”和“一条线”对目标的宽高比变化就特别敏感。这一点直接决定了模型选型和训练策略也解释了为什么很多传统视觉方案在这里翻车。2.2 YOLOv8的哪些结构设计正好匹配这类任务YOLOv8和YOLOv5相比最明显的改动是C2f模块、Anchor-Free的解耦头和更精细的多尺度特征融合。对带钢表面缺陷来说前两个改动的收益最直接。Anchor-Free意味着模型不再依赖预先聚类的锚框尺寸。YOLOv5训练前要用K-Means对训练集的框做聚类选出一组先验框这个步骤对长条形的scratches非常不友好当数据里既有方形麻点又有细长划伤时聚类结果会被拉偏小目标召回率掉得厉害。YOLOv8直接回归目标中心坐标和宽高不需要手动调锚框在缺陷这种形状极度不均匀的数据上省了很多事。C2f模块把梯度流做成分叉再合并让浅层特征和深层特征的融合更充分。热轧带钢表面缺陷里crazing和pitted_surface这类小目标依赖浅层细节rolled-in_scale这类大块缺陷依赖深层语义多尺度融合不够时小目标和大目标至少有一个会被牺牲。YOLOv8的解耦头把分类和回归分成两个分支也减少了这两种任务在训练时的冲突。一个常见误区是以为这类项目必须上语义分割其实现场判级通常只要求“哪个位置有什么缺陷、个数多少”目标框已经够用分割带来的标注成本会让数据准备周期翻倍。2.3 与规则视觉、其他检测模型的取舍传统视觉方案在表面缺陷检测里确实还活着但生存空间很窄。阈值分割、形态学处理对固定钢种、固定光照的产线可以稳定工作一旦换钢种、换相机角度、调亮度规则就要重写维护成本很高。Faster R-CNN这类两阶段模型精度不差但训练和部署链路没有YOLOv8顺尤其在导出ONNX、再转边缘端格式这个环节YOLOv8的工具链成熟得多。实际落地时我一般这样走先用YOLOv8在公开数据集上打一个基线确认数据标签没问题再考虑要不要上切图、难样本挖掘或者直接换成更重的主干提升精度。一上来就追求复杂模型只会让数据问题更难定位。3. 环境搭建让源码先动起来CPU版、GPU版和最小推理验证3.1 拿到源码包先做的三件事解压、文件树检查、读教程这类压缩包解压后第一件事不是急着装环境而是把文件树看清楚。我一般会先看三样东西数据集目录、权重目录、教程文档。典型工程结构是这样的project/ ├── datas/ │ ├── images/ # 按 train/val/test 组织 │ ├── labels/ # 与 images 对应的标签目录 │ └── data.yaml # 训练配置 ├── weights/ │ ├── yolov8n.pt # 预训练权重 │ └── best.pt # 训练好的权重 ├── scripts/ │ ├── voc2yolo.py # 数据转换脚本 │ └── check_labels.py # 标签检查脚本 ├── runs/ │ └── detect/ # 训练输出 └── README.md # 教程文档要注意的第一件事整个工程目录路径必须用英文不能带中文和空格。在Windows上OpenCV读取中文路径经常出问题报错还不明显表现为大量图片被Dataloader跳过训练数据量莫名其妙变少。把压缩包解压到D:\steel_detect或/home/xxx/steel_detect这类路径下再开始能省掉后面一半的玄学问题。README或教程文档里通常会写清楚数据格式、训练命令、环境版本要求。如果文档里写的是Python 3.8以上就按Python 3.9或3.10建环境如果写的是“需自行下载预训练权重”就先看看weights目录里有没有。这些信息比任何博客教程都更贴合这个压缩包本身。3.2 Ubuntu 20.04上的CPU版环境一步步装到能用CPU环境看起来是“低配”其实是排查数据问题和跑通流程最稳的起步方式。Ubuntu 20.04上搭建YOLOv8 CPU环境的步骤我常用这套命令conda create -n yolov8 python3.9 -y conda activate yolov8 pip install torch torchvision --index-url https://download.pytorch.org/whl/cpu pip install ultralytics python -c import torch; print(torch.__version__)代码逻辑说明先用conda创建独立环境避免污染系统Python--index-url https://download.pytorch.org/whl/cpu指向PyTorch官方CPU版wheel这个包不包含CUDA运行时体积小安装不会碰显卡驱动。最后一行验证torch是否装好如果输出类似2.x.x就说明安装成功。CPU环境下不要直接跑完整训练推理和查看标签完全没问题。想确认torch是否真的在CPU模式下工作可以补一句python -c import torch; print(torch.cuda.is_available())输出False是预期结果不要慌。如果你后续要换GPU机器代码和数据目录直接搬过去只要重装GPU版torch即可。3.3 GPU版环境先看nvidia-smi再决定torch wheelGPU机器上装环境第一步永远是先看驱动而不是先装CUDA toolkit。在终端执行nvidia-smi右上角的“CUDA Version”告诉你当前驱动支持的最高CUDA版本。比如显示12.1就选择对应cu121的torch wheelnvidia-smi pip install torch torchvision --index-url https://download.pytorch.org/whl/cu121 pip install ultralytics参数说明cu121表示CUDA 12.1PyTorch的wheel里自带CUDA runtime不需要系统里额外装完整的CUDA toolkit只要驱动版本不落后就能用。如果安装过程中出现“no matching distribution”或编译报错通常是驱动太老把cu121换成cu118再试。装完后验证方式同样是torch.cuda.is_available()输出True才算GPU环境真正生效。有两点值得提醒一是不要先装ultralytics再装torch那样pip可能把默认的CPU版torch拉进来回头还得重装二是如果显存只有4G或6G训练时建议直接用yolov8n模型起步别一上来就加载yolov8l后面第5章会详细说显存问题。3.4 最小验证用yolov8n跑一次推理环境装好后先别急着训练。从数据集里挑一张缺陷图跑一次推理确认整个推理链路是通的yolo predict modelweights/yolov8n.pt sourcedatas/images/train/xxx.jpg saveTrue这条命令的含义modelweights/yolov8n.pt指定使用压缩包自带或你下载的预训练权重source指向一张测试图片saveTrue表示保存可视化结果。如果weights目录里没有权重文件命令会自动联网下载yolov8n.pt网络慢时可以先手动下载再放到weights目录。推理结束后结果会输出到runs/detect/predict目录里面是画了框的图片。如果这一步跑通说明“图片读取→模型加载→推理→结果保存”这条链路是完整的后面训练只是换权重而已。很多人的训练失败其实在推理这一步就能提前暴露比如图片路径读不到、模型文件损坏、opencv版本不对这些都会被一条predict命令炸出来。4. 数据集整理与训练复现VOC转YOLO、标签检查、data.yaml与训练参数4.1 数据集形态NEU-DET或产线自采先统一标注格式压缩包里带的数据集来源无非两种公开的NEU-DET或者你自己在产线上用labelme、LabelImg标注的数据。NEU-DET是1800张灰度图每类缺陷300张图像尺寸固定为200x200在多数复现包里以Pascal VOC的XML格式或YOLO的txt格式提供。自采数据则是JSON格式居多图像尺寸和缺陷形态都更接近真实产线。无论原始格式是什么训练前都要统一成YOLO的txt格式并按下面的目录结构摆放datas/ ├── images/ │ ├── train/ │ ├── val/ │ └── test/ ├── labels/ │ ├── train/ │ ├── val/ │ └── test/ └── data.yaml这个结构是ultralytics默认读取方式图片在images/train对应的标签在labels/train文件名必须完全一致只有扩展名不同。如果压缩包里给的是Annotations和JPEGImages两个平铺目录就需要先做格式转换和目录划分。图像格式本身不用强求jpgpng、bmp都能读但注意全数据集统一不要混用。4.2 VOC XML转YOLO txt脚本、坐标归一化与边界保护VOC转YOLO是数据准备里最常见的操作也是踩坑重灾区。下面这段脚本可以直接存成voc2yolo.py使用import os import xml.etree.ElementTree as ET CLASSES [crazing, inclusion, patches, pitted_surface, rolled-in_scale, scratches] def convert(xml_path, out_dir): tree ET.parse(xml_path) root tree.getroot() w int(root.find(size/width).text) h int(root.find(size/height).text) stem os.path.splitext(os.path.basename(xml_path))[0] lines [] for obj in root.iter(object): name obj.find(name).text.strip() if name not in CLASSES: continue # 跳过不在类别清单里的标签避免后续类别id越界 box obj.find(bndbox) x1 float(box.find(xmin).text) y1 float(box.find(ymin).text) x2 float(box.find(xmax).text) y2 float(box.find(ymax).text) x1 max(0, min(x1, w)) y1 max(0, min(y1, h)) x2 max(0, min(x2, w)) y2 max(0, min(y2, h)) if x2 - x1 0 or y2 - y1 0: continue # 过滤掉宽高为零的无效框 xc ((x1 x2) / 2) / w yc ((y1 y2) / 2) / h bw (x2 - x1) / w bh (y2 - y1) / h lines.append(f{CLASSES.index(name)} {xc:.5f} {yc:.5f} {bw:.5f} {bh:.5f}) with open(os.path.join(out_dir, stem .txt), w) as f: f.write(\n.join(lines))脚本逻辑说明CLASSES列表的顺序就是后面data.yaml里names的顺序这个顺序必须是全项目唯一的“标准答案”。XML里读取的xmin/xmax是像素坐标要除以图像宽高归一化到0~1之间YOLO格式只认归一化坐标不认像素坐标。边界保护那几行max/min是为了防止标注框超出图像边界标注软件偶尔会产出这种越界框不处理的话训练时会出现负坐标或大于1的坐标轻则警告重则训练过程数值异常。CLASSES.index(name)这行是关键它把XML里的类名映射成数字id而不是硬编码if name crazing: id0。这样当类别清单调整时只需要改最上面一行不容易漏改。4.3 标签检查与数据划分70/20/10拆分与实例数统计标签转换完先别急着训练跑一遍标签检查脚本import os from collections import Counter label_root labels cls_count Counter() empty_files [] bad_files [] for root, _, files in os.walk(label_root): for f in files: if not f.endswith(.txt): continue path os.path.join(root, f) with open(path) as fh: lines [ln.strip() for ln in fh if ln.strip()] if not lines: empty_files.append(path) continue for line in lines: parts line.split() if len(parts) ! 5: bad_files.append((path, line)) continue try: cls int(parts[0]) vals [float(v) for v in parts[1:]] except ValueError: bad_files.append((path, line)) continue if not all(0.0 v 1.0 for v in vals): bad_files.append((path, line)) continue cls_count[cls] 1 print(每类实例数:, dict(cls_count)) print(空标签文件数:, len(empty_files)) print(异常行数:, len(bad_files))这个脚本做三件事统计每一类的目标实例数、检查空标签文件、检查格式异常行。每类实例数低于几十个就要警惕模型大概率学不好这个类异常行通常是坐标用了逗号分隔或类别id越界训练时会直接报错或静默忽略。划分数据集时我用70/20/10的比例并固定随机种子保证可复现import os, random, shutil random.seed(42) all_imgs [f for f in os.listdir(images/all) if f.lower().endswith(.jpg)] random.shuffle(all_imgs) n len(all_imgs) train, val, test all_imgs[:int(n*0.7)], all_imgs[int(n*0.7):int(n*0.9)], all_imgs[int(n*0.9):] for name, lst in [(train, train), (val, val), (test, test)]: os.makedirs(fimages/{name}, exist_okTrue) os.makedirs(flabels/{name}, exist_okTrue) for img in lst: stem os.path.splitext(img)[0] shutil.copy(fimages/all/{img}, fimages/{name}/{img}) lbl flabels/all/{stem}.txt if os.path.exists(lbl): shutil.copy(lbl, flabels/{name}/{stem}.txt)划分脚本的核心约束是“图像和标签必须同步走”只拷图像不拷标签训练时会大量报警只拷标签不拷图像训练数据直接少一批。如果数据来自产线按时间连续拍摄的同一卷钢不要简单随机划分而应该按时间段或卷号划分否则同一卷钢的相邻画面会同时出现在训练集和验证集mAP虚高到0.9以上上线立刻现原形。4.4 data.yaml的写法路径、类别顺序与常见冲突训练配置文件data.yaml是整个流程的“交通枢纽”什么都能错这里不能错path: /home/yourname/steel_detect/datas # 数据集根目录建议写绝对路径 train: images/train val: images/val test: images/test names: 0: crazing 1: inclusion 2: patches 3: pitted_surface 4: rolled-in_scale 5: scratches这里最容易翻车的是路径拼接逻辑ultralytics会把path和train拼在一起组成最终目录。如果path写成相对路径它相对于的是你执行命令时所在的终端目录不是yaml文件所在目录。同一个yaml在工程根目录下执行没问题切到runs目录下执行就找不到数据。我习惯直接写绝对路径换机器时改一处就行。另一个高频错误是names顺序和前面脚本里CLASSES顺序对不上。如果转换脚本里0号是crazingyaml里0号却写了patches模型训练出来等于标签串位mAP直接崩。还有一个小细节yaml文件不能用Tab缩进必须用空格否则解析直接报错。4.5 训练命令与核心参数从预训练权重到损失曲线数据集就位后训练命令长这样yolo detect train \ datadatas/data.yaml \ modelyolov8s.pt \ epochs120 \ batch16 \ imgsz640 \ workers4 \ cacheTrue \ ampTrue \ patience30 \ projectruns \ namesteel_v1参数选择说明参数作用我的默认值data数据集yaml路径datas/data.yamlmodel预训练权重或模型结构yamlyolov8s.ptepochs最大训练轮数120batch每批次图片数按显存调16/8/4imgsz输入图像缩放尺寸640workers数据加载进程数Linux用4Windows用0cache是否缓存数据到内存小数据集开Trueamp混合精度训练GPU上开Truepatience早停轮数30project/name输出目录runs/steel_v1modelyolov8s.pt表示加载COCO预训练权重并微调。如果你不想用预训练权重改成modelyolov8s.yaml即可从随机初始化开始训练。带钢缺陷和自然图像差异很大但预训练权重里的浅层特征仍然有用我建议保留。NEU-DET是200x200的小图imgsz640会先把图放大再喂给模型放大后缺陷细节会有插值模糊但这是兼容多尺度训练的常见做法。前期调试时可以用imgsz416加快迭代等参数调得差不多了再回到640。训练过程中判断是否正常最直接的方式是看损失曲线。ultralytics每一轮都会把指标写入runs/detect/steel_v1/results.csv用下面这段代码画出来import pandas as pd import matplotlib.pyplot as plt df pd.read_csv(runs/detect/steel_v1/results.csv) print(df.columns) # 不同小版本列名略有差异先打印确认 plt.plot(df[train/box_loss], labeltrain/box_loss) plt.plot(df[val/box_loss], labelval/box_loss) plt.legend() plt.show()正常状态是train和val的box_loss、cls_loss同步下降最后趋于平缓。如果train一路下降、val掉头上涨就是过拟合参考下一章的排查方法。训练结束后runs/detect/steel_v1/weights/下会生成best.pt和last.ptbest按验证集mAP保存后续推理和部署都只用best.pt。5. 避坑与常见问题排查空标签、显存溢出、过拟合与现场漏检5.1 训练结束mAP全是0先查标签目录和格式现象训练能正常启动loss也在下降但验证集mAP一直是0预测图里一个框都没有。原因最常出现在标签文件上。要么是labels/val目录是空的要么是txt里类别id超出names范围要么是转换脚本把标签全部写成了空文件。还有一种隐蔽情况标签目录结构写成了labels/train/train/xxx.txt多套了一层目录ultralytics按约定路径读不到。解决先运行4.3节的标签检查脚本把“每类实例数”打出来。如果val目录下标签数是0说明划分脚本没把标签同步过去。如果异常行数很多优先看是不是逗号分隔或者坐标越界。把标签文件按训练脚本的目录结构重新放好再重新训练。5.2 显存溢出与DataLoader卡死降batch、缓存和workers现象GPU机器上训练刚开始就报CUDA out of memoryWindows机器上则更容易遇到“An attempt has been made to start a new process before the current process has finished its bootstrapping phase”然后程序卡死。原因显存溢出是batch、imgsz和模型大小共同决定的。很多人拿着默认batch16就直接训yolov8s在6G显存的卡上必然爆。Windows的DataLoader报错则是PyTorch在Windows下使用多进程数据加载时spawn机制和ultralytics不兼容造成的跟显存无关。解决显存不足就按16→8→4的顺序降batchimgsz从640降到416也能省很多显存或者直接换yolov8n模型。Windows的烧脑问题更简单训练命令里加workers0强制数据加载在主进程内完成问题立刻消失。如果显存碎片严重可以设置环境变量PYTORCH_CUDA_ALLOC_CONFexpandable_segments:True再启动训练这个变量能让显存按需扩展对多次训练后显存碎片化的情况有一定缓解。5.3 训练loss一路下降、验证mAP不涨过拟合与mosaic开关现象train/box_loss曲线很漂亮一路走低val/box_loss在40轮左右开始反弹val/mAP50停在某个值附近震荡上不去。原因NEU这类小数据集总共才1800张图模型在训练后期把背景纹理和噪声背下来了却没有学到真正的缺陷模式。ultralytics默认开启mosaic数据增强把四张图拼成一张训练。对自然场景目标检测这招很好用但对热轧带钢这种背景灰度分布高度一致的图拼接出来的样本上下文是断裂的模型反而学到了虚假的拼接边界特征。解决训练命令里加两个参数mosaic0.0 close_mosaic0关闭拼接增强。同时把epochs从120收到100patience从30收到20给早停更多干预机会。如果某个类别实例数特别少可以在训练命令里追加class参数调整类别损失权重比如实例数稀少的类权重给1.2~1.5。不要盲目堆epochs小数据集训练超过150轮几乎必然过拟合。5.4 图片加载失败、找不到文件中文路径与path拼接问题现象训练日志里出现大量WARNING: image ... doesnt exist然后训练集实际参与训练的图片数量少了几百张。模型还能训练但mAP上不去。原因Windows下项目路径带中文OpenCV读文件时编码处理失败图片被当成不存在或者data.yaml里path写的是相对路径而执行训练命令的目录不在工程根目录拼出来的路径不对。解决整个工程目录改成纯英文路径一个中文、空格都不要有。如果压缩包下载在C:\Users\张三\Downloads先整体移动到D:\steel_detect再解压。data.yaml里的path直接写绝对路径不要赌当前工作目录。这两个问题在现场特别常见因为现场工控机的系统用户名经常是中文。5.5 离线测试还行、上线漏检灰度域差异与现场数据回流现象在压缩包附带的数据集上验证mAP50高达0.93看起来一切完美。部署到产线后现场真实图片漏检严重尤其是细划伤和麻点。原因公开数据集里的图片是经过筛选和中心裁剪的“特写”缺陷占画面比例大背景干净。现场相机拍的是整幅带钢表面缺陷在画面里可能只有几十个像素且伴随水渍、氧化皮、光照不均等干扰。模型在公开数据集的分布上学到的特征到了现场分布就失效了。解决上线前先收集现场图片人工挑出100~200张有明显缺陷的图用labelme标注后合并进数据集重新训练。训练时用runs/detect/steel_v1/weights/best.pt作为起点只训30~50轮让模型在保持已有能力的基础上适应现场数据分布。如果现场图分辨率很高缺陷又小要配合下一章的切图策略而不是直接整图送进模型。6. 结果验证、导出部署与进阶调优从best.pt到边缘端推理6.1 用val模式看mAP50、mAP50-95和混淆矩阵训练结束后验证命令不是用predict看图而是用val模式算指标yolo detect val \ modelruns/detect/steel_v1/weights/best.pt \ datadatas/data.yaml输出里重点看两个数mAP50和mAP50-95。mAP50对缺陷检测这类小目标任务更友好0.8以上算有可用性mAP50-95更严格如果它明显低于mAP50说明模型框的位置精度不足画出来的框和真实缺陷贴合度差。验证结束后去runs/detect/val目录打开confusion_matrix.png这张图能告诉你哪些类别互相混淆。常见情况是patches和rolled-in_scale混在一起scratches漏检严重。看到混淆矩阵再决定要不要补数据比对着loss曲线猜靠谱得多。6.2 导出ONNX并落到边缘盒子一张图看完整链路表检项目最终很少跑在训练用的GPU服务器上现场常见的是部署到边缘计算单元。导出ONNX是第一步from ultralytics import YOLO model YOLO(runs/detect/steel_v1/weights/best.pt) model.export(formatonnx, imgsz640)导出成功后同目录下会生成best.onnx。这个ONNX文件包含了完整的检测输出但不包含NMS后续的推理框架需要在后处理里自己实现非极大值抑制。如果是rk3588这类边缘算力盒子通常要借助对应的模型转换工具把ONNX转成硬件推理格式转换时要用一小批现场代表性图片做校准否则量化后精度可能明显下滑。转换完成后用现场测试集复查一次mAP这一步不能省。6.3 给数据提纯的三个进阶技巧难样本、切图、模型梯度难样本挖掘是最值得投入的进阶手段。把验证集里漏检的图收集出来人工复核后补上漏标的框合并进训练集重新划分、重新训练。这种方式对mAP的提升往往比把epochs从100加到200更明显。切图适用于现场大图小缺陷场景。把4000x3000的全幅图按640的窗口滑窗切割窗口之间留50~100像素重叠对每个patch单独推理再把结果映射回原图坐标做NMS合并。这样小缺陷在模型输入里占的像素比例能提升好几倍recall通常有明显改善。模型选型上我建议按yolov8n→yolov8s→yolov8m的阶梯做对比而不是一步到位用最大模型。对NEU这种小数据集yolov8s通常已经够用换yolov8m能提几个点的mAP但推理时间可能翻倍。先用小模型跑通整个流程再根据现场算力预算决定是否升级。我自己的习惯是接到这类表面缺陷项目时先花半小时把标签统计清楚确认每类实例数和现场分布然后才开训练。凡是跳过我这一步直接调参的项目最后基本都回来补数据了。希望帮到你。本文还有配套的精品资源点击获取