news 2026/9/28 14:58:36

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

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
铝片表面缺陷检测数据集实战: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", encoding="utf-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", encoding="utf-8") as f: data = json.load(f) # 建立 image_id 集合 image_ids = {img["id"] for img in data["images"]} # 检查 1:json 里记录的图片文件是否都存在 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)}") # 检查 3:bbox 是否有零宽零高或越界 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_width
  • y_center = (y + h/2) / img_height
  • norm_w = w / img_width
  • norm_h = h / img_height

class_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_ok=True) with open(coco_json, "r", encoding="utf-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.yaml

data.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(f"images/train/{sample}", f"labels/train/{os.path.splitext(sample)[0]}.txt", names)

如果框整体偏移、宽高明显不对,大概率是归一化时用错了宽高,或者 bbox 格式理解成了右下角坐标。如果类别名全错,检查 class_map 和 names 的顺序是否对齐。这一步花五分钟,能避免训练几小时后才发现数据是错的。

4. 训练配置与数据增强:小样本工业缺陷的调参思路

412 张图在目标检测里属于小样本,直接套默认配置很容易过拟合,训练集 loss 降得很低但验证集 mAP 上不去。这一章讲清楚训练时几个关键参数怎么设,以及针对四类缺陷做什么增强。

4.1 训练参数设置与含义

以 YOLOv8 为例,一份针对小样本工业缺陷的起手配置:

yolo detect train \ data=dataset/data.yaml \ model=yolov8n.pt \ epochs=200 \ imgsz=640 \ batch=16 \ lr0=0.001 \ lrf=0.01 \ warmup_epochs=5 \ patience=50 \ augment=True \ mosaic=1.0 \ mixup=0.1 \ degrees=15.0 \ translate=0.1 \ scale=0.5 \ fliplr=0.5 \ flipud=0.0 \ hsv_h=0.015 \ hsv_s=0.7 \ hsv_v=0.4

逐个说关键参数。model 选 yolov8n 是因为数据量小,大模型参数量多更容易过拟合,n 版本作为 baseline 先跑通再考虑换大。epochs 给 200 配合 patience=50,意思是验证指标 50 轮不提升就早停,避免无效训练。lr0 初始学习率 0.001 比默认的 0.01 小,小数据集上大学习率容易震荡。mosaic=1.0 是 YOLO 的招牌增强,把四张图拼成一张,对小样本扩充效果明显,但工业缺陷里针孔这种小目标在拼接后可能被裁掉,如果发现小目标召回低可以降到 0.5。degrees=15 做小角度旋转,铝片缺陷方向不固定,适度旋转合理,但褶皱这类有方向性的缺陷旋转太大会破坏特征,15 度是保守值。flipud=0.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_type="yolov8", model_path="runs/detect/train/weights/best.pt", confidence_threshold=0.25, device="cuda:0" ) # 切片推理:slice 尺寸 512,重叠 128 result = get_sliced_prediction( "test_image.jpg", detection_model, slice_height=512, slice_width=512, overlap_height_ratio=0.25, overlap_width_ratio=0.25 ) # 导出结果 result.export_visuals(export_dir="sahi_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 拉出来对比,确认小目标那一类的提升幅度值不值得多花的推理时间,再决定上线用哪套。希望这份数据集的拆解和这些踩坑记录,能帮你少走点弯路。

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

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

存储选型、数据建模到迁移实施:一套完整的数据搬迁实战指南

1. 存储选型&#xff1a;先搞清楚你的数据是哪种“性格”做迁移项目&#xff0c;最容易犯的错不是技术不够&#xff0c;而是一上来就纠结“用哪个牌子的存储”。我见过太多人把存储选型做成品牌对比会&#xff0c;最后买了一堆高性能设备&#xff0c;结果装完才发现存的全是图片…

作者头像 李华
网站建设 2026/9/28 14:56:53

YOLOv8教室人数统计实战:从训练到Gradio可视化部署

简介&#xff1a;这份资源是面向计算机、人工智能、通信工程、自动化等专业学生与教师的YOLOv8目标检测实战项目&#xff0c;聚焦智慧教室场景下的人数统计任务&#xff0c;可作为毕业设计、课程设计或大作业的完整参考方案。压缩包共8个文件&#xff0c;包含3个Python脚本、3个…

作者头像 李华
网站建设 2026/9/28 14:56:50

致好久不见的你:从消息到重逢,主动联系旧友的实操指南

致好久不见的你——这句话在我手机备忘录里存了大半年&#xff0c;一直没有发出去。起因很普通。某个周末深夜&#xff0c;手机相册弹出一则“去年的今天”&#xff0c;照片里是几个围着火锅大笑的人。我盯着那几个人辨认了好一会儿&#xff0c;才想起来其中两张脸属于谁。一个…

作者头像 李华
网站建设 2026/9/28 14:54:30

PyTorch猫狗公鸡三分类实战:从环境配置到模型部署全流程指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/28 14:54:12

CrewAI智能体开发:封装S3读取工具的全流程实践

作为一个长期在数据管道和AI Agent之间来回折腾的人&#xff0c;我越来越觉得“给智能体配好工具”才是落地过程中最容易被低估的环节。模型选得再好&#xff0c;计划排得再漂亮&#xff0c;最后拉不到数据、读不了文件&#xff0c;整个流程就是空中楼阁。今天想拆解的这个小项…

作者头像 李华
网站建设 2026/9/28 14:54:11

AI资讯日报热词背后的实战指南:从Agent到本地部署

1. 从热搜词里读AI走向&#xff1a;这届资讯日报该怎么看先说结论&#xff1a;我不太喜欢把“AI资讯日报”写成新闻清单&#xff0c;什么“某某公司发布新模型”“某某产品完成融资”&#xff0c;一天十条转发&#xff0c;看完就忘。真正值得看的&#xff0c;是那些反复出现、让…

作者头像 李华