东北大学热轧带钢表面缺陷数据集:从解压到缺陷分类基线实战

发布时间:2026/10/28 22:04:22
东北大学热轧带钢表面缺陷数据集:从解压到缺陷分类基线实战

简介:东北大学热轧带钢表面缺陷数据集面向材料、机械与机器视觉方向的研究人员和学生,用于热轧带钢表面缺陷的识别与分类研究,可支撑深度学习模型训练与工业质检算法验证。压缩包为7z格式,共约2000个文件,以1800张jpg图像和1800个xml标注文件为主,图像用于呈现裂纹、氧化皮、夹杂、凹坑等缺陷形态,xml则提供对应的目标框与类别标注,可直接用于监督学习与目标检测任务,整体约251.02MB。目前已有1333人学习下载,说明该数据集在缺陷检测领域具有较高的参考价值。读者可据此搭建卷积神经网络或目标检测模型,完成数据预处理、训练集与验证集划分、缺陷分类与定位实验,并对比不同算法在真实工业图像上的表现,为产线自动化检测与工艺优化提供数据支撑。

1. 东北大学热轧带钢表面缺陷数据集:从拿到 .7z 到跑通第一个缺陷分类器

你从师兄的硬盘里拷来一个 7z 压缩包,文件名写着「东北大学热轧带钢表面缺陷数据集」,解压出来一堆按类别分好的灰度图,然后呢?很多人卡在这一步:知道这是热轧带钢表面缺陷检测领域最常用的公开数据之一,却不知道它到底包含什么、标签怎么组织、能不能直接喂给 YOLO 或 ResNet。这篇笔记就按一线做缺陷检测的流程,把「拿到 .7z → 解压校验 → 目录解析 → 转成训练格式 → 跑通基线 → 避开标注坑」整条链路讲清楚。适合刚接触工业表面缺陷检测的学生、需要快速搭基线的算法工程师,以及想评估这个数据集是否匹配自己产线场景的从业者。热轧带钢表面缺陷数据集不是万能钥匙,但它是你验证检测思路成本最低的起点。

2. 先搞清楚这个数据集里到底有什么:六类缺陷与目录结构

2.1 热轧带钢表面缺陷的六种典型类别

东北大学热轧带钢表面缺陷数据集(常被简称为 NEU 热轧带钢数据集)采集自热轧产线的表面检测系统,图像为灰度图,分辨率通常为 200×200 像素。它覆盖六类最常见的热轧缺陷:轧制氧化皮(rolled-in scale)、斑块(patches)、开裂(crazing)、凹坑(pitted surface)、夹杂(inclusion)、划痕(scratches)。每类通常有若干张样本,整体规模在千级到两千级之间,属于典型的小样本工业数据集。

这个规模意味着什么?它不适合从头训练一个大模型,但非常适合做迁移学习、做传统特征工程对比、做数据增强策略验证。我一般会把它当作「算法选型试验台」:同一个 backbone,换不同的预处理和增强,看哪组在六类上的混淆矩阵最干净。热轧带钢的缺陷有个特点——类间差异小、类内差异大,比如斑块和氧化皮在低对比度下肉眼都难分,这正是它作为 benchmark 的价值所在。

2.2 解压后先别急着写代码,用三条命令看清目录

拿到 .7z 后,第一步不是打开 PyCharm,而是在 Linux 或 WSL 下把结构摸清楚。7z 在 Linux 下需要 p7zip 支持,先确认工具在位,再解压并统计。

# 安装 p7zip(Debian/Ubuntu 系) sudo apt-get install p7zip-full # 查看压缩包内文件列表,不解压,先确认层级 7z l NEU_NEU-DET.7z | head -50 # 解压到指定目录,保留原始目录结构 7z x NEU_NEU-DET.7z -o./neu_data # 统计每个类别目录下的图片数量 find ./neu_data -type d -name "*" -exec sh -c 'echo "$1: $(ls -1 "$1" | wc -l)"' _ {} \;

7z l只列目录,能在解压前判断压缩包是不是嵌套了两层、有没有多余的 macOS 元数据目录。7z x的-o参数指定输出路径,注意-o和路径之间没有空格,这是 7z 命令的固定写法,写错会报参数错误。最后那条find用来核对每类样本数,如果某一类明显偏少,后面做分层采样时就要留意。

