尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

铝片表面缺陷检测的YOLOV5实战:从数据集规范到部署调优

铝片表面缺陷检测的YOLOV5实战:从数据集规范到部署调优 简介铝片表面缺陷目标检测数据集面向工业质检与目标检测实践场景按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, fvis_{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做预训练权重输入 640batch 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 的方式与常见报错验证输出会打印mAP0.5、mAP0.5:0.95、Precision、Recall 四个指标工业场景里怎么读这四个数有讲究指标含义铝片场景解读mAP0.5IoU 0.5 下的平均 AP缺陷是否被大致框住产线能否用mAP0.5:0.95严格平均 AP小目标检测能力的真实反映Precision检出的目标里正确的比例误检多时这个值掉得明显Recall真实缺陷中被找出的比例漏检多时这个值掉得明显针孔这类小目标通常会把 mAP0.5:0.95 拉下来很多比 mAP0.5 低 10 个点以上都算正常不用急着以为训练失败。真正需要警惕的是 Precision 和 Recall 同时偏低说明模型既找不全也找不准。训练过程中实操排错频率最高的几个问题处理方式如下表报错或症状最常见原因处理方式CUDA out of memorybatch 太大或 cache 占显存batch 降到 8去掉 --cacheImage not founddata.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 输入下会退化到两三个像素卷积特征完全丢失阈值怎么调都救不回来。本文还有配套的精品资源点击获取
返回列表