做工业视觉这两年,我越来越认同一个判断:缺陷检测模型的门槛从来不在模型结构,而在数据。真实工厂里,想凑齐一套带标注的表面缺陷样本,往往要等产线跑几个星期;而NEU-DET数据集,几乎是每个刚接触表面缺陷检测的人都会碰到的那份“标准教材”。它不大,整体只有1800张图像,但覆盖了钢材表面最常见的六类缺陷,并且每一张都带有人工框选的标注框。这篇文章就把这份数据集的底细拆开聊一聊,从标注格式、六类缺陷的成像特点,到用它训练检测模型时我踩过的坑,以及从实验室精度到产线落地之间的落差。想用这个数据集做算法预研、发对比实验或练手的同学,看完可以直接照着做。
1. 先认识数据集本身:1800张图像里藏着的真实信息
1.1 为什么NEU-DET能成为缺陷检测方向的“入门基准”
做缺陷检测的人很难绕开这个数据集。我在好几个项目里都拿它做过第一轮算法验证。原因很直接:它免费公开、标注完整、类别划分清楚,而且缺陷种类覆盖了热轧钢材表面的主要痛点。相比那些动辄几十万张的通用目标检测数据集,NEU-DET的规模小,但跨界门槛低——哪怕你之前根本不懂冶金工艺,也能通过这份数据快速理解“表面缺陷”在图像上到底是什么形态。
更重要的是,它带的是人工精细标注的矩形框,不是整图分类标签。这意味着你可以直接用来训练Faster R-CNN、YOLO、SSD这类通用检测器,不需要额外整理格式。早期很多论文里关于钢材表面缺陷检测的实验,用的就是这份数据;后来大家在YOLOv5、YOLOv8上复现对比实验,也默认拿它当基准。它不是一个“大而全”的工业数据集,但它是一个“小而精”的算法试金石。
1.2 图像与标注的整体统计特征
我最早拿到数据时以为它会有很多子文件夹,实际上整理得相当清晰。按我手头这份数据统计,整体情况如下表:
| 项目 | 内容 |
|---|---|
| 图像总数 | 1800张 |
| 图像尺寸 | 200 × 200像素 |
| 图像通道 | 灰度图(单通道) |
| 缺陷类别数 | 6类 |
| 每类样本数 | 300张 |
| 标注格式 | PASCAL VOC XML / COCO JSON(不同版本有差异) |
| 单张缺陷数量 | 多数每张只有1个标注框,少量有多个框 |
这里有一个很容易被忽略的细节:图像只有200×200,而且是灰度图。很多刚上手的人会顺手resize成416×416或640×640,但这两个点直接决定了你的预处理方式。灰度图意味着如果你的检测器默认输入是三通道RGB,就得考虑是把灰度图复制成三通道,还是改网络输入层;200×200的小分辨率则意味着很多缺陷在图上只占很小面积,比如裂纹类缺陷的线宽可能只有2~3个像素,稍微一降采样就无影无踪。
每类300张的均衡设计在工程里其实很理想。真实产线场景里,划痕往往佔一大半,夹杂只有零星几个,模型很容易被类别不平衡带偏。但NEU-DET每个类别都是300张,做出来的结果不会因为某个类样本太少而显得不可信。这也是我拿它做基准测试时最满意的一点。
2. 六类缺陷逐个看:成因、影像特征与检测难度
2.1 一张表格看清六类缺陷的“长相”
这份数据集的六个标签分别是crazing(裂纹/龟裂)、inclusion(夹杂物)、patches(斑块)、pitted_surface(麻点)、rolled-in_scale(氧化铁皮压入)、scratches(划痕)。我刚开始做分类时经常把它们搞混,尤其是patches和rolled-in_scale,在灰度图里视觉特征确实接近。后来我把它们整理成一张对照表,再也没认错过:
| 类别(英文标签) | 中文习惯叫法 | 典型影像特征 | 主观检测难度 |
|---|---|---|---|
| crazing | 裂纹/龟裂 | 不规则网状细线,对比度极低,接近背景灰度 | 难 |
| inclusion | 夹杂 | 团块状暗色区域,边界相对清晰,形状不规则 | 中等 |
| patches | 斑块 | 大片灰度异常区域,像污渍或区域变色,边缘模糊 | 中低 |
| pitted_surface | 麻点 | 表面散布许多小凹坑,呈暗色点状,大小不一 | 中等偏难 |
| rolled-in_scale | 氧化铁皮压入 | 块状亮色或暗色区域,边界较锐利,常有层状纹理 | 中等 |
| scratches | 划痕 | 连续线状暗纹,方向相对一致,线条清晰 | 低 |
2.2 为什么crazing是几乎所有检测器的“噩梦”
在NEU-DET上做实验,最先暴露短板的基本都是crazing这一类。原因从影像特征上就能看出来:裂纹在画面上呈现为大量细碎的网状线条,线宽可能只有几个像素,灰度值跟周边的钢板背景非常接近,肉眼不加放大都容易漏看。经过检测器主干网络下采样后,这种细线特征很难保留下来。我用YOLOv8s在这个类上的mAP@0.5经常比scratches低20个百分点以上,倒不是因为模型笨,而是信息在特征提取阶段就丢了。
有个实测经验值得分享:对crazing这类缺陷,把输入分辨率从640提高到768甚至896,AP会有明显回升。代价是训练和推理速度下降,所以工程落地时要权衡。另一个有效办法是把图像对比度做一次自适应增强再进网络,相当于替模型把细纹从背景里“抠”出来。
pitted_surface(麻点)的麻烦则不太一样。它不是难检测,而是容易误检——钢板表面本来就有一些纹理和噪声,很多都会被当成麻点框出来。加上一张图里麻点缺陷往往不止一个,模型输出十几个低置信度框的情况很常见。对这类目标,后处理NMS的IoU阈值设置要更保守一点,我通常会把置信度阈值从0.25提到0.35。
3. 标注文件与数据组织:读懂XML和JSON才能真正开始训练
3.1 目录结构通常长这样
公开渠道拿到NEU-DET数据后,不同来源解压出来的目录结构会有一点差异。一种常见组织方式是图像放在images或JPEGImages目录,标注放在annotations目录;有些版本还会额外提供ImageSets,里面是train.txt和val.txt这样的文件列表。不管目录怎么摆,核心就两类文件:图像文件和每个图像对应的标注文件。文件名一一对应,比如IMG_001.jpg对应IMG_001.xml,方便程序按文件名关联。
我在开始写训练脚本前,第一件事不是看模型代码,而是写一条命令统计标注框数量、类别分布和框宽高分布。这一步能提前发现很多坑,比如某个类别的标注框是不是有坐标越界,是不是有空的XML文件,是不是有图像没有对应标注。NEU-DET本身比较干净,但网上转手的版本偶尔会缺文件,检查一下总没坏处。
3.2 VOC XML里到底写了什么
以PASCAL VOC格式为例,一个标注文件的内容大概是这样的:
<annotation> <folder>NEU-DET</folder> <filename>IMG_001.jpg</filename> <source> <database>NEU-DET</database> </source> <size> <width>200</width> <height>200</height> <depth>1</depth> </size> <object> <name>inclusion</name> <pose>unspecified</pose> <truncated>0</truncated> <difficult>0</difficult> <bndbox> <xmin>40</xmin> <ymin>60</ymin> <xmax>150</xmax> <ymax>170</ymax> </bndbox> </object> </annotation>如果你用OpenCV读取图像,大概率会得到三通道的array,因为很多库默认会把灰度图读成BGR三通道。但XML里depth写的可能是1。这个不一致本身不影响标注坐标,但会影响你对输入通道数的判断。我的建议是统一用单通道的思路做数据加载,真需要三通道再复制拼接,别依赖图像数组的后两个通道值。
3.3 把VOC标注转成YOLO训练需要的txt格式
YOLO系列一般需要每张图对应一个txt文件,每行内容是“类别id x_center y_center width height”,坐标全部归一化到0~1。我写过一个简单的转换脚本,逻辑不复杂,但很实用:
import os import xml.etree.ElementTree as ET CLASS_NAMES = ["crazing", "inclusion", "patches", "pitted_surface", "rolled-in_scale", "scratches"] def voc_to_yolo_txt(xml_path, txt_path, img_w=200, img_h=200): tree = ET.parse(xml_path) root = tree.getroot() lines = [] for obj in root.iter("object"): name = obj.find("name").text if name not in CLASS_NAMES: continue cls_id = CLASS_NAMES.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) x_center = (xmin + xmax) / 2.0 / img_w y_center = (ymin + ymax) / 2.0 / img_h w = (xmax - xmin) / img_w h = (ymax - ymin) / img_h lines.append(f"{cls_id} {x_center:.6f} {y_center:.6f} {w:.6f} {h:.6f}") with open(txt_path, "w", encoding="utf-8") as f: f.write("\n".join(lines))这里有个容易出错的点:有些转换脚本会写成x_center = xmin + w/2但忘记除以图片宽度,结果坐标全部大于1,训练时会直接报错或导致loss跑飞。另外,图片尺寸如果你不确定,不要硬编码200,最好从XML里的size节点读出来,防止某些重制版本改了分辨率。
4. 训练检测模型:我试过的配置与踩坑记录
4.1 数据划分与基础训练参数
NEU-DET没有官方统一的train/val划分,这是个容易被忽视的问题。有些人直接按文件名取前80%做训练、后20%做验证,但问题在于这个数据集的图像顺序可能按类别排列——前300张全是crazing,接下来300张全是inclusion。如果你不做随机shuffle,验证集里可能根本没有某些类别,mAP统计就失真了。
我建议按类别做分层抽样,保证训练集和验证集里每个类别都有接近80/20的比例。另外要记得用整个文件列表先随机洗牌,再按类别统计切分。实际操作中,我一般把1200张用作训练集,600张用作验证集。这个比例对这份数据量来说比较合适,不会让验证集因为样本太少而波动太大。
基础训练参数的话,我长期调下来觉得这个配置值得参考:
| 参数项 | 我的常用设置 | 备注 |
|---|---|---|
| 输入尺寸 | 640 × 640 | 原图200×200,放大后能保留更多细节 |
| Batch Size | 16 | 看显存,8也行但收敛会慢一些 |
| 优化器 | SGD(momentum=0.937) | 比AdamW在这个场景略稳 |
| 初始学习率 | 0.01 | 配合warm-up,前几个epoch不要太高 |
| 训练轮数 | 100 ~ 150 | 数据量小,再多容易过拟合 |
| 增强策略 | mosaic、random_flip、hsv微调 | 但crazing类慎用mosaic,原因见下 |
4.2 踩坑记录:mosaic增强对细纹类缺陷并不友好
这个坑我很早就踩过,印象特别深。初次用YOLOv5训练时,默认开启mosaic增强,训练loss降到很低,但验证集mAP一直上不去。后来把增强关闭,mAP反而涨了。仔细看失败案例才发现,mosaic会把4张图拼在一起,尺寸缩到一半。对scratches这种大块线状缺陷问题不大,但对crazing这种细线缺陷,被缩放再拼接之后,裂纹特征基本被抹掉了;模型学到的全是拼接边界附近的人造假纹理,自然测不好。
所以我的做法是:要么关闭mosaic,要么只对小部分batch开启,比如用mosaic的概率设为0.3或0.5,而不是默认的1.0。如果你不想改这么细,可以直接用mixup或copy-paste增强替代,对这类小目标数据更友好。这个结论我自己在不同数据集上验证过几次,不一定所有场景都适用,但对NEU-DET这种小图数据集基本成立。
4.3 灰度图到底该用单通道还是三通道
NEU-DET是灰度图,但大部分主流检测模型输入是三通道。两种做法我都试过:一种是把灰度图复制成三个通道喂进去,另一种是改模型第一层卷积的输入通道数为1。实际结果显示,在NEU-DET这种数据量级别上,两种做法的最终性能差异很小,基本在误差范围内。
但如果目标是部署到真实产线,我强烈建议直接按三通道复制来做。原因不是性能,而是适配性:真实产线里工业相机输出的虽然经常是黑白图,但一旦换相机、换光源,可能就会变成彩色图,模型如果从一开始就按单通道设计,后面迁移的灵活性就差了。复制成三通道的最简单写法是np.repeat(gray_img[..., np.newaxis], 3, axis=-1),或者直接用PIL的convert("RGB")也行。
4.4 小输入尺寸带来的过拟合预警
NEU-DET总共只有1800张,对深度学习模型来说属于小样本。我在训练过程中基本到第80个epoch左右就会观察到验证集精度不再上升,甚至开始下跌,而训练集精度还在继续涨,这是典型的过拟合信号。想缓解的话,常用手段是加大数据增强强度、加入Dropout或减弱backbone复杂度。
另外一个容易被忽略的点是:很多预训练权重是在ImageNet上训的,输入通道是RGB、图像尺寸相对较大。直接用YOLOv8自带的预训练权重微调NEU-DET,通常效果不错,但注意不要把backbone冻结太久。我试过冻结前30个epoch,最终AP比全程微调低了2到3个百分点,因为在灰度小图上,低层特征仍然需要适应新的图像域。
5. 评估结果怎么读:AP高不代表你能落地
5.1 常见指标与多维对比
在NEU-DET上报告结果时,最常见的指标是mAP@0.5和mAP@0.5:0.95。前者只看IoU阈值0.5下框的重合度,后者要看从0.5到0.95按0.05步长多个阈值下的平均表现,更严格。对缺陷检测来说,我建议两个指标都看,因为单看mAP@0.5时,一个框框得偏大会被容忍;但在真实质检里,框偏大意味着定位不准,后面很难接机械臂抓取或裁剪判断。
一组有代表性的结果可能长这样(具体数值会随随机种子和划分方式浮动):
| 类别 | AP@0.5(参考范围) | AP@0.5:0.95(参考范围) |
|---|---|---|
| crazing | 0.45 ~ 0.62 | 0.25 ~ 0.40 |
| inclusion | 0.70 ~ 0.82 | 0.45 ~ 0.58 |
| patches | 0.78 ~ 0.88 | 0.52 ~ 0.65 |
| pitted_surface | 0.62 ~ 0.75 | 0.38 ~ 0.50 |
| rolled-in_scale | 0.66 ~ 0.80 | 0.42 ~ 0.55 |
| scratches | 0.82 ~ 0.92 | 0.58 ~ 0.72 |
我拿这张表出来不是让你对号入座,而是想说:如果你的实验里crazing的AP比其他类低15个百分点以上,属于正常现象,不必怀疑代码写错。反倒是如果某天crazing突然冲到最高分,那大概率是验证集里混进了太简单的样本,或者数据划分出了泄漏。
5.2 为什么NEU-DET的实验结果有时会被“高估”
许多论文里报告的NEU-DET精度都很高,这容易让你在实际项目中产生错觉。原因主要有三个。第一,图像背景干净,每张图基本就是一块钢材表面,没有产线现场常见的集装箱、传送带、机械臂、光照阴影等干扰物体。第二,标注框是研究团队仔细框过的,边界基本精确,不会出现标注框偏移或漏标。第三,数据集规模小,训练集和验证集的图像分布高度一致,分布漂移几乎为零。
这意味着,你在NEU-DET上跑出来的高mAP,更多反映的是“在限定条件下的算法潜力”,而不是“在真实场景里的可用性”。我后来把一个在NEU-DET上mAP@0.5达到0.82的模型,直接放到真实产线拍摄的钢板图像上测试,mAP直接掉到0.45左右。所以每当我看到别人拿这个数据集说“我们的模型已经能上线”的时候,都会提醒自己先看测试集是不是只是这个数据集的随机划分。
5.3 用失败案例反向定位模型短板
很多人训练完只看mAP数字,但我更推荐多翻验证集的失败预测图。YOLO系列训练时会保存部分预测结果,或者你可以在推理阶段把预测框画出来和GT叠加对比。重点关注三类样本:完全漏检的、重复框的、置信度高但框错位置的。漏检通常是特征表达不足;重复框可能是NMS阈值太高或太低;置信度高但框错位置则可能与标注本身的质量有关。
NEU-DET里有一种常见失败模式:一张图上有两个相近的麻点缺陷,模型只检出其中一个,或者把两个合并成一个框。这既是标注主观性的体现,也给后处理提了个醒——真实质检场景里,一个视觉缺陷在图像上可能被连续相机拍成多帧,简单NMS反而会把同一缺陷的多个角度框全部消灭掉,这时候需要按跟踪ID合并。
6. 从NEU-DET到真实产线:必须正视的领域落差
6.1 数据域差异比你想象的大
NEU-DET里的图像主要来自热轧带钢表面,背景相对均匀,光照条件也比较一致。而真实产线里,钢板材质可能不同,相机安装角度可能不同,光源种类从环形光到同轴光都有,甚至钢板表面会有油污、水渍、氧化色不均匀等复杂干扰。这些因素都让真实图像的分布和NEU-DET存在明显偏移。用源数据集训练、直接部署到目标产线,几乎一定会遇到性能下降。
我在一个模拟项目X里做过一个实验:把NEU-DET上训练好的模型,直接用在一台不同相机拍摄的钢板图上,第一轮测下来漏检率比验证集高了3倍。这不是模型本身不行,而是源域和目标域之间的差异没有处理。解决办法无外乎几条:在目标产线采集少量真实缺陷样本做微调;或者用风格迁移、图像增强把NEU-DET的图“伪装”成目标工况;更稳妥的是把缺陷模拟合成数据加进训练集混合训练。
6.2 模拟缺陷生成不能完全替代真实数据
有些人为了省事,会用图像处理在干净钢板上合成划痕、麻点。这种方法能快速扩充数据量,但有一个致命问题:合成的缺陷过于“规整”,边界、纹理和真实缺陷差异明显。模型可能学会了识别“画出来的划痕”,但真实划痕的光影变化和边缘毛刺一出现,模型就露馅了。如果你想用模拟数据,最好是先用一小部分真实缺陷做验证集,把合成策略反复调整,等验证集性能不再提升,再去扩展更多的合成样本。
另外,NEU-DET只有静态图像,没有视频序列。真实检测往往连着一台相机连续抓帧,缺陷在相邻帧里会有位移和形变。如果只看这个数据集,你训练出来的模型很难具备帧间稳定性和去重能力。我在实际项目里会在推理阶段加一个轻量级目标跟踪,例如用IoU匹配相邻帧的检测框,把连续N帧都被检出的缺陷才上报。这步处理能明显降低误报,而它完全不在NEU-DET的训练范畴内。
6.3 我的实践建议:NEU-DET适合做预研,别当产线模型来源
讲了这么多落差,并不是让你别用这份数据集,而是想说清楚它的合理定位。NEU-DET适合用来验证检测算法的结构选型、做数据增强策略的消融实验、对比不同backbone的性能、以及训练新加入的算法工程师熟悉整个流程。这些价值非常大,我至今还在用它做新模型的快速筛选。但如果你要部署到真实产线,务必把NEU-DET当成起点而不是终点,后续至少还要做三件事:采集目标场景真实样本、针对性地做数据域适应、建立一套产线侧的连续帧确认机制。
最后分享一个技巧:在NEU-DET上做任何实验,都建议固定随机种子,否则不同次训练的mAP波动可能会超过3个百分点,这会干扰你判断某个改动到底有没有效果。我习惯把每次实验的配置、训练集划分文件、评估结果存成一份带时间戳的记录,后面复盘时效率高很多。这个习惯帮我避开了不少“明明改了代码精度却忽高忽低”的假象。