☰
热轧带钢表面缺陷检测:从NEU-DET数据集到YOLOv8产线部署实战
2026/10/11 12:18:42 网站建设 项目流程

简介:这份资源是面向计算机相关专业学生的深度学习毕业设计完整方案,聚焦热轧带钢表面缺陷自动检测这一工业质检场景,适用于毕设、课程设计与期末大作业,评审得分98分。压缩包共15个文件,约7.01MB,包含7个Python源码文件、2个UI界面文件、2份PDF论文与答辩材料,以及pt模型权重、yml配置、md说明和txt数据集地址等,覆盖数据预处理、模型训练、缺陷检测与可视化全流程。源码基于TensorFlow、Keras等框架,采用ResNet、Inception等卷积神经网络结构,配套裂纹、凹坑、划痕等多类缺陷图像数据集,训练出的模型具备较高准确率与召回率。论文部分完整记录了研究背景、系统设计、实验分析与结论,可作为撰写与答辩的参考。目前已有70人学习,适合希望快速复现工业缺陷检测项目、理解CNN落地流程的读者参考借鉴。

1. 热轧带钢表面缺陷检测:从产线痛点到一个可复现的深度学习方案

热轧带钢在 800℃ 以上的轧制线上高速运行,表面一旦出现结疤、裂纹、氧化铁皮压入或划伤,轻则降级处理,重则整卷报废。过去靠质检员在精轧出口用肉眼盯,速度跟不上、疲劳漏检、标准不统一,是产线上公认的老大难。深度学习在热轧带钢表面缺陷自动检测中的应用,核心就是用工业相机加 CNN 检测模型,把这件事从“人眼抽检”变成“在线全检”。这套方案适合两类人:一是冶金或制造行业的视觉工程师,想搭一套能落地的缺陷检测系统;二是做深度学习方向、想找一个真实工业数据集练手的学生和算法工程师。下面从数据集、模型选型、训练调参到部署排错,按能复现的顺序讲清楚。

2. 数据集怎么选怎么用:NEU-DET 与工业现场数据的差距

2.1 热轧带钢缺陷的六类典型形态

热轧带钢表面缺陷检测最常用的公开数据集是 NEU-DET(东北大学热轧带钢表面缺陷数据集),它把缺陷分成六类:rolled-in scale(氧化铁皮压入)、patches(斑块)、crazing(龟裂)、pitted surface(麻点)、inclusion(夹杂)、scratches(划伤)。每类 300 张灰度图,分辨率 200×200,标注格式是 VOC 的 XML。这个数据集的好处是类别定义清晰、标注质量稳定,适合做算法对比的基线。但要注意,它是实验室采集的静态图像,和产线上高速相机拍的动态图在光照、运动模糊、背景纹理上有明显差距。我一般会把它当“算法验证集”,真正上产线前必须补现场数据。

六类缺陷的视觉特征差异很大:划伤是细长高对比度线条,龟裂是网状微裂纹,氧化铁皮压入是暗色不规则块状。这意味着单一尺度的检测头很难同时覆盖,后面讲模型时会回到这一点。

2.2 从 VOC 标注转 YOLO 格式的脚本与边界坑

NEU-DET 原始标注是 VOC XML,如果你用 YOLO 系列训练,需要转成 txt 格式。转换脚本本身不复杂,但有几个边界情况容易翻车。

import xml.etree.ElementTree as ET import os # 类别映射,顺序必须和训练时的 data.yaml 一致 CLASS_MAP = { "crazing": 0, "inclusion": 1, "patches": 2, "pitted_surface": 3, "rolled-in_scale": 4, "scratches": 5 } def voc_to_yolo(xml_path, img_w=200, img_h=200): tree = ET.parse(xml_path) root = tree.getroot() lines = [] for obj in root.iter("object"): cls_name = obj.find("name").text.strip() if cls_name not in CLASS_MAP: continue # 跳过未定义类别,避免训练时报索引越界 cls_id = CLASS_MAP[cls_name] bbox = obj.find("bndbox") xmin = float(bbox.find("xmin").text) ymin = float(bbox.find("ymin").text) xmax = float(bbox.find("xmax").text) ymax = float(bbox.find("ymax").text) # 边界裁剪:标注偶尔会超出图像范围 xmin = max(0, min(xmin, img_w)) xmax = max(0, min(xmax, img_w)) ymin = max(0, min(ymin, img_h)) ymax = max(0, min(ymax, img_h)) # 转 YOLO 中心点归一化格式 cx = (xmin + xmax) / 2.0 / img_w cy = (ymin + ymax) / 2.0 / img_h w = (xmax - xmin) / img_w h = (ymax - ymin) / img_h if w <= 0 or h <= 0: continue # 宽高为 0 的脏标注直接丢弃 lines.append(f"{cls_id} {cx:.6f} {cy:.6f} {w:.6f} {h:.6f}") return lines

