简介:基于YOLOv8的热轧带钢表面缺陷检测项目,面向工业质检工程师、计算机视觉学习者与算法研究者,提供一套从数据准备、模型训练、性能评估到推理部署的完整解决方案。数据集包含横向裂缝、纵向裂缝、块状裂缝、龟裂、坑槽等典型缺陷的标注图像,结合源码可快速复现训练与检测流程。压缩包内共2000个文件,其中1808个txt文件存储缺陷标注框信息,14个Python脚本实现训练、验证与推理逻辑,161个Markdown文档分步讲解环境搭建、参数配置、数据增强和mAP评估等核心内容,另有YAML模型配置、C++辅助部署程序及HTML说明页面,整体大小74.49MB,目录结构清晰便于查找。目前已有841人学习使用,教程中给出的依赖安装、调参策略和常见排错方法能大大降低入门门槛,既适合高校毕业设计,也可作为企业产线质检方案的技术参考。
1. 热轧带钢表面缺陷检测不是通用目标检测,yolov8要解决的是产线上持续跑的问题
热轧带钢表面缺陷检测与普通目标检测有个明显差别:你面对的是一张宽幅的金属表面图像,上面有氧化铁皮、水渍、光照不均带来的反光,而缺陷本身可能只有几十个像素,比如麻点、划伤、裂纹和夹杂,它们之间形态接近,又存在严重的类别不平衡。很多人第一次拿yolov8官方权重去跑热轧带钢图片,结果漏检一片,原因不是模型不行,而是数据集组织、标注方式和训练参数都不适配工业现场。这篇文章沿一条可落地的路线走一遍:从缺陷类别定义和标注转换,到ubuntu20.04搭建yolov8环境并训练自己的数据集,再到用混淆矩阵和损失函数曲线调优,最后给出一个在边缘设备上部署时特别好用的瓦片切分技巧。适合刚接触yolov8但手里只有现场图片的工程师,也适合需要把缺陷检测模型从实验推到产线的人员。
2. 构建热轧带钢缺陷数据集:从标注格式到yolov8能直接训练的数据组织
2.1 热轧带钢表面缺陷的类别定义与常见标注体系
做热轧带钢表面缺陷检测,第一步不是选模型,而是把类别定义清楚。业界用得比较多的是东北大学的NEU-DET公开数据集,它把热轧带钢表面缺陷分成六类,这六类基本覆盖了产线上最常见的缺陷形态:crazing(裂纹)、inclusion(夹杂)、patches(麻点)、pitted_surface(氧化铁皮压入)、rolled_in_scale(氧化铁皮)、scratches(划伤)。实际现场采集的数据可能更多,比如还有辊印、擦伤、压痕等,但在模型起步阶段,维护六至八个类别就够了,类别太多反而会让相互之间形态接近的缺陷更难区分。
下表是这六类缺陷在训练时常用的class_id和视觉特征。标注属性参考NEU-DET的风格,但没有必要完全照抄,如果你有自己的现场数据,可以按这个结构扩展。
| class_id | 名称 | 常见成因 | 视觉特征 |
|---|---|---|---|
| 0 | crazing | 热应力导致的表面裂纹 | 细小网状或线状纹理,对比度低 |
| 1 | inclusion | 杂质卷入基体 | 点状或短线状暗斑,边界不规则 |
| 2 | patches | 表面局部腐蚀或氧化 | 片状暗色区域,灰度不均匀 |
| 3 | pitted_surface | 氧化皮压入后剥落 | 密集凹坑,呈点状集群 |
| 4 | scratched | 机械刮擦 | 连续直线或曲线亮痕,方向性强 |
| 5 | rolled_in_scale | 氧化铁皮压入表面 | 大块暗色压入物,形状不规则 |
这里有个容易踩的细节:NEU-DET中crazing和patches、pitted_surface这三类的特征在低分辨率下非常相似,如果你用默认imgsz=640训练,小缺陷很容易被当作背景滤掉。所以在数据准备阶段,不要急着把所有图片都缩小到统一尺寸,而应保留原始分辨率的标注信息,训练时再通过imgsz参数控制输入大小。
2.2 用labelme标注后转换成yolo格式
现场采集的热轧带钢图像通常没有现成标注,一般的做法是用labelme画矩形框或任意多边形,然后导出JSON文件。yolov8训练时并不直接吃JSON或XML,它要求每张图片对应一个同名的txt文件,每行内容为“class_id x_center y_center width height”,并且所有坐标值必须归一化到0到1之间。
下面这段代码可以把labelme导出的JSON批量转换成yolo格式的txt文件。
import json import os def labelme_json_to_yolo(json_path, out_dir, class_names): with open(json_path, 'r', encoding='utf-8') as f: data = json.load(f) img_w = data['imageWidth'] img_h = data['imageHeight'] out_lines = [] for shape in data['shapes']: label = shape['label'] if label not in class_names: continue cls_id = class_names.index(label) points = shape['points'] # labelme的矩形框是两点坐标,多边形则取最小外接矩形 xs = [p[0] for p in points] ys = [p[1] for p in points] xmin, xmax = min(xs), max(xs) ymin, ymax = min(ys), max(ys) x_center = (xmin + xmax) / 2 / img_w y_center = (ymin + ymax) / 2 / img_h w = (xmax - xmin) / img_w h = (ymax - ymin) / img_h out_lines.append(f"{cls_id} {x_center:.6f} {y_center:.6f} {w:.6f} {h:.6f}") if out_lines: txt_name = os.path.splitext(os.path.basename(json_path))[0] + '.txt' with open(os.path.join(out_dir, txt_name), 'w') as f: f.write('\n'.join(out_lines)) if __name__ == '__main__': class_names = ['crazing', 'inclusion', 'patches', 'pitted_surface', 'scratched', 'rolled_in_scale'] json_root = 'data/labelme_annotations' out_root = 'data/yolo_labels' os.makedirs(out_root, exist_ok=True) for json_file in os.listdir(json_root): if json_file.endswith('.json'): labelme_json_to_yolo( os.path.join(json_root, json_file), out_root, class_names )这段代码先读入JSON中的图片宽高,然后遍历每个标注形状。labelme的矩形框通常只有两个点,但手工标注时可能画出多边形,所以代码里统一取所有点的最小外接矩形。输出时x_center和y_center都除以图片宽高归一化,这样不管训练时imgsz设多少,标注都能正确映射。需要留意的是,如果原始图片分辨率很大,比如5000像素宽,标注框的x_center在归一化后会变成一个很小的浮点数,写txt时保留6位小数够用,但建议不要小于6位,否则坐标精度损失会让小目标的定位偏差变大。
转换完成后,把整个数据集组织成yolov8期望的目录结构,train和val两个子集建议按图片数量比例8:2切分,同时保证每个类别在两个子集中都有分布。
datasets/ steel/ images/ train/ val/ labels/ train/ val/2.3 处理数据集用于yolov8训练时的增强参数选择
数据增强对热轧带钢表面缺陷检测的影响比其他场景更明显。yolov8默认开启mosaic增强,它会把四张图拼接成一张,这对小目标检测通常是好事,但有一个坑:当缺陷本身很小时,mosaic拼接会把缺陷缩得更小,导致模型学不到足够的纹理细节。我一般建议训练前期用mosaic,训练最后十到二十个epoch自动关闭。ultralytics官方配置里已经做了这个策略,但你也可以直接关闭,在训练参数里写mosaic=0.0。
对于热轧带钢这类表面纹理比较一致的图像,水平翻转可以开,垂直翻转要谨慎。如果产线图像的拍摄方向固定,带钢在画面中不会出现上下颠倒的情况,那flipud建议设为0。hsv增强和灰度增强可以适当调大,因为现场光照变化大,金属表面反光会改变局部灰度分布,增强这部分鲁棒性比增加图片数量更划算。常见做法是hsv_h=0.015, hsv_s=0.7, hsv_v=0.4,这是yolov8默认值,但如果你的数据集中反光样本很少,可以把hsv_v提高到0.6。
3. 环境配置与训练那条最短路径:yolov8最小命令从装环境到出模型
3.1 ubuntu20.04搭建yolov8环境的cpu与GPU两种选择
如果你手头没有NVIDIA显卡,用CPU也能把yolov8跑起来,只是训练时间长一些。先创建一个干净的conda环境,Python版本选3.10或3.11都可以,yolov8官方支持这两个版本。装依赖时不用手动去装PyTorch,直接pip install ultralytics会把配套的环境带起来。下面这组命令在ubuntu20.04上验证过,步骤很简单。
conda create -n yolov8 python=3.10 -y conda activate yolov8 pip install ultralytics安装完成后用下面的命令验证环境是否正常。如果你只有CPU,这个导入操作会自动下载torch的CPU版本,训练前建议确认一下torch.cuda.is_available()的返回值。
import torch import ultralytics print(torch.__version__) print(ultralytics.__version__) print(torch.cuda.is_available())如果你的机器是NVIDIA显卡,想用GPU训练,需要手动安装与驱动匹配的PyTorch版本。常见做法是先装CUDA toolkit,再执行pip install torch torchvision --index-url https://download.pytorch.org/whl/cu121,最后装ultralytics。这样能避免pip自动把torch的CPU版本装进去。注意这里cu121对应CUDA 12.1,不是CUDA版本越高越好,要以显卡驱动支持的版本为准。
3.2 数据集配置文件:steel.yaml的写法与路径规则
yolov8训练时通过YAML文件指定数据集的路径和类别名。下面是热轧带钢数据集对应的配置文件,路径要以你实际存放数据集的位置为准。
path: /home/user/datasets/steel train: images/train val: images/val names: 0: crazing 1: inclusion 2: patches 3: pitted_surface 4: scratched 5: rolled_in_scale这里有个很容易忽略的点:names字典里的类别顺序必须和标注txt中的class_id一致。很多人在前面用labelme转换时已经指定了class_names列表的顺序,那么yaml里的names也必须按同样顺序写,否则训练出来的模型在推理时会把类别标签错位。path这里我写的是绝对路径,你如果要在多台机器上复现,也可以写成相对路径,但yolov8会把相对路径锚定到当前工作目录,不如绝对路径稳妥。
3.3 训练自己的数据集:训练命令与关键参数逐项说明
环境通了,数据集配置好了,就可以开始训练。第一次跑建议用yolov8n,这个模型最小,训练速度快,能先把整个数据链路跑通,之后再换yolov8m或yolov8l追求精度。
yolo detect train \ data=steel.yaml \ model=yolov8n.pt \ epochs=100 \ imgsz=640 \ batch=16 \ patience=20 \ project=runs/steel \ name=yolov8n参数说明:
- data指定yaml文件路径,model是预训练权重,yolov8n.pt在第一次运行时会自动下载,如果你的机器无法访问外网,需要手动把权重文件放到当前目录。
- epochs是训练轮数。热轧带钢数据集规模通常不大,几百到几千张图片,100个epoch足够,不需要像COCO那样训练300轮。
- imgsz是输入图像尺寸。如果你发现缺陷目标普遍比较小,可以改成960,但显存占用会明显上升。对于CPU环境,建议保持640。
- batch是批大小,默认16。GPU显存不足时先降到8,CPU环境设成2或4即可,尽量不要超过8。
- patience是早停轮数,连续20轮验证集指标不再提升就自动停止。这个参数对工业场景很友好,因为现场数据标注成本高,样本量少,训练后期很容易过拟合,早停能帮忙保留最好的那版权重。
训练结束后,在runs/steel/yolov8n/weights目录下会生成best.pt和last.pt,best.pt是验证集上表现最好的权重,后续推理和部署都用它。
3.4 训练过程的监控:损失函数曲线和results图怎么读
训练完成后用yolov8画损失函数曲线图,这是一个很实用的技能。训练过程中ultralytics会自动生成results.png,里面包含box_loss、cls_loss、dfl_loss三条损失曲线,以及precision、recall、mAP50、mAP50-95的变化曲线。我在实际项目里一般先看box_loss和cls_loss是不是都平滑下降,如果cls_loss在训练后半段反弹,大概率是过拟合了,这时候回看patience早停的epoch位置,确认best.pt不是最后一个epoch。如果box_loss已经很低但mAP50-95不涨,说明定位已经足够好,问题出在边界框和真实框的IoU分布上,可以检查是不是有小目标因为标注框太小导致IoU计算不稳定。
要单独绘制某一条曲线,可以读取训练过程中自动保存的results.csv,用pandas画图。下面的命令可以快速预览。
python -c " import pandas as pd import matplotlib.pyplot as plt df = pd.read_csv('runs/steel/yolov8n/results.csv') plt.plot(df['epoch'], df['train/box_loss'], label='box_loss') plt.plot(df['epoch'], df['val/box_loss'], label='val_box_loss') plt.legend() plt.savefig('loss_curve.png') "这里有个细节:results.csv是训练时实时追加写入的,训练中断后再次续训,文件末尾会出现重复的epoch行。如果你要分析损失曲线,建议先做去重,否则曲线会往回折,看起来像损失跳变。
4. 评估、调优与部署:混淆矩阵、边缘模型和热轧缺陷的差异点
4.1 用模型评估命令输出混淆矩阵,定位类别互相干扰
yolov8训练完成后,不要直接拿best.pt上产线,先用验证集做一轮评估,把混淆矩阵找出来看。
yolo detect val \ model=runs/steel/yolov8n/weights/best.pt \ data=steel.yaml执行完会在runs/steel/yolov8n/目录下生成混淆矩阵图片confusion_matrix.png和PR曲线。热轧带钢表面缺陷检测中最常见的问题是pitted_surface和patches互相误判,因为这两类在图像上都表现为局部暗色区域,只是纹理粒度不同。如果混淆矩阵里这两类的交叉值很高,不要急着加数据,先回到标注层面检查是不是标注框标准不统一,比如同样是麻点,有人把整个区域标成一个框,有人只标局部,这种标注不一致会让模型学到一个模糊的边界。
4.2 小缺陷召回率低的三个常用调整方向
热轧带钢表面缺陷检测的难点不在大缺陷,而在小缺陷。mAP50-95低通常是小目标拖后腿。常见做法按优先级排序:
第一,增大imgsz。把imgsz从640调到960,小缺陷在输入图像中的像素占比变大,特征提取更充分。显存不够时配合batch减半。第二,调整增强策略。关闭mosaic或者仅在训练前中期开启,否则小缺陷被四张图缩放拼接后几乎不可见。第三,移除或减少强数据增强。金属表面图像本身纹理重复度高,radiate、perspective这类空间增强会让缺陷形态变得不真实,建议全部设为0。yolov8中具体参数为scale=0.5, translate=0.1, perspective=0.0, flipud=0.0。
4.3 误检率偏高时引入无缺陷负样本
热轧带钢表面不是每帧都有缺陷,模型很容易把氧化铁皮、水渍、光照反光当成缺陷。解决误检最有效的方法不是调阈值,而是在训练集中加入无缺陷的负样本图片,并在标注文件中不给它们写任何目标。yolov8对这类背景图片的处理方式是把每张图片视为没有目标,损失函数中的分类部分会趋向于输出背景置信度。我一般会把负样本比例控制在总样本量的10%到20%,太多会降低模型对真实缺陷的召回。
加入负样本后重新训练,误检下降很明显,但要注意验证集也需要同等比例的负样本,否则验证指标会虚高。
4.4 rk3588部署yolov8时的导出与推理思路
训练好的模型要部署到产线边缘设备,比较常见的是rk3588这类带NPU的板子。部署前先把权重从PyTorch格式导出成ONNX。
yolo export model=runs/steel/yolov8n/weights/best.pt format=onnx opset=12导出的ONNX模型可以先用onnxruntime在PC上做一次推理验证,确认输出尺寸符合预期。yolov8的ONNX输出是一个1x84x8400的矩阵,84由4个边界框坐标加80个类别概率组成,但这对应的是COCO数据集。你的模型只有6类,所以输出应该是1x10x8400,类别数变了,导出时模型结构会自适应调整。部署到rk3588时,一般需要把ONNX再转成rknn格式,这个转换工具在Rockchip官方仓库里,转换前需要准备一张校准图片做量化。量化精度通常选int8,热轧带钢图像的灰度范围比较集中,int8量化后mAP下降不超过2%的话可以接受。
5. 一个提升小缺陷召回率的具体做法:瓦片切分与重叠推理
在热轧带钢现场,相机拍到的原始图像往往很大,比如4096x2000,直接resize到640输入会丢失大量小缺陷。最后这一节给出一个改动成本低、效果又挺明显的方法:把大图切成多块小图分别推理,再合并结果。这比单纯调imgsz更可控,因为切分后的每块小图缺陷占比变大,等效于把一个小目标放大了。
import cv2 import numpy as np def split_image_into_tiles(img, tile_size=640, overlap=64): h, w = img.shape[:2] step = tile_size - overlap tiles = [] coords = [] ys = list(range(0, h - tile_size + 1, step)) xs = list(range(0, w - tile_size + 1, step)) if (h - tile_size) % step != 0: ys.append(h - tile_size) if (w - tile_size) % step != 0: xs.append(w - tile_size) for y in ys: for x in xs: tiles.append(img[y:y + tile_size, x:x + tile_size]) coords.append((x, y)) return tiles, coords tiles, coords = split_image_into_tiles(large_img)切分后每个tile用yolov8的model.predict单独推理,输出框的坐标是相对tile的,需要加上coords中的偏移恢复到原图坐标。重叠区域的好处是,如果某个缺陷正好被切分线切成两半,只要缺陷在至少一个tile中完整出现,就能被检出来,后续用普通NMS把重叠框合并即可。这里overlap设成64像素,对于热轧带钢表面缺陷来说是个经验值,因为大多数小缺陷直径在20到50像素之间,64像素重叠能保证缺陷不会被完全切掉。
合并框时注意置信度处理:同一目标在重叠区域可能出现多个框,传统NMS是保留置信度最高的框,但在工业检测里,如果两个框来自不同tile,置信度都很低,用NMS的IoU阈值0.5可能保留一个不够稳。我一般会用weighted NMS,也就是对重叠的同类框按置信度加权平均坐标,这样定位坐标会比直接NMS更稳。实现方式是把所有恢复坐标后的框收集起来,按类别分组,然后做标准NMS,再把保留的框和它周围的同类框做一次加权。这一步虽然增加了一点推理耗时,但能明显减少漏检。部署到rk3588这类NPU设备时,因为单次推理耗时低,四到六个tile的串行推理也能跑在实时要求不高的抽检场景;如果产线速度较高,可以先用小的tile_size观察准确率,再把并行推理放到多个NPU核心上。
本文还有配套的精品资源,点击获取