提示:如果压缩包内是中文目录名,在 Windows 解压后再传到 Linux 可能出现编码乱码,建议直接在 Linux 下用 7z 解压,或者解压后统一重命名为英文类别名,避免后续 DataLoader 读路径时报错。

2.3 目录命名与标签映射的常见组织方式

解压后典型结构是train/images和train/annotations分离,或者按类别直接分文件夹。前者对应目标检测(每张图有 XML 或 JSON 标注框),后者对应分类任务。你需要先确认拿到的是哪种版本:分类版每类一个文件夹,检测版则带边界框标注文件。

如果是检测版,标注格式常见为 PASCAL VOC 的 XML,里面记录了每个缺陷的bndbox。这时候类别名到 id 的映射必须固定下来,不能靠os.listdir的顺序,因为不同机器上目录返回顺序可能不同,会导致训练时标签错位——这是最隐蔽的翻车点之一。我一般会手写一个class_to_id字典,写死顺序,训练和推理共用同一份。

3. 把 .7z 变成能训练的数据:解压、校验与格式转换

3.1 解压后的完整性校验:别让坏图混进训练集

工业数据集在拷贝、压缩、传输过程中出现截断或损坏的概率不低。如果直接丢给 DataLoader,轻则报PIL.UnidentifiedImageError,重则训练到一半崩掉。所以解压后第一件事是遍历所有图片,用 PIL 尝试打开并验证。

import os from PIL import Image def validate_images(root_dir): bad_files = [] for dirpath, _, filenames in os.walk(root_dir): for fname in filenames: if not fname.lower().endswith(('.png', '.jpg', '.bmp')): continue fpath = os.path.join(dirpath, fname) try: with Image.open(fpath) as img: img.verify() # 只检查文件头,不解码全部像素 except Exception as e: bad_files.append((fpath, str(e))) return bad_files if __name__ == "__main__": bad = validate_images("./neu_data") print(f"损坏文件数: {len(bad)}") for p, err in bad: print(p, err)

img.verify()只校验文件完整性,速度快,适合大批量筛查。注意verify()之后图像对象不能再用,要重新open。如果坏图数量少,直接删除并在日志里记录;如果成批损坏,说明压缩包本身有问题,需要重新获取。这一步花两分钟,能省掉后面几小时的排查。

3.2 从分类目录到 ImageFolder 可读结构

分类版最省事的做法是让目录结构直接符合torchvision.datasets.ImageFolder的要求:根目录下每个子目录是一个类别,子目录里放该类图片。如果原始结构已经是这样,直接指向根目录即可;如果不是,用脚本重排。

import os import shutil SRC = "./neu_data/raw" DST = "./neu_data/classified" CLASSES = ["crazing", "inclusion", "patches", "pitted_surface", "rolled-in_scale", "scratches"] for cls in CLASSES: os.makedirs(os.path.join(DST, cls), exist_ok=True) # 假设原始图片名里带类别前缀,按前缀归位 for fname in os.listdir(SRC): for cls in CLASSES: if fname.startswith(cls): shutil.copy(os.path.join(SRC, fname), os.path.join(DST, cls, fname)) break

ImageFolder会按子目录名的字母序生成class_to_idx,所以类别顺序由目录名决定,不由你代码里的列表决定。如果你后续要固定标签顺序,要么重命名目录加数字前缀(如0_crazing),要么不用ImageFolder,自己写 Dataset。我倾向后者,因为工业项目里标签顺序往往要和产线 MES 系统对齐,不能依赖字母序。

3.3 检测版标注转 YOLO 格式:坐标归一化与边界坑

如果拿到的是带 XML 的检测版,要转成 YOLO 的 txt 格式,每行class_id cx cy w h,全部归一化到 0~1。转换脚本本身不长,但边界处理容易出错。

import os import xml.etree.ElementTree as ET from PIL import Image CLASS_MAP = {"crazing": 0, "inclusion": 1, "patches": 2, "pitted_surface": 3, "rolled-in_scale": 4, "scratches": 5} def voc_to_yolo(xml_path, img_dir, out_dir): tree = ET.parse(xml_path) root = tree.getroot() fname = root.find("filename").text img_path = os.path.join(img_dir, fname) w, h = Image.open(img_path).size lines = [] for obj in root.findall("object"): cls_name = obj.find("name").text.strip() if cls_name not in CLASS_MAP: continue 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, ymin = max(0, xmin), max(0, ymin) xmax, ymax = min(w, xmax), min(h, ymax) cx = (xmin + xmax) / 2.0 / w cy = (ymin + ymax) / 2.0 / h bw = (xmax - xmin) / w bh = (ymax - ymin) / h lines.append(f"{CLASS_MAP[cls_name]} {cx:.6f} {cy:.6f} {bw:.6f} {bh:.6f}") out_path = os.path.join(out_dir, fname.replace(".jpg", ".txt")) with open(out_path, "w") as f: f.write("\n".join(lines))