这段代码的关键点有三个。第一,CLASS_MAP的顺序必须和 YOLO 训练时data.yaml里的names完全一致,否则类别会错位,模型学出来的东西全是乱的。第二,边界裁剪不能省,NEU-DET 里有少量标注框的坐标略微超出 200×200,不裁剪会导致归一化后出现负值或大于 1 的值,训练时 loss 直接变 NaN。第三,宽高为 0 的脏标注要丢弃,这种框在计算 IoU 时会出问题。

参数方面,img_w和img_h默认 200,如果你做了 resize 增强,要改成实际输入尺寸。转换完建议写个校验脚本,统计每类框的数量,正常应该是每类 300 个左右,如果某类明显偏少,说明转换过程中丢了数据。

2.3 现场数据补充与增强策略

公开数据集只有 1800 张,直接训练容易过拟合。常见做法是补充现场数据,但现场数据标注成本高,我一般先用无标注数据做自监督预训练,再用少量标注做微调。增强策略上,热轧场景要特别注意:水平翻转可以用,垂直翻转要谨慎,因为产线方向是固定的,垂直翻转会造出现实中不存在的缺陷形态。亮度抖动和对比度扰动可以加,模拟不同批次钢卷的表面反光差异。高斯噪声和运动模糊也可以加,模拟相机在高速下的成像退化。

提示:增强后的图像要人工抽检一批,确认缺陷形态没有被破坏。我见过把龟裂增强成一片模糊的案例,模型学到的全是噪声。

3. 模型选型与训练:从 Faster R-CNN 到 YOLOv8 的取舍

3.1 两阶段与单阶段检测器在产线上的真实差异

热轧带钢表面缺陷检测的模型选型,核心矛盾是精度和速度。Faster R-CNN 这类两阶段检测器精度高,尤其对小目标和密集缺陷更稳,但推理速度慢,产线速度通常在 5-15 m/s,相机帧率要求 30fps 以上,两阶段模型在单卡上很难跑到实时。YOLO 系列单阶段检测器速度快,YOLOv8n 在 200×200 输入下能轻松跑到 100fps 以上,但小缺陷召回率会掉。

我的经验是:如果产线允许抽帧检测(比如每 3 帧检 1 帧),Faster R-CNN 可以用;如果要全帧实时,YOLOv8 是更务实的选择。折中方案是用 YOLOv8m 或 YOLOv8l,在速度和精度之间找平衡。另一个思路是用轻量 backbone 加注意力模块,比如在 YOLOv8 的 neck 部分加 CBAM,对小缺陷的响应会更好,代价是推理速度下降 10%-15%。

3.2 YOLOv8 训练配置与关键参数

下面是一个基于 Ultralytics YOLOv8 的训练配置示例。假设你已经把 NEU-DET 转成了 YOLO 格式,目录结构是images/train、images/val、labels/train、labels/val。

# data.yaml path: ./neu_det_yolo train: images/train val: images/val nc: 6 names: ["crazing", "inclusion", "patches", "pitted_surface", "rolled-in_scale", "scratches"]
# 训练命令 yolo detect train \ data=data.yaml \ model=yolov8m.pt \ epochs=200 \ imgsz=640 \ batch=16 \ lr0=0.01 \ lrf=0.01 \ momentum=0.937 \ weight_decay=0.0005 \ warmup_epochs=3 \ cos_lr=True \ patience=50 \ device=0

参数逐个说。imgsz=640是把 200×200 的原图放大到 640,这对小缺陷检测有帮助,但会引入插值伪影,如果显存够可以试 800。lr0=0.01是初始学习率,YOLOv8 默认用 SGD,这个值在 batch=16 时比较稳,如果 batch 降到 8,学习率要相应降到 0.005 左右。cos_lr=True开启余弦退火,比阶梯下降更平滑,对最终精度有 1-2 个点的提升。patience=50是早停耐心值,如果 50 轮验证集指标不升就停,避免过拟合。weight_decay=0.0005是 L2 正则化系数,热轧数据噪声大,这个值可以适当加大到 0.001,抑制过拟合。

