ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

热轧带钢表面缺陷检测实战:NEU数据集与YOLOv8训练避坑指南

热轧带钢表面缺陷检测实战:NEU数据集与YOLOv8训练避坑指南 简介东北大学热轧带钢表面缺陷数据集是一份面向工业视觉检测的图像标注资源。压缩包内共2000个文件主要文件类型为JPG表面缺陷图像与XML标注文件整体大小约251MB。数据聚焦热轧带钢常见的裂纹、氧化皮、夹杂、凹坑等缺陷可供金属制造领域的算法工程师、科研人员及机器学习初学者用于训练卷积神经网络等深度学习模型完成缺陷自动分类与定位。资源已有1331人学习配套图像和标注便于开展数据预处理、模型训练与评估也可用于对比不同目标检测算法的性能。使用前通常需要做图像归一化、亮度校正与增强并合理划分训练集、验证集和测试集通过迭代训练模型能够学习多种缺陷模式辅助产线自动报警或停线检查降低人工目检成本。借助这份数据读者能系统接触热轧带钢表面质量检测流程为实际产线中的智能质检方案提供实验基础。1. 东北大学热轧带钢表面缺陷数据集1800张图能做什么、不能做什么“东北大学热轧带钢表面缺陷数据集.7z”这个压缩包在工业视觉圈传得很开1800张200×200灰度图覆盖氧化铁皮压入、裂纹、麻点、夹杂物、斑块、划痕六类热轧带钢表面缺陷常见流通版还带VOC风格的XML标注。它解决的是一个很实际的困境——想做钢材表面缺陷检测却找不到可下载、可复现的公开数据。适合三类人做轧线质检算法预研的工程师拿YOLOv8练自己数据的开发者以及想验证小数据集迁移学习的研究者。这个数据集有个反直觉的地方图小、类多、纹理高度相似跑通很容易跑出能外推的结果很难。下面按落地顺序走先拆包看清标注再转YOLO格式接着调YOLOv8最后把几个隐蔽的坑挑明。2. 先拆包弄清内容六类缺陷的工艺背景与XML标注结构2.1 六类热轧缺陷为什么长得像工艺成因决定图像纹理NEU这套数据集里的六类缺陷中文习惯叫法分别是氧化铁皮压入、裂纹、麻点、夹杂物、斑块、划痕英文名在标注里固定为rolled-in_scale、crazing、pitted_surface、inclusion、patches、scratches。这六个名字不是随便起的背后各有明确的工艺来源。标注名中文名典型图像特征在产线上怎么来的crazing裂纹细小龟裂纹理呈网状热应力与轧辊摩擦导致表面开裂inclusion夹杂物深色条带或颗粒方向接近轧向钢水或连铸坯内非金属夹杂被轧平patches斑块成片明暗差异边缘模糊冷却不均或氧化膜分布不均pitted_surface麻点密集小凹坑圆点状氧化铁皮剥落或除鳞水冲击遗留rolled-in_scale氧化铁皮压入黑色片状压痕边界清晰加热炉内氧化皮未除净被轧辊压入scratches划痕顺轧向的细长条纹辊道、导板或辊面异物刮伤这张表值得先看因为后续做数据增强甚至人工修标注时必须知道裂纹和麻点的区别是“线条”还是“坑”斑块和氧化铁皮压入的区别是“边缘模糊”还是“边界硬”。在200×200的灰度图里这两组最容易混。我一般会把这张表打印出来放旁边第一批训练batch图出来后逐张对照你会发现不少标注边界连人都容易犹豫。这也是这个数据集练手的真正价值缺陷类别少但类内差异和类间相似都不小。2.2 在Linux服务器上解压NEU-DET最小命令与目录边界下载到手的一般是NEU_surface_defect.7z不同渠道可能叫NEU-DET.7z。别双击解压先测包再解压这是处理7z数据集的基本习惯# Debian/Ubuntu 安装7z命令行工具CentOS对应 yum install p7zip p7zip-plugins sudo apt-get update sudo apt-get install -y p7zip-full # 先测试压缩包完整性出现 Everything is Ok 再继续报 CRC 错就重下 7z t NEU_surface_defect.7z # 解压到指定目录注意 -o 和目录名之间不能有空格否则会被当成两个参数 7z x NEU_surface_defect.7z -o/home/yourname/datasets/neu_surface -y几条参数说清楚x是解压命令保留压缩包内目录结构-o后跟输出路径要求中间不留空格-y是静默覆盖已有文件重跑时不打断t是test模式只校验不落盘。如果拿到的是分卷压缩名字带.7z.001这种把第一段卷名喂给7z x即可它会自动续读后续分卷。解压完先看目录结构不要急着写训练脚本。流通版本常见两种整理法一种是images目录放jpg、annotations目录放xml和COCO2017按images/annotations分目录的思路类似只是XML粒度更简单另一种是每张图和一个同名xml紧挨着放在同一目录。前者干净后者在划分训练集时遍历方便。如果你拿到的只有jpg没有XML说明是“分类版”只能做图像分类或自己用标注工具画框处理思路完全不同。提示如果压缩包内文件名含中文或韩文Linux下7z默认字符集可能解出乱码文件名。先确认7z t通过再加参数-mcp65001强制UTF-8解码再试还乱就升级到p7zip 16.02以上老版本对非ASCII支持很弱。3. 把XML标注转成YOLO格式转换脚本与四个边界坑3.1 为什么必须归一化、怎么归一化才不出框VOC风格XML记录的是像素绝对坐标比如xmin37 ymin12 xmax118 ymax86而YOLO训练需要的txt每一行是class_id x_center y_center width height四项全除以图像宽高得到0到1的相对值。NEU这套图的宽高固定是200×200但脚本里不能写死因为后续换自己的图集时尺寸会变而且有些二手版本标注的size字段与实际图片尺寸不一致——这种数据本身有缺陷转换脚本救不回来但至少不能被“写死200”再放大一次损失。最容易出框的坑在于把xmax减去xmin得到框宽却忘了除以图像宽或者中心点直接写成xmax。我一般转换完立刻抽验三个指标坐标最大值不超过1.0、最小值不小于0.0、每个类别的框宽高比与缺陷形态吻合——划痕应该偏瘦长斑块应该接近方形。3.2 转换脚本直接能跑的Python版以下脚本按dataset/images/xxx.jpg和dataset/annotations/xxx.xml的目录假设写输出YOLO txt到dataset/labelsimport xml.etree.ElementTree as ET from pathlib import Path CLASSES [crazing, inclusion, patches, pitted_surface, rolled-in_scale, scratches] def convert_one_xml(xml_path: Path, label_dir: Path): tree ET.parse(xml_path) root tree.getroot() # 宽高从XML里读不写死200留足兼容性 size root.find(size) img_w float(size.find(width).text) img_h float(size.find(height).text) lines [] for obj in root.iter(object): name obj.find(name).text.strip() if name not in CLASSES: # 遇到未登记类别直接跳过并告警而不是让训练器背黑锅 print(f[skip] unknown class {name} in {xml_path.name}) continue cls_id CLASSES.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) # YOLO相对坐标中心点与宽高全部除以图宽高 x_center (xmin xmax) / 2.0 / img_w y_center (ymin ymax) / 2.0 / img_h box_w (xmax - xmin) / img_w box_h (ymax - ymin) / img_h # 过滤异常框宽高0或坐标越界直接扔 if box_w 0 or box_h 0 or x_center 0 or y_center 1.0: print(f[warn] bad box in {xml_path.name}: {name}) continue lines.append(f{cls_id} {x_center:.6f} {y_center:.6f} f{box_w:.6f} {box_h:.6f}) if lines: label_dir.mkdir(parentsTrue, exist_okTrue) label_path label_dir / (xml_path.stem .txt) label_path.write_text(\n.join(lines), encodingutf-8) return len(lines) def convert_all(annotations_dir: Path, label_dir: Path): from collections import Counter counter Counter() for xml_path in sorted(annotations_dir.glob(*.xml)): convert_one_xml(xml_path, label_dir) # 统计每个类别的框数量发现0样本类别要回头查标注 for txt_path in sorted(label_dir.glob(*.txt)): for line in txt_path.read_text().strip().splitlines(): counter[int(line.split()[0])] 1 print(per-class box count:, dict(counter)) if __name__ __main__: base Path(/home/yourname/datasets/neu_surface) convert_all(base / annotations, base / labels)代码逻辑很直接先解析XML根节点从size标签读真实宽高再遍历每个object取name和bndbox计算中心坐标与宽高的相对值最后写入与图片同名的txt。这里用Path而不是字符串拼接避免Windows和Linux分隔符差异。末尾的统计输出很关键——如果pitted_surface只有几十个框而不是三百多说明这类标注没解析完先修数据再训练。注意脚本里CLASSES的顺序就是YOLO类别编号的顺序后续YAML必须与它完全一致否则会出现“训练正常、预测标签全错位”的隐蔽问题。3.3 按类别分层划分训练与验证集划分方式直接决定验证指标可信度。一种稳妥的划分是按类别分层抽样保证每类在训练集和验证集里的比例接近import random from pathlib import Path from collections import defaultdict random.seed(42) # 固定随机种子保证可复现 def split_by_class(images_dir: Path, train_ratio: float 0.8): image_paths sorted(images_dir.glob(*.jpg)) buckets defaultdict(list) for img_path in image_paths: label_path images_dir.parent / labels / (img_path.stem .txt) if not label_path.exists(): print(f[warn] missing label: {img_path.name}) continue # 以这张图第一个类别作为分层依据 first_cls label_path.read_text().split()[0] buckets[first_cls].append(img_path) train_files, val_files [], [] for cls_id, files in buckets.items(): random.shuffle(files) split int(len(files) * train_ratio) train_files.extend(files[:split]) val_files.extend(files[split:]) return sorted(train_files), sorted(val_files) train_imgs, val_imgs split_by_class( Path(/home/yourname/datasets/neu_surface/images))这个脚本有两个边界要说明一是它以“第一个类别”作为分层键对NEU这种单缺陷为主的图够用但碰到一张图同时标多个类别时会有偏差二是按图划分而不是按框划分防止同一张图的多框信息泄漏到训练集和验证集。真正要上产线时按“卷号”分组比按类别分组更重要这个留到第5章展开。4. 用YOLOv8训练热轧缺陷dataset.yaml与超参取舍4.1 dataset.yaml怎么写才不踩类别错位转换完先写YAML。YOLOv8要求类别顺序与转换脚本的CLASSES完全一致最容易出隐蔽问题的地方就在这里——脚本里次序是crazing第一YAML里如果写成inclusion第一loss能正常下降但跑出来的预测类别全体错位表现是混淆矩阵对角线空白、预测标签和图像完全对不上。这不是模型问题是元数据错位排查时必须先查这个。# neu_det.yaml path: /home/yourname/datasets/neu_surface # 写绝对路径相对路径易因工作目录不同而失效 train: images/train val: images/val names: 0: crazing 1: inclusion 2: patches 3: pitted_surface 4: rolled-in_scale 5: scratchesnames的顺序必须与转换脚本CLASSES一致不是按字母序。路径建议写绝对路径否则在不同终端里yolo命令的当前目录变了就会报找不到图片。4.2 训练命令与关键超参基础训练命令如下yolo detect train \ dataneu_det.yaml \ modelyolov8s.pt \ epochs120 imgsz512 batch16 \ workers4 device0 seed42 \ projectruns/neu nameexp1关键参数取舍表参数推荐值为什么这么设modelyolov8s.pt从COCO预训练继续练比随机初始化收敛快得多n型号对200×200的细纹理特征提取能力偏弱imgsz512原始图只有200×200盲目拉到640等于放大3倍多黑边占比高且纹理被插值磨平512在分辨率与显存之间折中batch8~32图很小显存占用低从16起步显存宽裕就拉到32爆显存再降回8epochs100~1501800张的小数据集120轮足够看趋势主要盯val指标而不是死等300轮workers2~4小图IO便宜开8个worker反而可能把磁盘队列打满CPU负载上去了训练速度没变快seed42固定种子保证你两次跑结果一致至少能排除随机性干扰这里多说一句选型理由为什么用yolov8s而不是n热轧带钢缺陷纹理细模型下采样stride一旦把32×32的区域压成1个特征点裂纹和麻点的细节就丢了。n型号参数量少收敛快但在这种细纹理任务上AP通常比s低3~5个点。反过来也没必要直接上l或x1800张图训大模型只会过拟合s是性价比拐点。转换出来的txt标注不挑框架。YOLOv5、YOLOv8甚至MMDetection的YOLOX都能直接吃只要把yaml里的train/val路径指过去不需要二次转换。我一般先跑YOLOv8出基线再用YOLOv5复验同一份txt如果两套框架mAP差异在一个点以内说明数据和训练配置都没问题。4.3 从日志判断有没有在好好学训练时看终端输出或runs/neu/exp1/results.csv里的四个指标train/loss、val/box_loss、val/cls_loss、mAP50。小数据集上最常见的现象是train loss一路下降val/cls_loss却在20轮后反弹——典型过拟合。这时候先查数据划分是否泄漏再谈增强。看到NaN loss大概率是batch里出现空标注文件或者label路径指向了不存在的txt看到mAP一开始就接近1.0反而可疑先检查是不是验证集图片混进了训练。还有一个实操习惯第一轮先跑20个epoch不调任何超参把plotsTrue默认生成的train_batch图翻一遍。这一步比看任何指标都重要能发现标注错位、灰度图通道异常、框画错位置这类数据问题。5. 热轧带钢缺陷数据集常见问题避坑从7z解压到AP虚高5.1 7z解压报密码错误或CRC校验失败现象7z x到一半突然报CRC Failed退出或者压缩包作者明明给了密码输入时提示Wrong password。原因一是下载过程截断二是命令行下密码含特殊字符被shell转义三是p7zip版本太老对AES-256加密头支持不完善。解决先用7z t测试包完整性不完整就重新下载密码带!$#这类字符时把密码写进双引号或先执行export PASS你的密码再引用环境变量避免bash把!当历史展开仍然报错就升级p7zip到16.02以上。NEU数据集本身基本不加密如果你拿到的转传版本加了密码解压前先7z l看一下包内文件列表确认目录结构再动手避免解到一半发现磁盘不够。5.2 灰度图进YOLO报通道不匹配现象训练或推理时数据加载阶段报Invalid number of channels或者shape从(H, W)变成了(H, W, 1)导致与预训练权重期望的[B, 3, H, W]对不上。原因NEU这套是单通道灰度图OpenCV默认读出来是(H, W)而YOLOv8从COCO预训练来的pipeline默认三通道。解决图片进Dataloader前统一转三通道import cv2 img cv2.imread(str(path), cv2.IMREAD_GRAYSCALE) img_rgb cv2.cvtColor(img, cv2.COLOR_GRAY2BGR)这里用BGR而不是灰度直接复制成三份因为Ultralytics内部还会做一次BGR转RGB。顺序颠倒会得到反色图训练loss照样能降但验证集表现会很诡异你还会以为模型出了问题。5.3 随机划分导致AP虚高现象按文件名随机8:2划分后验证集mAP50高达0.88到0.92把模型拿去跑现场拍来的新图效果立刻打折。原因热轧线是连续采样的同一张带钢在相邻时间拍出的图像缺陷纹理高度相关。随机拆分会把“近亲”图片同时放进训练集和验证集验证集不再代表独立分布。这个问题在单一来源的数据集上几乎必有NEU也不例外。解决按“批次/卷号”划分而不是按文件名随机打散。NEU文件名从部分渠道能看到前缀带卷号或采样序号按前缀把整卷分进同一边没有卷号时按文件名前缀分组模拟批次。练手可以随机划分但对外报指标必须写清楚是随机划分还是独立批次划分这两个数字不能混着说。5.4 错标与类别混淆怎么处理现象混淆矩阵里crazing和pitted_surface互相串检rolled-in_scale错认成patches。第二类容易混还可以理解第一类在视觉上差别很大串检多半是标签问题。原因200×200分辨率下纹理本来就接近加上数据集中存在少量标错框模型在错标上“学得很认真”。解决先看训练batch图而不是调超参。训练时plotsTrue默认开启到runs/neu/exp1/train_batch*.jpg逐张看把明显标错的XML手动改掉重转一遍。再看验证集混淆矩阵错检集中在哪一对就给哪一类做针对性增强比如裂纹类做弹性形变斑块类加强hsv扰动而不是全局把mosaic调大。提示这个数据集的标签质量整体不错但“不错”不等于“完美”。碰上纹理更接近的缺陷先怀疑标签和图像分辨率别让模型背锅。6. 从跑通到可信独立验证与增强调整的收尾技巧6.1 用best.pt先跑一遍批量推理训练完先别急着看指标先拿best.pt对独立测试集做批量推理yolo detect predict modelruns/neu/exp1/weights/best.pt \ source/home/yourname/datasets/neu_surface/images/test \ save_txtTrue save_confTrue保存的txt里每行是class_id x_center y_center w h conf把conf阈值从默认0.25提高到0.4再看结果。这一步的目的是数据清洗而非打分看图比看数字更容易发现漏检。6.2 验证的三把尺最后的验证阶段我看三个东西。一是confusion_matrix.png对角线之外有没有成对的高亮块如果有回第5.4节查标签。二是val_batch的预测叠加图框是不是整张图堆成一团是就说明背景被误检。三是results.csv里的mAP50趋势最后一轮还在爬说明没训完中途反弹又跌回说明增强或划分有问题。6.3 两条最值回票价的增强调整对NEU这种小图、灰度、纹理数据我最常调的两个增强参数是把mosaic关掉或降到0.5因为mosaic拼接会把200×200的缺陷切成碎块浅层特征学到的是噪声再就是开弹性形变裂纹和划痕这类线性纹理最吃这个。两个调整都不改代码训练命令里直接覆盖yolo detect train ... mosaic0.5 close_mosaic10 ...mosaic降到0.5的意思是batch里一半样本走拼接一半保持原图既保留多样性又不让纹理碎得太厉害close_mosaic10表示最后10个epoch关闭mosaic让模型在真实分布上收尾。我自己的习惯是每次实验固定seed先跑脚本丢掉缺失label的文件再进调参循环。吃过最大的亏是随机划分跑出0.9的mAP换卷后立刻现原形从那以后对外提指标一定注明划分方式。希望帮到你。本文还有配套的精品资源点击获取
返回列表