关键点在max(0, xmin)和min(w, xmax):有些标注框会超出图像边界一两个像素,不裁剪的话归一化后中心点和宽高可能为负或大于 1,YOLO 训练时直接报NaN。另外w, h必须从实际图像读取,不能硬编码 200×200,因为数据集里可能混有不同尺寸的图。转换完建议随机抽 20 张可视化核对,别全信脚本。

4. 跑通第一个基线:从 DataLoader 到混淆矩阵

4.1 用 PyTorch 搭一个最小分类训练循环

在 PyCharm 里新建项目后,把解压好的classified目录挂进来,先跑一个 ResNet18 迁移学习的基线。这个基线不追求 SOTA,只用来确认数据管道通畅、标签没错位。

import torch import torch.nn as nn from torch.utils.data import DataLoader from torchvision import datasets, transforms, models transform = transforms.Compose([ transforms.Grayscale(num_output_channels=3), # 灰度转三通道适配预训练权重 transforms.Resize((224, 224)), transforms.ToTensor(), transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]), ]) dataset = datasets.ImageFolder("./neu_data/classified", transform=transform) loader = DataLoader(dataset, batch_size=32, shuffle=True, num_workers=4) model = models.resnet18(weights=models.ResNet18_Weights.DEFAULT) model.fc = nn.Linear(model.fc.in_features, 6) model = model.cuda() criterion = nn.CrossEntropyLoss() optimizer = torch.optim.Adam(model.parameters(), lr=1e-4) for epoch in range(10): model.train() for imgs, labels in loader: imgs, labels = imgs.cuda(), labels.cuda() optimizer.zero_grad() loss = criterion(model(imgs), labels) loss.backward() optimizer.step() print(f"epoch {epoch} done")

Grayscale(num_output_channels=3)这步不能省,因为 ResNet 预训练权重期望三通道输入,直接喂单通道会报维度错误。lr=1e-4是迁移学习的常用起点,太大容易把预训练特征打散。num_workers=4在 Windows 下如果报错,改成 0 用主进程加载。这个循环跑通只说明管道没问题,不代表模型好,下一步要看混淆矩阵。

4.2 关键参数怎么设:学习率、批大小与增强强度

小样本工业数据集上,参数设置比模型结构影响更大。下面这张表是我在 NEU 热轧带钢数据上反复试出来的经验区间,不是绝对最优,但能让你少走弯路。

参数推荐区间说明
学习率1e-4 ~ 5e-4迁移学习用 Adam 时取小值,从头训练可到 1e-3
批大小16 ~ 32样本少时批太大梯度噪声小,反而容易过拟合
输入尺寸224×224原图 200×200,放大到 224 适配预训练
水平翻转开启缺陷方向无强语义,翻转是安全增强
旋转±15° 以内超过 15° 可能把划痕方向语义破坏
颜色抖动关闭或极弱灰度图做颜色抖动意义不大

增强强度是双刃剑。热轧带钢的划痕有方向性,垂直翻转可能把「纵向划痕」变成训练集里不存在的模式,我一般只开水平翻转加小角度旋转。如果你发现验证集准确率远低于训练集,先别加正则,先检查增强是不是过猛。

4.3 用混淆矩阵定位「斑块 vs 氧化皮」的混淆

六类里最容易混的是 patches 和 rolled-in_scale,两者在灰度图上都是不规则暗斑。训练完一定要画混淆矩阵,不要只看总体准确率。

from sklearn.metrics import confusion_matrix import seaborn as sns import matplotlib.pyplot as plt model.eval() all_preds, all_labels = [], [] with torch.no_grad(): for imgs, labels in loader: preds = model(imgs.cuda()).argmax(dim=1).cpu() all_preds.extend(preds.tolist()) all_labels.extend(labels.tolist()) cm = confusion_matrix(all_labels, all_preds) sns.heatmap(cm, annot=True, fmt="d", xticklabels=dataset.classes, yticklabels=dataset.classes) plt.savefig("confusion.png", dpi=150)

如果混淆矩阵显示这两类互相误判严重,说明模型学到的是全局灰度分布而非局部纹理。解决办法有两个方向:一是换更强的局部特征提取(如加入注意力模块),二是回到数据层面,检查这两类的样本是否存在标注不一致。我遇到过同一张图在两个版本里被标成不同类的情况,这种脏数据不清理,调模型是白费力气。

5. 避坑与排查:热轧带钢数据集上最容易翻车的五件事

5.1 现象:训练 loss 正常下降但验证准确率卡在 16% 左右

原因:16% 约等于 1/6,说明模型在预测单一类别,通常是标签映射错位或ImageFolder类别顺序与你的评估代码不一致。解决:打印dataset.class_to_idx,和你的CLASS_MAP逐项核对;评估时直接用dataset.classes的顺序,不要另写一份列表。

5.2 现象:DataLoader 报UnidentifiedImageError中断训练

原因:解压不完整或传输损坏的图片混在目录里。解决:跑 3.1 节的校验脚本,删除坏图;如果坏图比例超过 5%,重新解压或重新获取压缩包,不要逐张修复。

5.3 现象:7z 在 Linux 下解压报「Cannot open the file as archive」

原因:压缩包在传输中被截断,或者用了不兼容的压缩算法。解决:先用7z t测试压缩包完整性,确认文件大小与来源一致;如果是分卷压缩,确保所有分卷在同一目录再解压。

5.4 现象:PyCharm 里运行正常,命令行跑报找不到数据路径

原因:PyCharm 默认工作目录是项目根,命令行是你当前所在目录,相对路径./neu_data解析结果不同。解决:统一用os.path.dirname(os.path.abspath(__file__))拼绝对路径,或者在脚本开头os.chdir到固定目录。这是最没技术含量但最常浪费时间的坑。

5.5 现象:验证集准确率虚高,换一批图就崩

原因:同一张原图经过增强后同时出现在训练和验证集,造成数据泄漏。解决:划分数据集时按原图划分,先分 train/val 再各自做增强;不要先增强再随机划分。小样本数据集上这个错误能让准确率虚高十几个点。

6. 进阶技巧:用分层采样和类别权重把召回率提上来

基线跑通后,真正决定这个数据集能不能支撑你产线评估的,是少数类召回率。热轧带钢六类里,crazing 和 inclusion 样本往往偏少,模型倾向于牺牲它们保整体准确率。我的习惯是两步走:先用分层采样保证每个 batch 里六类都有,再给损失函数加类别权重。

from torch.utils.data import WeightedRandomSampler import numpy as np targets = [s[1] for s in dataset.samples] class_counts = np.bincount(targets) class_weights = 1.0 / class_counts sample_weights = [class_weights[t] for t in targets] sampler = WeightedRandomSampler(sample_weights, len(sample_weights)) loader = DataLoader(dataset, batch_size=32, sampler=sampler, num_workers=4) # 损失函数侧再加一层权重 weight_tensor = torch.tensor(class_weights / class_weights.sum() * 6, dtype=torch.float32).cuda() criterion = nn.CrossEntropyLoss(weight=weight_tensor)

WeightedRandomSampler让少数类被抽到的概率提高,CrossEntropyLoss的weight参数则在梯度层面放大少数类损失。两者叠加要小心,可能让多数类欠拟合,建议先只开 sampler,看混淆矩阵变化,再决定要不要加 loss 权重。验证时不要只看总体准确率,按类打印 recall,尤其盯住你产线上最不能漏检的那一类。

还有一个容易被忽略的点:这个数据集是灰度图,但很多预训练权重是在 ImageNet 彩色图上训的。除了前面说的复制三通道,还可以试试先用数据集自身做自监督预训练(如 SimCLR 的简化版),再微调分类头。我在小样本上试过,比直接拿 ImageNet 权重涨两三个点,代价是要多写一套增强管道。值不值得做,取决于你对这两个点的精度是否敏感。

最后说个习惯:每次改完数据管道或增强策略,先跑 3 个 epoch 看混淆矩阵,不要等跑完 50 个 epoch 才发现标签错了。热轧带钢数据集体量小,试错成本低,但前提是你把校验和可视化做在前面。希望帮到你。

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