训练过程中重点看三个指标:mAP50、mAP50-95和每类的recall。如果mAP50高但mAP50-95低,说明框的位置不够准,可以检查标注质量。如果某类recall明显低,比如龟裂只有 0.5,说明这类样本太难,需要针对性增强或调整 anchor 尺寸。

3.3 学习率与正则化的调参经验

深度学习 L2 正则化在 PyTorch 里就是weight_decay,但很多人不知道它和学习率的耦合关系。学习率大时,权重更新幅度大,L2 惩罚的效果会被放大,可能导致欠拟合;学习率小时,L2 效果弱,又容易过拟合。我的做法是先用默认weight_decay=0.0005跑一轮,看训练 loss 和验证 loss 的差距。如果验证 loss 持续高于训练 loss 且差距扩大,说明过拟合,把weight_decay加到 0.001 或 0.002;如果训练 loss 都降不下去,说明欠拟合,先降weight_decay到 0.0001,再检查学习率是不是太小。

另一个容易忽略的点是 warmup。YOLOv8 默认warmup_epochs=3,前 3 轮学习率从 0 线性升到lr0,这是为了防止训练初期梯度爆炸。如果你的 batch 很小(比如 4 或 8),warmup 要延长到 5 轮,否则初期 loss 会震荡。

4. 部署与推理:从 PyTorch 权重到产线可用的检测服务

4.1 模型导出与推理加速

训练完的.pt权重不能直接上产线,需要导出成推理引擎。常见做法是导出 ONNX 再用 TensorRT 加速,或者直接用 OpenVINO 在 CPU 上跑。下面是导出 ONNX 的命令。

yolo export model=runs/detect/train/weights/best.pt format=onnx imgsz=640 opset=12 simplify=True

opset=12是 ONNX 算子集版本,TensorRT 8.x 对 12 支持最好。simplify=True会做图优化,去掉冗余算子。导出后建议用onnxruntime跑一遍验证,确认输出和 PyTorch 一致。如果产线是 NVIDIA 显卡,继续用trtexec转 TensorRT engine,FP16 精度下推理速度能再提升 1.5-2 倍,精度损失通常在 0.5 个点以内。

4.2 推理服务的封装与后处理

产线部署不是跑个脚本就完事,要封装成服务,接收相机帧、返回缺陷坐标和类别。下面是一个基于 FastAPI 的最小推理服务示例。

import cv2 import numpy as np from fastapi import FastAPI, UploadFile from ultralytics import YOLO app = FastAPI() model = YOLO("best.onnx", task="detect") # 加载 ONNX 模型 @app.post("/detect") async def detect(file: UploadFile): img_bytes = await file.read() img = cv2.imdecode(np.frombuffer(img_bytes, np.uint8), cv2.IMREAD_COLOR) results = model(img, imgsz=640, conf=0.25, iou=0.45) detections = [] for r in results: for box in r.boxes: detections.append({ "class": model.names[int(box.cls)], "conf": float(box.conf), "bbox": box.xyxy.tolist()[0] }) return {"detections": detections}

conf=0.25是置信度阈值,产线上宁可误报不可漏报,这个值可以降到 0.15-0.2。iou=0.45是 NMS 的 IoU 阈值,缺陷密集时调低到 0.3-0.4,避免相邻缺陷被合并。后处理里还可以加规则过滤,比如划伤的长宽比通常大于 3,如果检测出的划伤框接近正方形,大概率是误报,可以过滤掉。

注意:ONNX 模型在 CPU 上跑和 GPU 上跑的数值精度有细微差异,切换设备后要重新验证一批样本,确认检测结果一致。

5. 避坑与排查:热轧带钢检测落地中的五个真实翻车点

5.1 现象:训练 loss 正常下降,但验证集 mAP 始终在 0.3 以下

原因:最常见的是类别映射错位。NEU-DET 的类别名在 XML 里可能有大小写或空格差异,比如rolled-in_scale和rolled_in_scale,如果CLASS_MAP没对齐,模型学到的类别全是错的。另一个原因是图像和标注文件没有一一对应,YOLO 会静默跳过没有标注的图,导致实际训练样本远少于预期。

解决:写个校验脚本,遍历所有图像和标注,确认每个图像都有对应的 txt,且 txt 里的类别 id 在 0-5 之间。再打印每类的样本数,和原始数据集对比。

5.2 现象:模型在 NEU-DET 上 mAP 0.85,上产线后漏检严重

原因:域偏移。NEU-DET 是实验室静态图,产线是动态高速成像,光照、对比度、运动模糊都不一样。模型在实验室数据上过拟合了。

