ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

铝片表面缺陷检测数据集实战:COCO转YOLO与训练避坑指南

铝片表面缺陷检测数据集实战:COCO转YOLO与训练避坑指南 简介这份数据集面向从事机器视觉与工业质检的研究者、算法工程师及图像处理初学者用于铝片表面针孔、擦伤、脏污、褶皱四类缺陷的目标检测训练与验证。资源包共402个文件以400张jpg缺陷图像和2个json标注文件为主压缩包约15.74MB图像按COCO格式标注了缺陷位置与类型json文件分别对应训练集与验证集便于直接开展模型训练与性能评估。目前已有161人学习下载。数据集覆盖工业缺陷检测的典型场景读者可据此搭建检测流程、验证算法在真实产线图像上的准确率与泛化能力也可通过数据增强扩充样本规模为自动化质检与生产线智能化改造提供实验素材。1. 铝片表面缺陷检测数据集412 张图、4 类缺陷、COCO 标注能直接跑通训练吗产线上铝片跑得飞快人眼盯一天下来漏检率飙升这是很多做工业质检的团队都会碰到的场景。这份铝片表面工业缺陷目标检测数据集就是冲着这个痛点来的412 张实拍铝片图像覆盖针孔、擦伤、脏污、褶皱四类常见缺陷每张图都按 COCO 格式做了位置和类别的标注并且已经切分成 train.json 和 valid.json 两份。它适合两类人——刚入门目标检测、想找一个真实工业场景练手的新手以及已经在做表面缺陷项目、需要一份带标注数据做 baseline 验证的工程师。数据集本身不绑定框架YOLO 系列、Faster R-CNN、DETR 都能吃关键是你得先把 COCO 标注和训练框架之间的格式鸿沟填平否则拿到手就是一堆 json 和 jpg跑不起来。2. COCO 标注结构拆解从 json 字段到四类缺陷的映射关系拿到数据集第一件事不是急着训练而是把标注文件读明白。COCO 格式看着标准但工业数据集里经常藏着类别 id 不连续、图片文件名和 image_id 对不上这类问题先摸清楚结构能省掉后面大量排查时间。2.1 COCO json 的四个核心字段一份标准的 COCO 检测标注 json 里真正跟训练相关的是这四个顶层字段字段含义训练时的用途images图片列表含 file_name、id、width、height建立 image_id 到文件路径的映射annotations标注列表含 image_id、category_id、bbox、area提供每个缺陷框的位置和类别categories类别列表含 id、name定义类别 id 到类别名的映射info / licenses元信息训练时忽略bbox 的格式是[x, y, width, height]注意是左上角坐标加宽高不是右下角坐标。这一点在转 YOLO 格式时是高频翻车点后面避坑章节会细说。用下面这段代码把标注结构打印出来确认四类缺陷的 category_id 分布import json from collections import Counter # 分别读取训练集和验证集标注 for split in [train, valid]: with open(f{split}.json, r, encodingutf-8) as f: data json.load(f) # 打印类别定义确认针孔/擦伤/脏污/褶皱的 id print(f {split} categories ) for cat in data[categories]: print(f id{cat[id]}, name{cat[name]}) # 统计每类缺陷的标注框数量判断类别是否失衡 cat_counter Counter(ann[category_id] for ann in data[annotations]) print(f {split} 各类别框数 ) for cid, cnt in sorted(cat_counter.items()): print(f category_id{cid}: {cnt} boxes) print(f图片总数: {len(data[images])}, 标注框总数: {len(data[annotations])})这段代码做三件事读 json、打印类别映射、统计每类框数。跑完你就能知道四类缺陷在训练集里各有多少框。工业缺陷数据集常见的问题是某类缺陷样本极少比如针孔可能只有几十个框而脏污有几百个这种失衡会直接影响模型对小类别的召回后面做数据增强时要针对性补。2.2 图片与标注的一致性校验在正式转换格式之前必须确认 images 里记录的每张图在磁盘上真实存在且 annotations 里的 image_id 都能在 images 里找到对应项。工业数据集在打包分发时偶尔会丢图或者 json 里残留已删除图片的记录不校验的话训练到一半报 FileNotFoundError 就很被动。import os with open(train.json, r, encodingutf-8) as f: data json.load(f) # 建立 image_id 集合 image_ids {img[id] for img in data[images]} # 检查 1json 里记录的图片文件是否都存在 missing_files [] for img in data[images]: if not os.path.exists(os.path.join(images, img[file_name])): missing_files.append(img[file_name]) print(f磁盘缺失图片数: {len(missing_files)}) # 检查 2标注里的 image_id 是否都能在 images 中找到 orphan_anns [ann for ann in data[annotations] if ann[image_id] not in image_ids] print(f孤儿标注数image_id 无对应图片: {len(orphan_anns)}) # 检查 3bbox 是否有零宽零高或越界 bad_bbox [] for ann in data[annotations]: x, y, w, h ann[bbox] if w 0 or h 0: bad_bbox.append(ann[id]) print(f异常 bbox 数宽或高0: {len(bad_bbox)})三个检查分别对应三类常见脏数据丢图、孤儿标注、退化框。孤儿标注如果不清理转 YOLO 格式时会生成没有对应图片的 label 文件训练时被静默忽略你以为用了全部数据其实少了一部分。退化框宽高为 0 会让某些框架在计算 IoU 时除零报错。这三步跑完数据干净了再往下走。3. COCO 转 YOLO 格式转换脚本、目录结构与参数核对COCO 标注不能直接喂给 YOLO必须转成每张图一个 txt、每行一个框的格式。这一步是整个流程里最容易出错的环节坐标系转换、归一化、类别 id 重映射三个地方任何一个搞错训练 loss 都会异常。3.1 转换原理与坐标归一化YOLO 格式每行是class_id x_center y_center width height全部是相对于图片宽高的归一化值范围 0 到 1。而 COCO 的 bbox 是绝对像素坐标的左上角加宽高。转换公式x_center (x w/2) / img_widthy_center (y h/2) / img_heightnorm_w w / img_widthnorm_h h / img_heightclass_id 需要从 COCO 的 category_id 重映射成从 0 开始的连续整数因为 YOLO 要求类别索引从 0 开始且连续。如果 COCO 里 category_id 是 1、2、3、4映射后就是 0、1、2、3。import json import os def coco_to_yolo(coco_json, img_dir, out_label_dir, class_map): coco_json: COCO 标注文件路径 img_dir: 图片所在目录 out_label_dir: 输出 YOLO label 的目录 class_map: {coco_category_id: yolo_class_index} 映射字典 os.makedirs(out_label_dir, exist_okTrue) with open(coco_json, r, encodingutf-8) as f: data json.load(f) # image_id - (file_name, width, height) id2img {img[id]: img for img in data[images]} # 按 image_id 聚合标注 from collections import defaultdict img2anns defaultdict(list) for ann in data[annotations]: img2anns[ann[image_id]].append(ann) for img_id, anns in img2anns.items(): img_info id2img[img_id] iw, ih img_info[width], img_info[height] # label 文件名与图片同名后缀换成 txt label_name os.path.splitext(img_info[file_name])[0] .txt lines [] for ann in anns: x, y, w, h ann[bbox] # 归一化并转中心点格式 xc (x w / 2) / iw yc (y h / 2) / ih nw w / iw nh h / ih cls class_map[ann[category_id]] lines.append(f{cls} {xc:.6f} {yc:.6f} {nw:.6f} {nh:.6f}) with open(os.path.join(out_label_dir, label_name), w) as f: f.write(\n.join(lines)) # 假设 COCO 里四类 id 是 1,2,3,4映射到 0,1,2,3 class_map {1: 0, 2: 1, 3: 2, 4: 3} coco_to_yolo(train.json, images, labels/train, class_map) coco_to_yolo(valid.json, images, labels/valid, class_map)class_map 必须根据你实际读出来的 category_id 来定不能照抄。归一化保留 6 位小数足够YOLO 读取时会自己解析。转换完成后labels/train 下的 txt 数量应该和 train.json 里的图片数一致对不上就说明有图片没有标注或者聚合逻辑有问题。3.2 目录组织与 data.yaml 配置YOLO 训练要求固定的目录结构图片和 label 分开放路径在 data.yaml 里声明dataset/ ├── images/ │ ├── train/ # 训练图片 │ └── valid/ # 验证图片 ├── labels/ │ ├── train/ # 训练 label txt │ └── valid/ # 验证 label txt └── data.yamldata.yaml 内容path: /absolute/path/to/dataset train: images/train val: images/valid nc: 4 names: 0: pinhole # 针孔 1: scratch # 擦伤 2: dirt # 脏污 3: wrinkle # 褶皱names 的顺序必须和 class_map 的映射结果一致否则模型学出来的类别会张冠李戴。nc 是类别数这里是 4。path 建议写绝对路径相对路径在不同工作目录下启动训练时容易找不到。3.3 转换结果验证转完之后不要直接开训先做一轮可视化抽查。用下面这段代码随机抽几张图把 YOLO 格式的框画回去肉眼确认框的位置和类别对不对import cv2 import random import os def visualize_yolo(img_path, label_path, names): img cv2.imread(img_path) ih, iw img.shape[:2] if os.path.exists(label_path): with open(label_path) as f: for line in f: cls, xc, yc, nw, nh map(float, line.split()) # 反归一化回像素坐标 x1 int((xc - nw / 2) * iw) y1 int((yc - nh / 2) * ih) x2 int((xc nw / 2) * iw) y2 int((yc nh / 2) * ih) cv2.rectangle(img, (x1, y1), (x2, y2), (0, 255, 0), 2) cv2.putText(img, names[int(cls)], (x1, y1 - 5), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0, 255, 0), 2) cv2.imwrite(check_vis.jpg, img) names [pinhole, scratch, dirt, wrinkle] # 随机抽一张训练图 sample random.choice(os.listdir(images/train)) visualize_yolo(fimages/train/{sample}, flabels/train/{os.path.splitext(sample)[0]}.txt, names)如果框整体偏移、宽高明显不对大概率是归一化时用错了宽高或者 bbox 格式理解成了右下角坐标。如果类别名全错检查 class_map 和 names 的顺序是否对齐。这一步花五分钟能避免训练几小时后才发现数据是错的。4. 训练配置与数据增强小样本工业缺陷的调参思路412 张图在目标检测里属于小样本直接套默认配置很容易过拟合训练集 loss 降得很低但验证集 mAP 上不去。这一章讲清楚训练时几个关键参数怎么设以及针对四类缺陷做什么增强。4.1 训练参数设置与含义以 YOLOv8 为例一份针对小样本工业缺陷的起手配置yolo detect train \ datadataset/data.yaml \ modelyolov8n.pt \ epochs200 \ imgsz640 \ batch16 \ lr00.001 \ lrf0.01 \ warmup_epochs5 \ patience50 \ augmentTrue \ mosaic1.0 \ mixup0.1 \ degrees15.0 \ translate0.1 \ scale0.5 \ fliplr0.5 \ flipud0.0 \ hsv_h0.015 \ hsv_s0.7 \ hsv_v0.4逐个说关键参数。model 选 yolov8n 是因为数据量小大模型参数量多更容易过拟合n 版本作为 baseline 先跑通再考虑换大。epochs 给 200 配合 patience50意思是验证指标 50 轮不提升就早停避免无效训练。lr0 初始学习率 0.001 比默认的 0.01 小小数据集上大学习率容易震荡。mosaic1.0 是 YOLO 的招牌增强把四张图拼成一张对小样本扩充效果明显但工业缺陷里针孔这种小目标在拼接后可能被裁掉如果发现小目标召回低可以降到 0.5。degrees15 做小角度旋转铝片缺陷方向不固定适度旋转合理但褶皱这类有方向性的缺陷旋转太大会破坏特征15 度是保守值。flipud0.0 关掉上下翻转因为工业图像有固定的拍摄方向上下翻转不符合真实分布。4.2 针对四类缺陷的增强策略四类缺陷的形态差异很大增强不能一刀切缺陷类型形态特点推荐增强慎用增强针孔极小目标几个像素mosaic、scale 放大大角度旋转易丢失擦伤细长条状水平翻转、小角度旋转上下翻转脏污不规则块状色彩抖动、亮度调整无褶皱有方向性的纹理小角度旋转大角度旋转、翻转针孔是最难的一类目标太小640 分辨率下可能只有 3 到 5 个像素。常见做法是把 imgsz 提到 1024 再训一版对比或者用切片推理SAHI在推理阶段把小目标放大。脏污对颜色敏感hsv_h、hsv_s、hsv_v 这三个色彩空间抖动参数可以适当调大模拟不同光照和脏污程度。褶皱有明确的方向性旋转超过 30 度就不像真实褶皱形态了所以 degrees 别设太大。4.3 训练过程监控与指标解读训练启动后重点盯三个指标box_loss、cls_loss、mAP50。box_loss 管框的位置回归cls_loss 管分类两个都应该稳定下降。如果 box_loss 降但 cls_loss 不降说明框定位学得还行但类别分不开可能是类别特征太相似或者标注有歧义。mAP50 是 IoU 阈值 0.5 下的平均精度工业缺陷检测里这个值能到 0.7 以上就算可用0.85 以上算不错。验证集 mAP 和训练集 mAP 差距大是过拟合的信号。412 张图训练集大概 330 张左右模型很容易记住训练样本。对策有三个加大增强力度、加 dropout 或 weight_decay、减少模型参数量。我一般先加增强因为工业场景的增强是符合真实分布的比正则化更对症。5. 避坑与排查标注、格式、训练三类高频问题这一章记录的是我在用类似工业缺陷数据集时真实踩过的坑每条按现象、原因、解决来写你对照排查能省不少时间。5.1 训练 loss 为 nan 或持续不降现象训练启动后 box_loss 直接是 nan或者几十轮过去 loss 纹丝不动。原因最常见的是 label 文件里有坐标超出 0 到 1 范围的值或者 bbox 宽高为负。COCO 转 YOLO 时如果原 bbox 有异常值归一化后就会越界。另一个原因是 data.yaml 里 nc 和实际类别数不一致导致分类头维度对不上。解决跑一遍 label 合法性检查把所有坐标不在 0 到 1 之间的行找出来。下面这段脚本能定位问题文件import os def check_labels(label_dir): bad [] for fname in os.listdir(label_dir): if not fname.endswith(.txt): continue with open(os.path.join(label_dir, fname)) as f: for i, line in enumerate(f): parts line.strip().split() if len(parts) ! 5: bad.append((fname, i, 字段数不对)) continue vals list(map(float, parts[1:])) if any(v 0 or v 1 for v in vals): bad.append((fname, i, f坐标越界: {vals})) return bad issues check_labels(labels/train) for item in issues[:20]: print(item) print(f共发现 {len(issues)} 处问题)5.2 验证集 mAP 远低于训练集现象训练集 mAP50 到 0.9验证集只有 0.4 到 0.5差距悬殊。原因典型过拟合。412 张图对检测模型来说偏少模型把训练样本背下来了。另外如果 train 和 valid 的划分不是随机打散的比如 valid 里全是某种光照条件下的图分布差异也会导致这个现象。解决先确认 train.json 和 valid.json 的划分是否随机。如果 valid 集中某类缺陷占比和 train 差异大说明划分有偏。对策是重新随机划分或者用交叉验证。增强方面把 mosaic、mixup 开大scale 范围拉宽。如果还不行换更小的模型或者加 weight_decay。5.3 小目标针孔漏检严重现象擦伤、脏污、褶皱都检得出来唯独针孔召回率极低很多小针孔框都不出。原因针孔在 640 分辨率下像素太少经过骨干网络多次下采样后特征几乎消失。这是小目标检测的通用难题不是数据问题。解决三个方向。一是提高输入分辨率到 1024 或 1280让针孔占据更多像素。二是用带 P2 检测层的模型结构P2 对应更高分辨率的特征图专门抓小目标。三是推理时用切片推理把大图切成小块分别检测再合并等效于放大了小目标。我一般先试提高分辨率成本最低。5.4 类别 id 映射错位导致类别全错现象模型能检出框但类别标签全是错的比如针孔被标成褶皱。原因COCO 的 category_id 和 YOLO 的 class index 没对齐。比如 COCO 里 id 是 1、2、3、4你直接拿 category_id 当 class_id 用但 YOLO 要求从 0 开始结果所有类别偏移了一位。或者 data.yaml 里 names 的顺序和转换时的 class_map 不一致。解决转换前先把 categories 打印出来明确每个 id 对应的缺陷名然后手动建立 class_map。转换后再抽查几张图可视化确认类别名和框对得上。这个坑一旦踩了训练几小时全白费所以可视化验证不能省。5.5 图片和 label 文件名不匹配现象训练时提示大量图片找不到 label或者有效训练样本数远少于预期。原因COCO 的 file_name 可能带路径前缀或者扩展名大小写不一致转换时生成的 label 文件名和图片文件名对不上。比如图片是 138.JPG 而 label 生成的是 138.txt某些系统区分大小写就会匹配失败。解决统一文件名处理逻辑转换时用os.path.splitext去掉扩展名再拼 .txt确保图片和 label 主名一致。训练前统计 images 目录和 labels 目录的文件主名集合取差集看有没有对不上的import os img_names {os.path.splitext(f)[0] for f in os.listdir(images/train)} lbl_names {os.path.splitext(f)[0] for f in os.listdir(labels/train)} print(f有图无 label: {len(img_names - lbl_names)}) print(f有 label 无图: {len(lbl_names - img_names)})两个数字都应该是 0不为 0 就说明有匹配问题先修好再训练。6. 进阶技巧用切片推理把针孔召回率拉上来小目标漏检是这份数据集最需要花心思的地方针孔这类几个像素的缺陷常规推理流程很难兼顾。我自己的习惯是训练用 640 或 1024 跑 baseline推理阶段对针孔单独上切片推理两套结果做融合。切片推理的思路不复杂把原图按固定尺寸切成有重叠的小块每块单独送进模型检测再把所有小块的检测框映射回原图坐标最后用 NMS 去重。这样每个小目标在某个切片里都会变成相对大的目标召回率能明显提升。以 SAHI 库为例核心调用是这样from sahi import AutoDetectionModel from sahi.predict import get_sliced_prediction # 加载训练好的 YOLO 模型 detection_model AutoDetectionModel.from_pretrained( model_typeyolov8, model_pathruns/detect/train/weights/best.pt, confidence_threshold0.25, devicecuda:0 ) # 切片推理slice 尺寸 512重叠 128 result get_sliced_prediction( test_image.jpg, detection_model, slice_height512, slice_width512, overlap_height_ratio0.25, overlap_width_ratio0.25 ) # 导出结果 result.export_visuals(export_dirsahi_output/)slice_height 和 slice_width 设成 512是因为针孔在 512 的切片里相对占比比在整图里大得多。overlap 比例 0.25 是为了避免目标正好落在切片边界被切断重叠区域保证边界目标至少在一个切片里是完整的。confidence_threshold 设 0.25 比常规的 0.5 低因为切片推理会产生更多候选框靠后面的 NMS 去重阈值低一点保证召回。切片推理的代价是推理速度成倍增加。一张 4000 像素宽的图512 切片加 0.25 重叠大概要切几十块每块过一次模型。产线实时检测场景下要权衡常见做法是只对疑似有针孔的区域做切片先用整图推理粗筛再对候选区域精检。另一个技巧是把切片推理的结果和整图推理的结果做加权框融合WBF比单纯 NMS 更能保留小目标。验证切片推理有没有效果别只看整体 mAP要单独统计针孔这一类的召回率。做法是在验证集上分别跑整图推理和切片推理按类别算 AP对比针孔那一栏的变化。如果针孔 AP 从 0.3 提到 0.6 以上说明切片策略生效了代价是推理耗时增加这个取舍要根据你的产线节拍来定。从那以后我每次拿到小目标工业数据集都强制先跑一遍整图 baseline再跑一遍切片推理把两类结果的分类别 AP 拉出来对比确认小目标那一类的提升幅度值不值得多花的推理时间再决定上线用哪套。希望这份数据集的拆解和这些踩坑记录能帮你少走点弯路。本文还有配套的精品资源点击获取
返回列表