简介东北大学公开的热轧带钢表面缺陷数据集聚焦带钢表面缺陷的识别与分析适用于机器视觉、深度学习和工业质检领域的科研人员与工程师可用于训练自动检测与分类模型。压缩包共含2000个文件主体为1800张jpg表面图像及对应的1800个xml标注文件图像覆盖裂纹、氧化皮、夹杂、凹坑等典型缺陷类型标注信息可直接用于监督学习。资源包整体约251.02MB文件命名清晰便于按类别筛选和批量预处理。目前已有1331人学习下载相关研究参考价值较高。利用该数据集读者可开展卷积神经网络等模型的训练与验证也可结合图像校正、归一化与增强手段提升检测精度为热轧产线表面质量监控提供算法支持。1. 热轧带钢表面缺陷数据集这份 NEU-DEF 资源值不值得下生产线上热轧带钢每秒数米过质检员盯屏盯到眼花裂纹这类缺陷一漏就是整卷降级。东北大学热轧带钢表面缺陷数据集就是为这个场景而生的把产线缺陷图像收进一个 .7z 压缩包让你能训练自动检测模型。包内图像以 crazing 开头命名对应热轧带钢的裂纹缺陷可直接做分类也可改造为检测任务素材是工业视觉领域常用的基准数据。适合做产线视觉落地的工程师、以缺陷检测开题的学生、想评估深度学习能否替掉人工目检的工艺人员。我的判断这类数据背景干净、缺陷集中比现场随手拍的乱图更适合做算法验证但解压、预处理、标注格式每步都有坑。2. 数据长什么样从 .7z 压缩包到可训练的缺陷图像集2.1 一个压缩包里藏着什么文件命名与缺陷类型先别急着解压先想清楚里面是什么。文件列表里一整排crazing_50.jpg、crazing_270.jpg这种命名crazing是类名后面的数字是样本编号这是缺陷数据集里最通用的约定。crazing对应热轧带钢表面的裂纹缺陷形态上是细碎网状或线状裂纹在灰度图像上表现为暗色纹理区域。摘要里提到的氧化皮、夹杂、凹坑等在完整版数据集中是另外几个缺陷类如果这份压缩包以 crazing 为主说明是做了裁剪的子集训练之前要确认自己手里的任务到底是二分类还是多分类。拿到数据的第一动作不是训练是统计。用一小段 Python 把类别数量、图像尺寸、色彩模式全摸一遍心里有数再动手from pathlib import Path from collections import Counter import cv2 img_paths list(Path(./NEU_DEF).rglob(*.jpg)) print(total images:, len(img_paths)) classes Counter() sizes Counter() for p in img_paths: # 优先取父目录名当类别平铺存放时才退回文件名前缀 cls p.parent.name if p.parent.name not in (NEU_DEF, images) else p.stem.split(_)[0] classes[cls] 1 img cv2.imread(str(p)) if img is not None: sizes[img.shape[:2]] 1 print(classes:, classes) print(sizes:, sizes)这段代码有两点用心一是取类别时优先看父目录名因为完整版数据集的类名里可能有下划线比如pitted_surface直接按文件名前缀split(_)[0]会切出错误结果二是顺手统计了图像尺寸NEU 这类表面缺陷数据通常是灰度图、分辨率相对统一但不同打包版本可能被二次压缩或裁剪尺寸统计能第一时间发现异常。如果cv2没装pip install opencv-python就行。这一步输出的两个 Counter就是你对这份数据集的第一个真实认知。项目说明压缩格式.7z需 7-Zip 或 p7zip 解压图像格式JPG灰度表面图命名规则类名_编号.jpg如 crazing_50.jpg缺陷类型至少包含 crazing裂纹完整版本常含氧化皮、夹杂、凹坑等2.2 7z 解压Linux 与 Windows 下的两种可靠做法7z 格式在工业数据集分发里很常见压缩率高但第一步就卡住的人不少。Linux 下先装 p7zip 再解压sudo apt-get update sudo apt-get install -y p7zip-full 7z x 东北大学热轧带钢表面缺陷数据集.7z -o./NEU_DEF注意-o参数后面直接跟路径不能有空格写成-o./NEU_DEF而不是-o ./NEU_DEF否则 7z 会把它解析成另一个参数。解压完成后用ls ./NEU_DEF确认目录结构顺便对比文件数量和你下载页面上标注的数量是否一致。如果终端对中文文件名报错或乱码先ls确认实际文件名再用通配符7z x *.7z -o./NEU_DEF绕过中文输入问题。Windows 下我只推荐 7-Zip 官方版右键压缩包选“解压到当前文件夹”最省事。想走命令行就找到7z.exe的位置执行 C:\Program Files\7-Zip\7z.exe x D:\datasets\东北大学热轧带钢表面缺陷数据集.7z -oD:\datasets\NEU_DEF注意Windows 控制台对中文文件名的编码处理不太稳定如果命令没反应或路径乱码先cd进压缩包所在目录再写文件名而不是全路径。解压完不要立刻删压缩包。我会把原始 .7z 留在原处只把解压出来的目录复制到工作区。工业数据集的原始镜像只有一份后面标注格式转坏、目录被误删时压缩包就是后悔药。2.3 目录结构设计为训练和后续扩展留好余地解压出来的原始目录通常是平铺的所有 jpg 堆在一起这种结构不能直接喂给 PyTorch 的ImageFolder。我一般会新建三个目录按缺陷类别建子目录再复制过去NEU_DEF/ ├── train/ │ ├── crazing/ │ ├── inclusion/ │ └── ... ├── val/ │ ├── crazing/ │ └── ... └── test/ ├── crazing/ └── ...这样做的原因有两个一是ImageFolder天然要求“类别即子目录”的结构省掉自定义 Dataset 的麻烦二是保留原始文件和派生目录分离后续如果拿到补充标注还能追溯回原始图像。如果做的是检测任务而不是分类我会在每个 split 下再分images和labels两个目录跟 COCO 数据集的目录约定对齐这样接 yolov8 系列脚本时几乎不用改路径逻辑。目录结构定了训练之前的数据准备工作才算正式开始。3. 训练前的数据准备划分、归一化与增强的工程套路3.1 数据集划分别让同类缺陷图像同时出现在训练集和验证集划分数据集的关键不是随机是“随机得科学”。热轧带钢表面缺陷图像是单张独立截取的样本不像视频流那样有强时序相关性所以按类别随机打乱基本够用。但有一个细节必须注意如果同一卷带钢上连续采样的图像被分成两份模型在训练时见过的纹理背景会在验证集里原样复现验证指标会虚高。我常用的划分逻辑是逐类别操作保证每个类在三个集合中都存在。直接上代码import random from pathlib import Path import shutil random.seed(42) src Path(./NEU_DEF/original_images) for cls_dir in src.iterdir(): if not cls_dir.is_dir(): continue imgs list(cls_dir.glob(*.jpg)) random.shuffle(imgs) n len(imgs) train_end int(n * 0.7) val_end int(n * 0.9) for idx, img in enumerate(imgs): if idx train_end: split train elif idx val_end: split val else: split test dst src.parent / split / cls_dir.name / img.name dst.parent.mkdir(parentsTrue, exist_okTrue) shutil.copy2(img, dst)比例按 7:2:1 切前 70% 进训练集中间 20% 进验证集最后 10% 留作测试集。random.seed(42)固定随机种子保证每次划分结果一致这是复现实验最基本的要求。用copy2而不是copy保留文件创建时间和属性后面做数据溯源时能多一条线索。这个脚本跑完后train/val/test 三个目录下各类别文件数应该大约等于原数的 70%、20%、10%可以用上一章的统计代码再核对一遍。3.2 预处理管线灰度归一化、尺寸统一与数据增强热轧带钢表面缺陷图是灰度图但预训练的 ResNet 系列要求三通道输入。不少新手在这里翻车ToTensor()把单通道灰度图变成(1, H, W)一进模型就报通道数不匹配。解决方式是在transforms里显式转三通道from torchvision import transforms transform_train transforms.Compose([ transforms.Grayscale(num_output_channels3), transforms.Resize((256, 256)), transforms.RandomHorizontalFlip(p0.5), transforms.RandomRotation(10), transforms.ColorJitter(brightness0.2, contrast0.2), transforms.CenterCrop(224), transforms.ToTensor(), transforms.Normalize(mean[0.485, 0.456, 0.406], std[0.229, 0.224, 0.225]) ]) transform_val transforms.Compose([ transforms.Grayscale(num_output_channels3), transforms.Resize((256, 256)), transforms.CenterCrop(224), transforms.ToTensor(), transforms.Normalize(mean[0.485, 0.456, 0.406], std[0.229, 0.224, 0.225]) ])Grayscale(num_output_channels3)会把单通道灰度图复制成三通道语义上不是真彩色但能喂进预训练模型这是工业灰度图迁移学习的通行做法。归一化继续用 ImageNet 的 mean/std虽然工业图和自然图像分布不完全一致但迁移场景下默认值很能打如果训练发现 loss 不降、收敛极慢再用数据集的真实 mean/std 覆盖。增强方面水平翻转、小角度旋转、亮度对比度抖动都可以上。但CutOut、RandomErasing这类遮挡式增强对微小表面缺陷要慎用——缺陷本身就只占图像很小一块再随机挖掉一块黑斑模型很容易把“暗区”学成缺陷特征。验证集的 transform 别加增强只做尺寸统一和归一化否则验证集指标反映的不是真实分布。3.3 标注格式转换分类任务与 YOLO 检测任务的不同路径如果只有分类标签、没有标注框而你想用 yolo 系列做检测有两条路。正路是拿 labelme 之类工具人工画框——表面缺陷区域小标注工作量相对可控另一条路是先用整图作框跑通管线的伪标注这条路只能验证代码流程出来的性能数字没有参考价值。伪标注的转换脚本长这样import cv2 from pathlib import Path CLASSES [crazing, inclusion, patches, pitted_surface, rolled-in_scale, scratches] src Path(./NEU_DEF/train) for cls_dir in src.iterdir(): if not cls_dir.is_dir(): continue cls_id CLASSES.index(cls_dir.name) if cls_dir.name in CLASSES else -1 if cls_id 0: continue for img_path in cls_dir.glob(*.jpg): img cv2.imread(str(img_path)) if img is None: continue h, w img.shape[:2] txt_path img_path.with_suffix(.txt) # 整图作框仅用于跑通管线落地必须换真实标注框 with open(txt_path, w) as f: f.write(f{cls_id} 0.5 0.5 1.0 1.0\n)YOLO 格式的五个字段是类别 id、归一化中心 x、归一化中心 y、归一化宽、归一化高。整图框写死0.5 0.5 1.0 1.0四张图跑通流程没问题。但我要把话说清楚这个脚本生成的标签训练的 mAP 完全不能代表真实检测能力。缺陷区域在整图里占比很小模型学不到位置信息真正要落地必须人工标注真实缺陷框再用labelme导出转换。另外表面缺陷用水平矩形框就够不需要上 mmrotate 那类旋转检测框架——DOTA 场景里带角度的物体才会用到旋转框钢带表面的裂纹、麻点没有强旋转语义别给自己加戏。分类任务就简单得多目录结构本身就是标签ImageFolder直接按子目录名生成类别映射不用额外写标注文件。所以拿到数据集先想清楚一个问题这数据是用来做分类还是做检测这个决定影响后面所有处理步骤。4. 用 CNN 跑通第一个缺陷分类模型PyTorch 实现要点4.1 选型理由为什么先用 CNN 而不是直接上 Transformer第一版模型我推荐 ResNet18。原因不是它新是它稳。表面缺陷数据集的样本量通常只有数千张ViT、Swin 这类 Transformer 需要海量数据预训练在小样本工业数据集上很容易欠拟合ResNet18 网络浅、参数少有 ImageNet 预训练权重可以直接迁移CPU 上也能跑推理产线部署不吃力。如果分类类别只有两类把最后一层全连接输出改成 2 就行。还有一种常见误用是直接套用 ResNet152 这类大网络指望模型容量大就能提升精度。实际上缺陷分类任务的难点不在特征提取能力而在小样本下的泛化大网络只会过拟合得更快。ResNet18 作为第一版 baseline 完全够用后面不够再往上加。4.2 构建 DataLoader 与训练循环先看数据加载和模型定义部分import torch import torch.nn as nn from torch.utils.data import DataLoader from torchvision import datasets, models, transforms transform transforms.Compose([ transforms.Grayscale(num_output_channels3), transforms.Resize((224, 224)), transforms.ToTensor(), transforms.Normalize(mean[0.485, 0.456, 0.406], std[0.229, 0.224, 0.225]) ]) train_ds datasets.ImageFolder(./NEU_DEF/train, transformtransform) val_ds datasets.ImageFolder(./NEU_DEF/val, transformtransform) train_loader DataLoader(train_ds, batch_size32, shuffleTrue, num_workers4) val_loader DataLoader(val_ds, batch_size64, shuffleFalse, num_workers4) # 新版 torchvision 用 weights 参数老版本把 weights 换成 pretrainedTrue model models.resnet18(weightsmodels.ResNet18_Weights.IMAGENET1K_V1) model.fc nn.Linear(model.fc.in_features, len(train_ds.classes))提示老版本 torchvision 没有ResNet18_Weights会用pretrainedTrue写法新旧混用会直接报 KeyError。装环境之前先确认版本pip show torchvision看一眼。ImageFolder会自动扫描子目录把目录名映射成类别索引顺序按字典序排列。model.fc.in_features取出 ResNet18 原始全连接层输入维度替换成自己的类别数。num_workers4让数据加载走多进程训练时不会因为读图卡住 GPU。训练循环我习惯带早停和模型保存device cuda if torch.cuda.is_available() else cpu model model.to(device) optimizer torch.optim.SGD(model.parameters(), lr1e-3, momentum0.9, weight_decay1e-4) scheduler torch.optim.lr_scheduler.StepLR(optimizer, step_size20, gamma0.5) criterion nn.CrossEntropyLoss() best_acc 0.0 patience, bad_epochs 8, 0 for epoch in range(50): model.train() total_loss 0.0 for images, labels in train_loader: images, labels images.to(device), labels.to(device) optimizer.zero_grad() outputs model(images) loss criterion(outputs, labels) loss.backward() optimizer.step() total_loss loss.item() * images.size(0) model.eval() correct total 0 with torch.no_grad(): for images, labels in val_loader: images, labels images.to(device), labels.to(device) outputs model(images) _, preds torch.max(outputs, 1) correct (preds labels).sum().item() total labels.size(0) val_acc correct / total print(fepoch {epoch} | loss {total_loss / len(train_ds):.4f} | val_acc {val_acc:.4f}) if val_acc best_acc: best_acc val_acc torch.save(model.state_dict(), ./best_defect_model.pth) bad_epochs 0 else: bad_epochs 1 if bad_epochs patience: print(fearly stop at epoch {epoch}, best acc {best_acc:.4f}) break scheduler.step()训练循环里的三个关键点optimizer.zero_grad()每步清零梯度不清会累加验证阶段用torch.no_grad()关掉梯度计算省显存也加速模型保存用的是state_dict()而不是整个模型文件更小加载时也更灵活。早停逻辑是连续 8 轮验证集精度不涨就停工业数据集训练 30 到 50 轮基本就到头了没必要硬跑满。4.3 训练参数怎么定学习率、Batch Size 与早停参数不是玄学但有规律可循。SGD 加动量是这类小数据集最稳的组合Adam 收敛快但最终精度往往不如调好的 SGD。学习率从 1e-3 起步StepLR 每 20 轮衰减一半让训练后期步长变小、逼近极值点。Batch Size 优先 32显存不够减到 16同时可以把学习率也降一点因为小 batch 的梯度噪声更大。参数推荐值说明optimizerSGD(momentum0.9)小数据集上比 Adam 稳learning rate1e-3 → 1e-4StepLR 每 20 轮 gamma0.5batch size32 / 16显存不足优先减半weight_decay1e-4L2 正则防过拟合patience8 ~ 10连续 N 轮验证集不涨即停max epochs30 ~ 50配合早停很少跑满如果单卡显存只有 4Gbatch_size32加ResNet18在 224 分辨率下很可能放不下。两个选择batch 降到 16或者开梯度累积——每 2 个 batch 才做一次optimizer.step()等效于 batch 32。我一般先试 16省事效果几乎没差别。训练完看一个数验证集 accuracy 有没有明显高于类别占比。如果两类各占一半accuracy 冲到 95% 以上先别高兴去查混淆矩阵——上一章说的类别不均衡在训练结果里会暴露得清清楚楚。5. 避坑指南解压、读取、训练中的五个高频问题以下五条是我处理这份数据集和后续训练时踩过的真实坑每条都按现象、原因、解决三步说清。5.1 7z 报密码错误但密码是对的现象执行7z x一直提示 Wrong password或者图形界面输入确认过的密码反复弹“密码错误”。原因有两类一类是复制粘贴的密码带了前导或尾部空格肉眼看不见另一类是压缩包在 Windows 下用中文密码创建Linux 端用不同 locale 解码同一个字符在不同字符集下编码不一致密码就对不上了。解决先手动输入一遍密码别复制再用命令行7z x 包名.7z -p密码直接传入路径用 Tab 补全避免打错字还不行就把压缩包改成纯英文文件名再解排除字符集干扰。如果报的是 CRC 失败而不是密码错误说明包本身损坏回到下载源重新拉文件先比对 SHA 值再解压。5.2 灰度图读成三通道黑图现象cv2.imread读出来显示全黑或偏色PIL读出来是 L 模式送入模型报通道错。原因cv2.imread默认按 BGR 三通道读入灰度图强行复制成三通道后每个通道值一样显示正常但语义已经变了更隐蔽的是有人用imread读灰度图又存回 jpg等于把灰度信息按三通道重新编码了一次像素值失真。解决统一用cv2.imread(path, cv2.IMREAD_GRAYSCALE)读成单通道再在 transform 里用Grayscale(num_output_channels3)转三通道。调试阶段务必打印img.shape和img.dtype单通道是(H, W)三通道是(H, W, 3)一眼就能看出问题。5.3 类别不均衡导致准确率高但召回率差现象总 accuracy 报表 95%但某个缺陷类的召回率只有 40%。原因表面缺陷数据集中各类样本量天然不均衡裂纹类可能只有其他类的一半模型学会偷懒全部判成多数类就能拿高分。解决两个手段配合使用。一是给损失函数加类别权重CrossEntropyLoss(weighttorch.tensor([1.0, 2.0]))让少数类的错分代价更高二是用WeightedRandomSampler按样本数反比采样让每个 batch 里少数类出现的概率更大。我自己优先用损失权重改动最小效果可解释。改完再看混淆矩阵多数类被误判到少数类的情况会明显减少。5.4 过拟合训练集 99% 验证集 70%现象训练 loss 掉到 0.01验证集 accuracy 停在 70% 左右不动。原因网络容量大、数据集小、增强太弱模型把训练集特征“背”下来了。新手最容易忽略的排查点验证集的 transform 是不是漏了ToTensor和Normalize如果验证集只做了Resize图像数据还是 PIL 对象或 0-255 的整数张量模型输出会完全不正常。解决先核对 transform 一致性再给网络加 DropoutResNet18 的最后一层全连接可以插一个nn.Dropout(0.3)最后把训练增强的旋转角度从 10 度提到 15 度加一点高斯模糊模拟产线光照波动。这套组合下来验证集一般能拉回 5 到 8 个点。5.5 解压后路径带中文导致 ImageFolder 读取失败现象Windows 下解压到D:\数据集\东北大学热轧带钢表面缺陷数据集Python 的ImageFolder报 FileNotFoundError或者加载到一半路径乱码。原因Windows 文件系统默认编码是 GBKPython 3 字符串默认 UTF-8中文路径越过某些原生接口时编码转换失败。Linux 下通常没这个问题但 Windows 上非常典型。解决数据目录统一放全英文路径比如D:\datasets\NEU_DEF压缩包本身叫中文名没关系解压输出目录用英文。如果原始目录已经建好用shutil.copytree复制到英文路径再操作不要在原位硬刚。这是最容易避免也最容易反复踩的坑我在 Windows 上被它耗掉过一整个下午。6. 从模型到产线缺陷检测的验证方法与落地技巧6.1 用混淆矩阵和单类指标验证模型别只看 accuracy产线上裂纹漏检等价于整卷降级误报只是多看一遍两类错误的代价完全不同。只看验证集 accuracy 会骗人单类指标才暴露真实能力。我每次训练完都跑一遍分类报告把 recall 和 F1 逐个类打出来from sklearn.metrics import classification_report y_true, y_pred [], [] with torch.no_grad(): for images, labels in val_loader: outputs model(images.to(device)) _, preds torch.max(outputs, 1) y_true.extend(labels.numpy()) y_pred.extend(preds.cpu().numpy()) print(classification_report(y_true, y_pred, target_namesval_ds.classes))6.2 产线部署的常见做法裁剪 ROI 与置信度阈值产线相机视野大整帧送模型又慢又容易误报。常见做法是在前端先做 ROI 裁剪把带钢表面按固定宽度切成小块只把候选区域送进模型。推理时输出的是 softmax 概率我会设一个阈值比如 0.7 以下全判为“疑似”进人工复核工位阈值调高误报减少但漏检增加调低则相反。具体值没有标准答案拿一段真实产线视频回放调一次选漏检率最低的那档。6.3 持续迭代的笨办法坏样本回灌我做过最土但最有效的优化把误检和漏检的图像存下来攒够一批人工标注混入原始训练集重新训练。表面缺陷形态会随轧辊磨损、来料批次变化模型上线后不是一劳永逸。这份数据集虽然老但图像干净、类别清晰正好用来把这条“采集、标注、重训、再验证”的迭代流程跑通。从那以后我每次拿到新的工业数据集都强制自己先跑一遍类别统计和尺寸校验再想清楚这数据是用于分类还是检测两个问题想透后面能省下大量返工。希望帮到你。本文还有配套的精品资源点击获取
