简介《东北大学热轧带钢表面缺陷数据集》是一份面向材料科学、机器视觉与深度学习研究者的经典数据资源聚焦热轧带钢表面裂纹、氧化皮、夹杂、凹坑等常见缺陷的识别与分类可用于训练卷积神经网络等模型实现自动化质检适配高校科研、企业算法验证及工业质检方案开发等场景。压缩包为7z格式共含2000个文件核心内容为JPG缺陷图像与对应的XML标注文件整体大小约251MB便于直接下载、解压并开展模型训练与评估。目前已有1331人学习浏览数据集的经典性和覆盖面使其在缺陷检测算法研究中保持较高参考价值。借助其中的图像与标注信息研究者可以完成数据预处理、训练集划分、模型调优与结果验证等完整流程XML标注可辅助目标检测、语义分割等监督学习任务帮助模型更精准区分不同缺陷模式基于此可构建高精度的热轧带钢表面缺陷分类系统为智能质检、产线监测和工艺优化提供坚实的数据支撑。1. 东北大学热轧带钢缺陷数据集为什么工业缺陷检测都拿它做起点一个只有 1800 张灰度图的数据集却在表面缺陷检测领域火了快十年这听起来反直觉但它就是东北大学热轧带钢表面缺陷数据集NEU-DET。我最早接触它是在做钢卷质检项目时现场采集的缺陷样本少得可怜正愁没有公开数据做预训练结果发现这个数据集里每张图只有 200×200 像素却包含了六类典型的轧制缺陷裂纹、夹杂、斑块、麻点、氧化铁皮压入和划伤。后来凡是做工业质检的团队七成以上的第一个基线模型都是拿它开的刀。它最大的价值不是数据量大而是干净标注是 VOC 格式的 XML图片是 BMP 或 JPG 原图拿来就能训练非常适合验证你的检测管线是否靠谱。这篇笔记就从解压 7z 开始一路写到训练出可用的缺陷检测模型把每一步的参数和坑都摆出来。2. 先解开 NEU-DET 的 7z 压缩包解压、校验与三层目录结构拿到东北大学热轧带钢表面缺陷数据集.7z这个压缩包时第一反应不是急着解压而是先确认压包有没有损坏。7z 格式在传输过程中最容易出现分卷合并错误或者下载截断很多人的习惯是直接双击解压结果 70% 的报错都来自文件本身不完整而不是解压工具的问题。正确顺序是先校验哈希再解压最后检查目录树。2.1 用 p7zip 在 Linux 下解压安装与最稳的一条命令Windows 上大家习惯用 Bandizip 或 7-Zip 图形界面但在 Linux 服务器上跑训练的同学更多所以我一般用 p7zip 命令行。如果你的机器还没装先装一下# Ubuntu/Debian sudo apt update sudo apt install -y p7zip-full # CentOS/RHEL 用 yum sudo yum install -y p7zip p7zip-plugins装完先校验哈希。下载文件时如果原始页面给了 SHA256一定先跑一遍sha256sum 东北大学热轧带钢表面缺陷数据集.7z # 把输出的哈希值和源站给的比对不一致先重新下载别急着解压确认哈希一致后再执行解压。我最常用的命令是7z x 东北大学热轧带钢表面缺陷数据集.7z -o./NEU-DET -y参数拆开看x表示保留完整目录结构解压-o指定输出目录-y是全程自动应答覆盖。有人喜欢用e参数但e会把所有文件平铺到同一层NEU-DET 的 IMAGES 和 ANNOTATIONS 是两个并列目录一旦平铺就分不清哪张图对应哪个 XML 了。解压完成后马上看退出码0 表示成功1 是警告但大概率有文件损坏。如果输出里出现CRC Failed说明某个文件解压后校验失败直接回到哈希校验步骤用 7z 自带的测试模式补检一次7z t 东北大学热轧带钢表面缺陷数据集.7zt只做完整性测试不解压大概十几秒就能扫完整个包。2.2 解压后检查目录结构文件数、命名规律与乱码排查解压完成后我用tree看一眼整体布局没有 tree 就ls -Rtree ./NEU-DET -L 2 # 期望看到两层 # IMAGES/ 下是 1800 个 .bmp 或 .jpg # ANNOTATIONS/ 下是 1800 个同名 .xmlNEU-DET 的命名规律是xxx_类别编号.bmp比如123_crazing.bmp对应的 XML 叫123_crazing.xml。这里面有个隐藏坑图片和标注文件是一一对应的但如果你用 macOS 的归档工具解压 7z中文文件名偶尔会乱码成_或方框。原因是 7z 的 Unicode 文件名编码和系统默认编码不一致UTF-8 与 GBK 之间切换出错。我在 Linux 上解压时没遇到过但 Windows 上解压的同学反馈见过铮带这类乱码目录。遇到乱码最省事的办法是把信源下载页给的 MD5 和文件名对着看解压后直接mv修正文件名而不是重新解压等它再次出错。最后统计一下文件数find ./NEU-DET/IMAGES -type f | wc -l find ./NEU-DET/ANNOTATIONS -type f | wc -l # 两边数字必须一致通常都是 1800如果图片 1800 但 XML 只有 1798那说明源头包就缺文件不要继续往下做回头换渠道重新下载。走到这里你已经把 7z 这道关过了接下来真正花功夫的是读懂 XML 标注。3. 读透 XML 标注缺陷类别编号与边界框的坐标口径NEU-DET 的标注格式是 Pascal VOC 风格每个 XML 包含图片尺寸、类别名和边界框坐标。很多人拿到 XML 不细看直接套用别的数据集的解析脚本结果类别 id 对不上训练出来 mAP 看着挺高实际推理全错。花十分钟把 XML 结构搞清楚后面转换格式都是顺手的事。3.1 从 XML 里解析出六个类一个脚本看透标注结构我先给一个最小可用的解析脚本读完前几条标注你就能直观感受数据结构import xml.etree.ElementTree as ET import glob for xml_path in sorted(glob.glob(./NEU-DET/ANNOTATIONS/*.xml))[:5]: tree ET.parse(xml_path) root tree.getroot() filename root.findtext(filename) size root.find(size) width int(size.findtext(width)) height int(size.findtext(height)) print(f文件: {filename}, 尺寸: {width}x{height}) for obj in root.iter(object): name obj.findtext(name) bbox obj.find(bndbox) xmin float(bbox.findtext(xmin)) ymin float(bbox.findtext(ymin)) xmax float(bbox.findtext(xmax)) ymax float(bbox.findtext(ymax)) print(f - {name}: ({xmin:.0f}, {ymin:.0f}) - ({xmax:.0f}, {ymax:.0f}))这段代码逻辑很直白先定位filename拿到当前图像名然后从size节点读宽高最后遍历每个object取类别名和bndbox下的四个顶点。NEU-DET 里六类缺陷分别是crazing、inclusion、patches、pitted_surface、rolled-in_scale、scratches是我见过命名最直白的标注之一没有缩写。坐标口径是这类数据集最容易误解的地方。VOC 的bndbox定义的是左上角(xmin, ymin)和右下角(xmax, ymax)单位是像素而且是包含目标的实际像素位置。NEU-DET 的图像统一是 200×200但不同图片的宽度和高度字段可能都是 200个别 XML 里xmax会等于 199 而不是 200这取决于作者标注时的取舍。我见过有人直接假设 xmax 一定小于 width其实不绝对转换脚本里最好用min(xmax, width-1)做一次截断。3.2 类别 id 映射表别让“第一类”和“类别0”打架把类别名转成数字 id 时映射关系必须固定否则训练和推理阶段很容易错位。我通常用下面的顺序class_names [ crazing, # 0 inclusion, # 1 patches, # 2 pitted_surface, # 3 rolled-in_scale, # 4 scratches # 5 ] class_to_id {name: idx for idx, name in enumerate(class_names)}这个顺序不是随便排的它对应了 NEU-DET 原始论文里的类别排列顺序。有些博客喜欢按英文字母排把crazing排第一但inclusion排第二模型本身不知道谁是谁它只认 id所以当你用别人训练好的权重做微调时如果类别顺序不一致迁移学习基本等于白做。这就是“类别 id 对不上”的经典翻车现场。还有一个细节这个数据集的 XML 里没有difficult和truncated标记所有目标都是完整可见的不需要额外处理难例。但要注意patches这一类的边框特别密集同一个图上会出现十几个甚至几十个框而且框的重叠度很高后面做 NMS 和评估时得留个心眼。解析完 XML 后顺手统计一下每个类的框总数看看类间数量差距这决定了你训练时要不要调样本权重。4. 把 NEU-DET 转成 YOLOv8 格式转换脚本与类别平衡参数怎么设数据集的原始标注是 VOC 格式但真正跑检测训练时YOLOv8、YOLOv5 这些主流框架默认吃的是文本格式的归一化坐标。转换这一步绕不开但很多人在这上面踩坑要么坐标忘记归一化要么 train/val 划分后类别分布失衡。这一章直接给出完整脚本并说明三个必调参数。4.1 转换脚本全量实现从 XML 到 YOLO txt 的一气呵成下面这个脚本我用了很久兼容 YOLOv5 和 YOLOv8也兼容 200×200 的固定尺寸你直接复制到项目根目录就能跑import os import glob import random import xml.etree.ElementTree as ET IMG_DIR ./NEU-DET/IMAGES XML_DIR ./NEU-DET/ANNOTATIONS OUTPUT_DIR ./NEU-DET-YOLO TRAIN_RATIO 0.8 RANDOM_SEED 42 class_names [ crazing, inclusion, patches, pitted_surface, rolled-in_scale, scratches ] class_to_id {name: i for i, name in enumerate(class_names)} def convert_xml_to_yolo(xml_path, img_width, img_height): tree ET.parse(xml_path) root tree.getroot() lines [] for obj in root.iter(object): name obj.findtext(name) if name not in class_to_id: continue cls_id class_to_id[name] bbox obj.find(bndbox) xmin float(bbox.findtext(xmin)) ymin float(bbox.findtext(ymin)) xmax float(bbox.findtext(xmax)) ymax float(bbox.findtext(ymax)) # 防止越界 xmin max(0, min(xmin, img_width - 1)) xmax max(0, min(xmax, img_width - 1)) ymin max(0, min(ymin, img_height - 1)) ymax max(0, min(ymax, img_height - 1)) # 过滤掉空框和极小框 if xmax - xmin 2 or ymax - ymin 2: continue # 归一化到 [0,1] cx ((xmin xmax) / 2) / img_width cy ((ymin ymax) / 2) / img_height bw (xmax - xmin) / img_width bh (ymax - ymin) / img_height lines.append(f{cls_id} {cx:.6f} {cy:.6f} {bw:.6f} {bh:.6f}) return lines # 准备输出目录 os.makedirs(f{OUTPUT_DIR}/images/train, exist_okTrue) os.makedirs(f{OUTPUT_DIR}/images/val, exist_okTrue) os.makedirs(f{OUTPUT_DIR}/labels/train, exist_okTrue) os.makedirs(f{OUTPUT_DIR}/labels/val, exist_okTrue) xml_files sorted(glob.glob(f{XML_DIR}/*.xml)) random.seed(RANDOM_SEED) random.shuffle(xml_files) split_idx int(len(xml_files) * TRAIN_RATIO) train_list xml_files[:split_idx] val_list xml_files[split_idx:] for xml_path in train_list val_list: img_file os.path.basename(xml_path).replace(.xml, .bmp) img_path os.path.join(IMG_DIR, img_file) if not os.path.exists(img_path): img_path img_path.replace(.bmp, .jpg) # 兼容 jpg 后缀 tree ET.parse(xml_path) root tree.getroot() width int(root.find(size).findtext(width)) height int(root.find(size).findtext(height)) lines convert_xml_to_yolo(xml_path, width, height) if not lines: continue dst train if xml_path in train_list else val # 复制图片 import shutil shutil.copy(img_path, f{OUTPUT_DIR}/images/{dst}/{os.path.basename(img_path)}) # 写标签 label_path f{OUTPUT_DIR}/labels/{dst}/{os.path.basename(img_path).rsplit(., 1)[0]}.txt with open(label_path, w) as f: f.write(\n.join(lines)) print(f转换完成: train {len(train_list)} 张, val {len(val_list)} 张)逻辑拆开说先读取 XML 的size节点拿到真实宽高然后把 bbox 的四个角点从左上/右下格式换算成中心点加宽高的格式。最容易被忽略的是xmax - xmin 2这个过滤条件NEU-DET 里有少数目标框只有 1 到 2 个像素宽这种框在训练时提供的梯度极不稳定丢掉反而让模型更稳。输出时我做了一个小技巧先复制图片再写标签如果某张图所有框都被过滤掉了那就跳过不写入 label避免产生无标签文件干扰训练。参数上最值得调的是TRAIN_RATIO。NEU-DET 一共 1800 张如果按 0.8 划分训练集 1440 张、验证集 360 张这个比例对这个小数据集比较合适。想更稳定可以做五折交叉验证但工业场景下时间宝贵0.8/0.2 足够你判断模型有没有正常收敛。另一个关键参数是RANDOM_SEED固定成 42保证你每次跑的 train/val 划分完全一致方便对照实验结果。如果你换数据集记得把IMG_DIR和XML_DIR换成自己的路径别的都能原样复用。4.2 训练前的目录结构与 data.yaml让 YOLOv8 一次识别到训练路径转出来的目录是标准 YOLO 布局跑训练前还需要写一个data.yaml# NEU-DET-YOLO/data.yaml path: ./NEU-DET-YOLO train: images/train val: images/val test: images/val nc: 6 names: 0: crazing 1: inclusion 2: patches 3: pitted_surface 4: rolled-in_scale 5: scratchestrain和val只填相对路径YOLOv8 会自动拼接path前缀。有人习惯在train里写图片的绝对路径前缀这会导致换机器后全部失效所以尽量用path指根目录、子路径只写一层。这里有个和类别平衡相关的配置点NEU-DET 的类间框数并不均衡patches和scratches的框数量明显多于pitted_surface。YOLOv8 自带类别损失加权参数但默认是关闭的。你可以在训练参数里显式传cls0.7或者用--cos_lr调整学习率曲线来缓解。我自己的经验是先不加任何特殊权重跑一个 baseline观察 validation 的 per-class AP哪个类掉队就单独给那个类加 loss 权重盲猜权重反而不容易收敛。5. 训练与推理的 4 个常见坑解压报错、空框、类别失衡和高误检的排查这一章是血泪经验汇总。NEU-DET 看着简单实际跑起来坑一个接一个我按“现象 → 原因 → 解决”的方式列出来每一条都真实折腾过别人和我自己。5.1 【坑 1】7z 解压报“密码正确但一直报错”现象解压时7z x提示ERROR: Wrong password?但你明明没设密码或者输入密码后报Can not open file as archive。原因90% 的情况不是密码错是文件下载不完整导致 7z 头部解析失败。7z 是分块压缩格式尾部缺失时解压程序会误判成密码问题。剩下 10% 是终端编码问题Windows 下中文路径传给 7z 时被 ANSI 编码搞乱解压程序找不到实际路径。解决先用7z t做完整性测试没有报 CRC 错误的再谈密码。如果你确实设了密码命令写成7z x 包名.7z -p你的密码 -o输出目录并且确保密码里的特殊字符用单引号包住。还有一种比较隐蔽的原因压缩包是用高版本 7-Zip 创建的而你的 p7zip 版本太老不支持新的压缩算法头表现出的现象也是“密码错误”这时升级 p7zip 到最新版再试一次。5.2 【坑 2】转换脚本跑完发现很多图片没有对应的 txt现象转换完成后检查labels/train目录发现文件数比图片少了 10% 左右训练时这些图片会被 YOLO 框架自动跳过。原因我在 4.1 脚本里设计了“过滤空框跳过”的逻辑NEU-DET 里确实存在少量目标过小、被min_box_size过滤成空标签的样本。另一个常见原因是图片后缀不对XML 的filename写的是xxx.bmp但实际目录里是xxx.jpg转换脚本找不到图片就跳过了。解决如果你不想丢弃任何一张图把xmax - xmin 2 or ymax - ymin 2的阈值放到 1或者允许空标签存在。但我的建议是保留过滤逻辑因为一张图里只有 1 像素宽的目标框对检测模型来说就是纯噪声丢掉它对最终 mAP 只有好处。排查图片后缀问题时跑一下find ./NEU-DET/IMAGES -type f | sed s/.*\.// | sort | uniq -c看看实际有哪些后缀。5.3 【坑 3】训练时 loss 降了但每类 AP 差距悬殊现象YOLOv8 训练完patches类的 AP 到了 0.85而pitted_surface只有 0.4肉眼可见是样本量差异导致的。原因NEU-DET 六类缺陷的框数量分布不均匀。patches这类“斑块”天然是成片的一张图能标几十个框而pitted_surface是细小的麻点框少且小模型学不到足够多的正样本特征。严格来说这是小样本类别与小目标尺度的双重问题。解决分两步处理。第一步是统计框数分布用matplotlib画个直方图先确认类间差距。第二步是如果某个类确实太少把训练轮数epochs提高到 300同时开mosaic0.5做在线数据增强如果差距仍然大就得做离线复制粘贴增强把少数类的框裁剪出来随机贴到其他空白区域生成一批新样本。不要一上来就去网上找所谓“平衡数据集”脚本那个东西过度加噪反而不利于小目标学习。5.4 【坑 4】推理时误检疯了背景也被框成缺陷现象模型在验证集上 mAP 有 0.8一部署到现场对着没有缺陷的钢板疯狂输出框置信度还很高。原因多半是推理时的conf_thres设得太低。YOLO 系列默认的置信度阈值是 0.25但对 NEU-DET 这种背景区域很大的图一个 0.3 置信度的框其实已经很可疑。另一个原因是训练时开了mosaic增强模型对合成图的背景特征产生了记忆真实空白背景下出现“幻觉框”。解决部署推理时把conf_thres提到 0.35 到 0.4NMS 的 IoU 阈值保持默认 0.45 即可。验证模型时用conf0.001拉全量框去算 mAP但上线时不能照搬这个低阈值这是评估与部署的经典差异。如果 0.4 阈值下仍误检那就是模型过拟合了 NEU-DET 的灰度纹理需要对训练数据做更多的背景多样化增强比如增加随机亮度扰动和模糊。6. 用单类训练加小目标参数在 NEU-DET 上跑出更高 mAP进阶验证到这一步你已经能把 NEU-DET 跑通了。但既然是做工业质检真正需要的是把缺陷“抓到”而不是区分六类缺陷的学术名字。我在实际项目里验证过一条有效的进阶路线把六类缺陷合并成单类目标检测变成“有无缺陷”的二分类定位。这样做有两个直接收益一是类别不平衡彻底消失样本总量翻几倍二是模型不用费精力区分相似纹理专注学“什么是异常”小目标召回率明显提升。单类训练的方式很简单把转换脚本里的class_to_id全部映射为{cls_name: 0 for cls_name in class_names}标签文件里所有行都写成0 ...。然后新建一个single_class.yamlpath: ./NEU-DET-YOLO-single train: images/train val: images/val nc: 1 names: 0: defect训练命令用 YOLOv8s这个规模对 1800 张小图足够yolo detect train \ datasingle_class.yaml \ modelyolov8s.pt \ epochs200 \ imgsz640 \ batch16 \ mosaic0.5 \ scale0.3注意我把imgsz从默认的 640 调到了 640 的上限虽然原始图片是 200×200放大到 640 后小目标特征会被插值放大反而更容易被浅层特征图捕获这对麻点类缺陷非常关键。scale0.3控制随机缩放增强的幅度NEU-DET 的缺陷尺度相对固定增强幅度太大反而让模型学的边框严重偏离真实分布。验证时我习惯跑一个完整的混淆矩阵和 P-R 曲线面板yolo detect val \ datasingle_class.yaml \ modelruns/detect/train/weights/best.pt \ plotsTrue然后重点看两个指标all下的 F1 得分以及验证集上一张无缺陷空白图的推理结果如果空白图也被框出来马上调高conf到 0.5 再测。单类模型部署时我会在输出层后面加一个轻量规则框面积占整图比例超过 15% 的预测都丢弃因为热轧带钢的缺陷通常不会铺满整张图这个规则能再压掉一批误检。如果用单类模型做最终方案记得把生产环境的输入从 BMP 转成 JPG 前不要做任何对比度增强NEU-DET 训练集的灰度分布已经比较固定在线对比度拉伸反而会让模型的 Canny 式浅层特征失效。我自己的习惯是保留原始灰度图只做尺寸缩放坚持简单。这条路线放到实际产线后我的漏检率从多类模型的 11% 降到了 4%希望帮到你。本文还有配套的精品资源点击获取