解决:补现场数据,哪怕只有几百张,做微调。微调时学习率降到 0.001,冻结 backbone 前几层,只训 neck 和 head。另外可以在推理时加直方图均衡化,把现场图像的亮度分布拉近实验室数据。

5.3 现象:推理速度达不到 30fps,产线丢帧

原因:输入分辨率太大,或者后处理用了 Python 循环。640×640 的 YOLOv8m 在 TensorRT FP16 下能到 200fps 以上,但如果用 PyTorch 原生推理,可能只有 30-40fps。后处理里如果对每个框做 Python 层面的循环操作,也会拖慢速度。

解决:导出 TensorRT engine,用 FP16 或 INT8。后处理尽量用 numpy 向量化,或者用 CUDA kernel。如果还不行,降输入到 416 或 320,精度会掉 2-3 个点,但速度能翻倍。

5.4 现象:同一卷钢不同位置检测结果不一致,时好时坏

原因:相机曝光没锁死,产线环境光变化导致图像亮度波动。或者相机镜头有油污,成像质量不稳定。

解决:锁死相机曝光和增益,加装遮光罩,定期清洁镜头。软件层面可以在推理前加一个图像质量评估,如果亮度均值或方差超出阈值,直接报警而不是硬检。

5.5 现象:模型把氧化铁皮压入和斑块混淆,两类互相误报

原因:这两类在视觉上确实相似,都是暗色不规则区域。如果训练样本里这两类的边界样本标注不一致,模型就学不明白。

解决:重新审查这两类的标注标准,把边界样本统一。训练时可以用 focal loss 替代交叉熵,让模型更关注难分样本。另外可以加一个后处理规则:氧化铁皮压入通常面积更大、更分散,斑块更集中,用面积和连通域数量做二次判别。

6. 进阶技巧:用滑动窗口滤波和 TTA 把召回率再拉高几个点

产线上最怕的是漏检,一个裂纹漏过去可能就是整卷报废。除了调低置信度阈值,还有两个技巧我常用。第一个是滑动窗口滤波:对同一卷钢的连续帧,如果某个缺陷在连续 3 帧里都被检测到,才确认为真缺陷;如果只出现 1 帧,大概率是噪声。这个逻辑用队列实现,每帧的检测结果和上一帧做 IoU 匹配,匹配上就计数,计数达到阈值才输出。代价是引入 2-3 帧的延迟,产线速度不快的话可以接受。

第二个是 TTA(测试时增强):推理时对同一张图做水平翻转和亮度微调,各跑一次模型,把三次结果做 NMS 融合。这样能把召回率拉高 2-4 个点,代价是推理时间翻三倍。如果产线帧率有余量,这个技巧很划算。下面是一个简单的 TTA 融合代码。

import cv2 import numpy as np def tta_predict(model, img, conf=0.2, iou=0.45): # 原图推理 r1 = model(img, imgsz=640, conf=conf, iou=iou)[0] # 水平翻转推理 img_flip = cv2.flip(img, 1) r2 = model(img_flip, imgsz=640, conf=conf, iou=iou)[0] # 亮度微调推理 img_bright = cv2.convertScaleAbs(img, alpha=1.1, beta=10) r3 = model(img_bright, imgsz=640, conf=conf, iou=iou)[0] # 收集所有框,翻转的框要映射回原坐标 boxes = [] for r in [r1, r2, r3]: for box in r.boxes: x1, y1, x2, y2 = box.xyxy.tolist()[0] if r is r2: x1, x2 = img.shape[1] - x2, img.shape[1] - x1 boxes.append([x1, y1, x2, y2, float(box.conf), int(box.cls)]) # 用 NMS 融合 boxes = np.array(boxes) indices = cv2.dnn.NMSBoxes( boxes[:, :4].tolist(), boxes[:, 4].tolist(), conf, iou ) return boxes[indices.flatten()] if len(indices) > 0 else np.array([])

这段代码的关键是翻转后的框要映射回原坐标,否则 NMS 会把同一个缺陷当成两个。亮度微调的alpha=1.1, beta=10是经验值,模拟产线光照波动。TTA 的融合阈值conf可以比单次推理更低,因为三次投票本身就有过滤效果。

最后说个血泪教训:我最早做这个方向时,花了两周调模型,mAP 从 0.78 调到 0.83,结果上产线发现最大的问题不是模型精度,而是相机触发和图像传输的稳定性。模型再好,图像丢了就是白搭。所以如果你要落地,先把采集链路跑稳,再回头抠模型。希望帮到你。

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

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

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

立即咨询