简介:铝片表面缺陷目标检测数据集面向工业质检与目标检测实践场景,按YOLOv5标准目录格式整理,覆盖针孔、擦伤、脏污、褶皱四类缺陷,可直接用于模型训练与验证。包内图像为640×480的RGB图片,共2000个文件,包含598张JPG图像、1401个TXT标签及1个可视化Python脚本,压缩包约82MB;训练集含1120张图像及对应标签,验证集含280张图像及对应标签。目前已有195人浏览学习,适合需要现成工业检测数据集的开发者与研究者。除数据组织清晰外,资源附带可视化脚本,可随机读取一张图片并绘制边界框保存,无需修改即可运行,能直观检查标注效果,也便于理解YOLO格式标签内容,快速接入训练流程。
1. 铝片表面缺陷检测与 YOLOV5 数据集格式的现实匹配
铝型材冲压件出厂前有一道人工目检:拿着手电斜照铝片,找针孔、擦伤、脏污、褶皱。这道工序换成算法来做,就是目标检测里的缺陷检测(Defect Detection)。铝片表面高光、纹理单一、缺陷又细又小,恰恰是 YOLOv5 这类单阶段检测器最容易翻车的场景之一。这套 82MB 的数据集按 YOLOV5 目录格式组织,训练集 1120 张、验证集 280 张,图片分辨率统一 640×480,标签 txt 与图片同名同结构存放,解压后可以直接进训练流程,不需要再转 COCO 或 VOC。不过,直接用默认参数训练,很可能会在验证集上看到 mAP 波动大、漏检集中在针孔和擦伤两类。下面几章把目录规范、标注复核、增强参数、训练排错和部署阈值完整过一遍。
2. 目录结构与标签规范:YOLOV5 数据集的默认约定拆解
2.1 data.yaml 与 images/labels 的同名映射
YOLOV5 训练时只认两类路径:图片目录和标签目录。图片放在images/train、images/val,标签放在labels/train、labels/val,训练时引擎会根据图片文件名自动到labels下找同名 txt,例如images/train/1163.jpg对应labels/train/1163.txt。这套数据集的目录结构不需要二次整理,解压后就是标准 YOLOV5 布局。训练集 1120 张图片配 1120 个标签文件,验证集 280 张同样配齐,比例约 4:1,对工业缺陷检测来说是够用的。产线后续还会采集新样本做滚动验证,数据这一侧不必拆得太碎。
datasets/aluminum/ ├── data.yaml # 类别与路径配置 ├── images/ │ ├── train/ # 1120 张 .jpg │ └── val/ # 280 张 .jpg └── labels/ ├── train/ # 1120 个同名 .txt └── val/ # 280 个同名 .txtdata.yaml 是 YOLOV5 读取数据集的入口,核心是train、val、nc、names四个字段。nc必须与标签中实际出现的最大类别 id + 1 一致,这里就是 4。names建议直接用英文,因为 OpenCV 的putText画不了中文,后续导出 ONNX 时类别名会被写进模型元数据,中文字符串在部分推理框架里会出现编码问题。
# datasets/aluminum/data.yaml train: /absolute/path/to/datasets/aluminum/images/train val: /absolute/path/to/datasets/aluminum/images/val nc: 4 names: ['pinhole', 'scratch', 'stain', 'wrinkle']2.2 txt 标签的归一化坐标与合法性检查
标签 txt 是 YOLO 格式,每行 5 个字段:class_id x_center y_center width height,四个坐标值都是相对图像宽高的比例,取值在 0 到 1 之间。width、height是框的宽高占整张图宽高的比例,不是像素值。这一行看起来简单,实际出错率很高,常见问题包括:分割符号混用(空格和 Tab 混在一起)、坐标小于 0 或大于 1、框超出图像边界、class id 超出nc范围。YOLOV5 在训练时会对部分越界标签做裁剪或丢弃,但不会主动报错,所以这类问题往往要等 loss 异常或 mAP 偏低时才暴露出来。
比较稳妥的做法是动手训练前先扫一遍标签,检查坐标范围和类别 id 是否合法:
from pathlib import Path label_dir = Path("datasets/aluminum/labels/val") bad = [] for txt in label_dir.glob("*.txt"): for line in txt.read_text().strip().splitlines(): parts = line.split() if len(parts) != 5: bad.append((txt.name, "字段数错误")) continue cls = int(parts[0]) x_c, y_c, w, h = map(float, parts[1:]) if not (0 <= x_c <= 1 and 0 <= y_c <= 1 and w > 0 and h > 0): bad.append((txt.name, "坐标越界")) if cls < 0 or cls > 3: bad.append((txt.name, "class id 非法")) # 检查框的四个角是否仍在图内 if (x_c - w / 2 < 0 or x_c + w / 2 > 1 or y_c - h / 2 < 0 or y_c + h / 2 > 1): bad.append((txt.name, "框出图"))这段脚本的判定逻辑是:首先确认每一行拆出来是 5 个字段,防止有人误存成 VOC 的xmin ymin xmax ymax格式;然后确认归一化坐标落在 [0,1] 区间,宽高必须为正;最后用中心点坐标减去半宽半高,检查框的四个角没有超出图片边界。跑完如果bad为空,说明标签基本规整。下面整理了几个高频问题和对应处置方式:
| 检查项 | 判定条件 | 处置方式 |
|---|---|---|
| 字段数错误 | split()后长度不等于 5 | 打开 txt 检查分隔符是否混用 |
| 坐标越界 | x_c、y_c、w、h 超出 [0,1] | 从标注软件重新导出 |
| 框出图 | 边界角坐标小于 0 或大于 1 | 裁剪到边界或删除该标签 |
| class id 非法 | 类别编号大于等于 nc | 核对标注软件类别顺序 |
3. 标签可视化复核:从 YOLO 的 txt 生成边界框
3.1 可视化脚本的核心逻辑
标签舍不舍得删,要看了图才能判断。随包提供的可视化 py 文件,本质上就是把 images 里的原图、labels 里对应的 txt,以及类别名三样东西拼在一起画框,然后把结果保存到当前目录。脚本无需改路径就能直接运行。自己手写也只有几十行,核心逻辑如下:
import cv2 from pathlib import Path IMG_DIR = Path("datasets/aluminum/images/val") LABEL_DIR = Path("datasets/aluminum/labels/val") CLASS_NAMES = ["pinhole", "scratch", "stain", "wrinkle"] COLORS = [(0, 0, 255), (0, 255, 0), (255, 0, 0), (0, 255, 255)] def draw_one(img_path, label_path, save_path): img = cv2.imread(str(img_path)) h, w = img.shape[:2] for line in label_path.read_text().strip().splitlines(): cls, x_c, y_c, bw, bh = line.split() cls = int(cls) x_c, y_c, bw, bh = map(float, (x_c, y_c, bw, bh)) # 归一化坐标换算回像素坐标 x1 = int((x_c - bw / 2) * w) y1 = int((y_c - bh / 2) * h) x2 = int((x_c + bw / 2) * w) y2 = int((y_c + bh / 2) * h) cv2.rectangle(img, (x1, y1), (x2, y2), COLORS[cls], 2) cv2.putText(img, CLASS_NAMES[cls], (x1, max(0, y1 - 5)), cv2.FONT_HERSHEY_SIMPLEX, 0.6, COLORS[cls], 2) cv2.imwrite(str(save_path), img) # 随机抽一张验证集图片绘制 files = list(IMG_DIR.glob("*.jpg")) img_file = files[0] label_file = LABEL_DIR / (img_file.stem + ".txt") draw_one(img_file, label_file, f"vis_{img_file.name}")line.split()默认按连续空白字符切分,能同时兼容空格和 Tab 分隔的标签。换算像素坐标时,x_center - width / 2得到框的左上角 x,再乘图像宽度转成像素,这是 YOLO 归一化格式与 OpenCV 画框之间最关键的转换。类别名直接写在框上方,缺陷小的时候字号 0.6 足够看清。
3.2 用绘制结果发现三类典型标注问题
把验证集 280 张全部过一遍图,重点看三类情况。第一是框是否紧贴缺陷边缘,铝片反光会导致边缘过曝,标注框通常比实际缺陷大一圈,IoU 损失主要来自这里。第二是同一处缺陷是否被重复画框,这在擦伤和褶皱上最常见,一道长擦伤被拆成两三个小框,训练时模型会无所适从。第三是脏污和褶皱是否标反,两者在灰度图上形态相似,褶皱有方向性纹路,脏污边界更圆润,看单张静态图确实容易混。发现这几种情况,建议直接用文本编辑器改 txt,不需要重新打开标注软件。
4. 四类缺陷的形态差异与数据增强参数调优
4.1 先统计类别分布,再谈增强
四类缺陷的形态差异很大。针孔是几像素大小的暗点,占整张图比例极小;擦伤是细长线条,方向不定;脏污成块状,灰度与铝面背景接近;褶皱是成片纹理区域,反光强烈。这四类放在同一个训练集里,样本数天然不均衡,先跑一段统计代码看清分布:
from pathlib import Path from collections import Counter counter = Counter() for txt in Path("datasets/aluminum/labels/train").glob("*.txt"): for line in txt.read_text().strip().splitlines(): counter[int(line.split()[0])] += 1 for i, name in enumerate(["pinhole", "scratch", "stain", "wrinkle"]): print(name, counter[i])统计出来的数量,大概率是脏污和褶皱偏多、针孔和擦伤偏少。这个分布直接决定了类别权重怎么设,也决定了增强策略不能搞一刀切。比如针孔是典型的小目标检测场景,在 640×480 原图上可能只占几十个像素,经过 YOLOV5 的 640 输入缩放后再下采样到 80×80 特征层,信息已经所剩无几。擦伤因为是细长条,旋转增强时方向角范围要足够大,但水平翻转要慎用,因为冲压擦伤通常有固定走向,盲目翻转会让模型学到错误的方向先验。
4.2 针对铝片反光与小目标的 hyp 修改
YOLOV5 的默认超参文件hyp.scratch-low.yaml是按自然场景调出来的,直接套在金属反光表面上有两个明显问题:亮度扰动幅度太小,模拟不了产线上光照角度变化;马赛克增强比例偏低,对小目标不友好。我一般在默认基础上做一版针对性的调整:
# hyp.aluminum.yaml hsv_h: 0.015 # 色调扰动调小,铝片本身颜色单一 hsv_s: 0.5 # 饱和度稍微放开,区分脏污与背景 hsv_v: 0.6 # 亮度扰动拉大,模拟不同打光角度 degrees: 30 # 旋转范围放大,擦伤方向不定 translate: 0.2 # 平移扰动,防止模型依赖缺陷位置 scale: 0.8 # 缩放扰动,兼顾大小目标 fliplr: 0.5 mosaic: 0.6 # 提高马赛克概率,改善小目标训练 mixup: 0.1hsv_v从默认 0.4 提到 0.6,是这套参数里最重要的一处改动。铝片是高反光表面,同一个缺陷在不同光源角度下亮度差异极大,训练时见过更多亮度区间的样本,推理时对光照变化的鲁棒性才起得来。mosaic从 0.5 提到 0.6,让四张图拼接的概率更高,小目标在拼接图里占比更小、样本更多样。degrees给到 30,对擦伤方向变化是必需的,但不要超过 45,因为针孔和脏污这类圆形缺陷在极限旋转下没有收益,反而增加计算开销。
5. 训练配置、验证指标与工业场景排错清单
5.1 训练与验证命令及参数含义
数据目录和增强参数准备好后,训练命令如下。用yolov5s.pt做预训练权重,输入 640,batch 16 在 11GB 显存的卡上比较稳妥。
python train.py --data datasets/aluminum/data.yaml \ --weights yolov5s.pt \ --img 640 \ --batch 16 \ --epochs 150 \ --cache \ --hyp hyp.aluminum.yaml \ --project runs/aluminum--img 640与数据原始分辨率 640×480 接近,上采样开销小,且能保留针孔这类小目标的细节;显存不够时优先降 batch 而不是降分辨率,分辨率一降,小目标直接消失。--cache会把图片预加载到内存,82MB 的数据集缓存后训练速度提升明显。--project runs/aluminum让每次实验输出到独立目录,方便和默认参数跑出来的结果做对比。
验证阶段使用val.py,并指定训练结束后best.pt的路径:
python val.py --data datasets/aluminum/data.yaml \ --weights runs/aluminum/exp/weights/best.pt \ --img 6405.2 读 mAP 的方式与常见报错
验证输出会打印mAP@0.5、mAP@0.5:0.95、Precision、Recall 四个指标,工业场景里怎么读这四个数有讲究:
| 指标 | 含义 | 铝片场景解读 |
|---|---|---|
| mAP@0.5 | IoU 0.5 下的平均 AP | 缺陷是否被大致框住,产线能否用 |
| mAP@0.5:0.95 | 严格平均 AP | 小目标检测能力的真实反映 |
| Precision | 检出的目标里正确的比例 | 误检多时这个值掉得明显 |
| Recall | 真实缺陷中被找出的比例 | 漏检多时这个值掉得明显 |
针孔这类小目标通常会把 mAP@0.5:0.95 拉下来很多,比 mAP@0.5 低 10 个点以上都算正常,不用急着以为训练失败。真正需要警惕的是 Precision 和 Recall 同时偏低,说明模型既找不全也找不准。
训练过程中实操排错频率最高的几个问题,处理方式如下表:
| 报错或症状 | 最常见原因 | 处理方式 |
|---|---|---|
| CUDA out of memory | batch 太大或 cache 占显存 | batch 降到 8,去掉 --cache |
| Image not found | data.yaml 里 train/val 路径使用了相对路径 | 换绝对路径 |
| 部分标签未加载 | txt 文件名与图片名不一致 | 比对两个目录文件名,统一后缀 |
| 类别数不匹配 | data.yaml 的 nc 与 txt 中 class id 不一致 | 重新统计标签最大类别编号 |
| loss 变 NaN | 学习率过高 | 训练命令加--lr0 0.001 |
如果训练过程中没有报错但 mAP 始终上不去,先把labels_correlogram.jpg拿出来看。这张图在训练输出目录里,横纵轴分别对应标签的归一化中心坐标和宽高,能直观看出目标在整张图上的分布。铝片缺陷如果大量集中在图像中心区域,说明之前标注时裁切位置有偏,靠增强很难完全弥补。
6. 导出 ONNX 部署,用置信度阈值折中漏检与误检
6.1 导出流程与 25200 维输出的含义
训练收尾后直接用export.py导出 ONNX,方便脱离 PyTorch 环境部署:
python export.py --weights runs/aluminum/exp/weights/best.pt \ --img 640 --batch 1 --simplify --include onnx--simplify会用 onnx-simplifier 折叠一些冗余算子,--batch 1固定输入维度,避免动态 batch 带来的推理性能损失。导出的模型输入是[1, 3, 640, 640],输出是[1, 25200, 85]。25200 来源于三个检测头候选框之和:640 输入时特征图尺寸是 80×80、40×40、20×20,每个网格位置对应 3 个锚框,加起来正好是(6400 + 1600 + 400) × 3 = 25200。85 是4 个框坐标 + 1 个置信度 + 80 个 COCO 类别的默认维度,导出后类别维度会被项目配置截断,实际拿到的是4 + 1 + 4 = 9,即最后四位分别表示针孔、擦伤、脏污、褶皱的类别的概率。
6.2 阈值调整的实测建议
用 ONNX Runtime 推理时,输出的 25200 个候选框直接画会堆成一片,必须做两件事:先按置信度阈值过滤,再做 NMS。核心代码如下:
import cv2 import numpy as np import onnxruntime as ort session = ort.InferenceSession("best.onnx") input_name = session.get_inputs()[0].name img = cv2.imread("test.jpg") img0 = cv2.resize(img, (640, 640)) # BGR 转 RGB,归一化后转 CHW,并增加 batch 维度 x = img0[:, :, ::-1].transpose(2, 0, 1)[None].astype(np.float32) / 255.0 out = session.run(None, {input_name: x})[0][0] # shape: [25200, 9] conf = out[:, 4] box_mask = conf > 0.25 candidates = out[box_mask]这里的阈值0.25是工业场景的起点值。铝片反光严重,脏污和背景在特征空间里距离较近,阈值设太高会把真实缺陷一起滤掉。实测中如果 Recall 偏低、漏检多,把置信度阈值降到 0.15;如果误检多、过检率高,升到 0.4,再配合 NMS 的 IoU 阈值 0.45 一起调。按这套数据集的特点,最终落地时置信度阈值卡在 0.2 到 0.3 之间、NMS 阈值固定 0.45,通常能在产线误检率和漏检率之间找到比较稳的平衡点。导出时建议始终用--img 640,不要为了边缘设备省显存降到 320,针孔这类缺陷在 320 输入下会退化到两三个像素,卷积特征完全丢失,阈值怎么调都救不回来。
本文还有配套的精品资源,点击获取