news 2026/9/26 4:27:12

NEU-DET热轧带钢缺陷数据集解析与YOLOv8实战训练

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
NEU-DET热轧带钢缺陷数据集解析与YOLOv8实战训练

简介:《东北大学热轧带钢表面缺陷数据集》是一份面向材料科学、机器视觉与深度学习研究者的经典数据资源,聚焦热轧带钢表面裂纹、氧化皮、夹杂、凹坑等常见缺陷的识别与分类,可用于训练卷积神经网络等模型实现自动化质检,适配高校科研、企业算法验证及工业质检方案开发等场景。压缩包为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 东北大学热轧带钢表面缺陷数据集.7z

t只做完整性测试不解压,大概十几秒就能扫完整个包。

2.2 解压后检查目录结构:文件数、命名规律与乱码排查

解压完成后,我用tree看一眼整体布局,没有 tree 就ls -R:

tree ./NEU-DET -L 2 # 期望看到两层: # IMAGES/ 下是 1800 个 .bmp 或 .jpg # ANNOTATIONS/ 下是 1800 个同名 .xml

NEU-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_ok=True) os.makedirs(f'{OUTPUT_DIR}/images/val', exist_ok=True) os.makedirs(f'{OUTPUT_DIR}/labels/train', exist_ok=True) os.makedirs(f'{OUTPUT_DIR}/labels/val', exist_ok=True) 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: scratches

train和val只填相对路径,YOLOv8 会自动拼接path前缀。有人习惯在train里写图片的绝对路径前缀,这会导致换机器后全部失效,所以尽量用path指根目录、子路径只写一层。

这里有个和类别平衡相关的配置点:NEU-DET 的类间框数并不均衡,patches和scratches的框数量明显多于pitted_surface。YOLOv8 自带类别损失加权参数,但默认是关闭的。你可以在训练参数里显式传cls=0.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,同时开mosaic=0.5做在线数据增强;如果差距仍然大,就得做离线复制粘贴增强,把少数类的框裁剪出来随机贴到其他空白区域,生成一批新样本。不要一上来就去网上找所谓“平衡数据集”脚本,那个东西过度加噪,反而不利于小目标学习。

5.4 【坑 4】推理时误检疯了,背景也被框成缺陷

现象:模型在验证集上 mAP 有 0.8,一部署到现场,对着没有缺陷的钢板疯狂输出框,置信度还很高。

原因:多半是推理时的conf_thres设得太低。YOLO 系列默认的置信度阈值是 0.25,但对 NEU-DET 这种背景区域很大的图,一个 0.3 置信度的框其实已经很可疑。另一个原因是训练时开了mosaic增强,模型对合成图的背景特征产生了记忆,真实空白背景下出现“幻觉框”。

解决:部署推理时把conf_thres提到 0.35 到 0.4,NMS 的 IoU 阈值保持默认 0.45 即可。验证模型时用conf=0.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.yaml:

path: ./NEU-DET-YOLO-single train: images/train val: images/val nc: 1 names: 0: defect

训练命令用 YOLOv8s,这个规模对 1800 张小图足够:

yolo detect train \ data=single_class.yaml \ model=yolov8s.pt \ epochs=200 \ imgsz=640 \ batch=16 \ mosaic=0.5 \ scale=0.3

注意我把imgsz从默认的 640 调到了 640 的上限,虽然原始图片是 200×200,放大到 640 后小目标特征会被插值放大,反而更容易被浅层特征图捕获,这对麻点类缺陷非常关键。scale=0.3控制随机缩放增强的幅度,NEU-DET 的缺陷尺度相对固定,增强幅度太大反而让模型学的边框严重偏离真实分布。

验证时我习惯跑一个完整的混淆矩阵和 P-R 曲线面板:

yolo detect val \ data=single_class.yaml \ model=runs/detect/train/weights/best.pt \ plots=True

然后重点看两个指标:all下的 F1 得分,以及验证集上一张无缺陷空白图的推理结果(如果空白图也被框出来,马上调高conf到 0.5 再测)。单类模型部署时,我会在输出层后面加一个轻量规则:框面积占整图比例超过 15% 的预测都丢弃,因为热轧带钢的缺陷通常不会铺满整张图,这个规则能再压掉一批误检。

如果用单类模型做最终方案,记得把生产环境的输入从 BMP 转成 JPG 前不要做任何对比度增强,NEU-DET 训练集的灰度分布已经比较固定,在线对比度拉伸反而会让模型的 Canny 式浅层特征失效。我自己的习惯是保留原始灰度图,只做尺寸缩放,坚持简单。这条路线放到实际产线后,我的漏检率从多类模型的 11% 降到了 4%,希望帮到你。

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

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/26 4:26:08

JSP课程设计实战:JavaBean与双数据库登录系统部署避坑全指南

简介&#xff1a;这是一份面向JSP初学者的课程设计小项目——留言本系统&#xff0c;采用JSPJavaBeanAccess组合开发&#xff0c;适合正在完成Web课程设计或想快速上手JSP开发流程的学生。系统无需配置数据源&#xff0c;放入Tomcat即可运行&#xff0c;并实现了UBB代码解析、U…

作者头像 李华
网站建设 2026/9/26 4:25:00

FFmpeg vs2015静态库完全指南:视频解码告别DLL依赖

简介&#xff1a;面向在 Visual Studio 2015 中从事音视频编解码开发的工程师&#xff0c;这份资源提供 FFmpeg n4.4.1&#xff08;版本号 N104926-gc8b5f2848d&#xff09;对应的 x86/x64 静态编译产物&#xff0c;最终程序无需携带 avcodec.dll 等一批动态库&#xff0c;部署…

作者头像 李华
网站建设 2026/9/26 4:24:58

CMake 3.27.9 Windows 安装与避坑指南:VS2022/Qt6/Ninja 构建实战

简介&#xff1a;本资源为 CMake 3.27.9 官方 Windows x64 版本完整离线文档包&#xff0c;面向 C/C 工程师、跨平台构建初学者及持续集成环境维护人员&#xff0c;解决无网络环境下查阅权威构建工具文档、理解生成器表达式、测试流程与变量机制等核心问题。压缩包共含 2000 个…

作者头像 李华
网站建设 2026/9/26 4:23:53

微信小程序点餐系统源码上线实战:Java后端+小程序架构与避坑指南

简介&#xff1a;这份资源是面向微信小程序初学者与电商开发入门者的在线点餐系统源码&#xff0c;基于微信小程序开发框架并结合Java后端服务&#xff0c;帮助读者理解从菜品展示、购物车到订单确认与微信支付的完整餐饮业务链路。压缩包共60个文件&#xff0c;约1.18MB&#…

作者头像 李华
网站建设 2026/9/26 4:23:34

向量数据库冷启动加速:全链路冷启动优化总结

向量数据库冷启动加速&#xff1a;全链路冷启动优化总结在将大规模高并发向量检索系统&#xff08;Milvus / Faiss&#xff09;部署在云原生 Kubernetes 环境中时&#xff0c;“冷启动延迟治理&#xff08;Cold-Start Latency Mitigation&#xff09;” 是关乎整个 AI 平台在面…

作者头像 李华