ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

钢材表面缺陷检测实战:基于YOLOv5的数据集训练与部署

钢材表面缺陷检测实战:基于YOLOv5的数据集训练与部署 简介这套基于YOLOv5的钢材表面缺陷检测资源面向工业视觉质检人员与深度学习开发者专注于解决钢材表面裂纹、凹坑、氧化皮、划痕等缺陷的自动识别与定位问题适用于产线质检、材料科学实验等场景。压缩包共包含2000个文件大小约501.32MB主要文件类型包括JPG钢材图像样本、TXT与XML格式的缺陷标注、YAML模型配置、Python训练与推理脚本、预训练权重及可视化辅助文件等兼顾数据、代码与说明文档目录结构清晰。目前已有2497人学习下载内容完整且实践性强。通过学习读者可以了解YOLOv5中CSPNet骨干网络、SPP-Block、特征金字塔及Mish激活函数等关键模块的实际应用并借助配套的数据增强与多尺度训练策略快速构建一套可运行的钢材缺陷检测方案为实际工业部署提供直接参考。1. 钢材表面缺陷检测为什么绕不开yolov5钢材表面缺陷检测是那种看起来谁都能做、做起来谁都想摔键盘的视觉任务。钢板在产线上跑起来之后人工眼睛盯不过三十分钟传统图像处理对划伤和麻点还能凑合碰上裂纹和氧化皮压入这种“背景长得很像缺陷”的情况规则阈值调到哪里都像玄学。而yolov5钢材表面缺陷数据集这个组合恰好把这类任务推进到可以落地yolov5检测管线成熟、超参数资料多、部署链路短钢材表面缺陷数据集虽然样本量不算大但类别定义清晰、标注完整适合先拿它验证模型和产线方案的匹配度。这套方案适合三类人想快速验证质检自动化可行性的工程师、准备给产线部署目标检测模型的团队、刚接触yolov5但手里正好有一批钢板图片的开发者。先说一个反直觉结论这套流程真正花时间的不是训练而是把数据整理成yolov5吃得了的格式目录和标签错一个字符后面全白干。2. 钢材表面缺陷数据集长什么样先从六类缺陷读起我拿到这类钢材数据集后的第一件事不是急着开训而是把六类缺陷从头到尾看一遍。别嫌这一步土后面所有玄学问题一半都出在没看清数据上。常见的钢材表面缺陷公共数据集是灰度图单张尺寸不大每张图里缺陷的数量、大小、对比度差别非常大。如果直接把整包数据丢给yolov5训练你会在后续排错时完全搞不懂它为什么漏检——因为问题很可能不在模型而在你根本没注意到某一类缺陷的形态特征。2.1 六类缺陷的类别分布与标注格式钢材表面缺陷数据集一般包含六个类别裂纹、夹杂、麻点斑块、点蚀表面、氧化皮压入、划伤。它们对应的英文标签在转换时必须原样保留尤其是带下划线的pitted_surface和rolled-in_scale错一个字符就会导致类别错位这是做这套方案最容易踩的第一个坑。class_id类别名视觉特征易混类别0crazing细密网状裂纹纹理和背景接近rolled-in_scale1inclusion颗粒状夹杂灰度比背景亮或暗patches2patches大块灰斑边缘模糊inclusion3pitted_surface小而深的点状凹坑patches4rolled-in_scale压入的氧化皮形状不规则crazing5scratches长条形划伤方向较一致inclusion这些类别的共性问题是类间相似度高于类内差异。crazing和rolled-in_scale在很多样本里人眼都难以一眼区分patches和pitted_surface更是经常同时出现在一张图上。标注格式一般是VOC风格也就是 XML 文件每个缺陷目标用一个bndbox给出左上角和右下角的绝对像素坐标。这个绝对值坐标是后面转换成 yolo 标签的关键不能直接把xmin和xmax原样写进 txt必须归一化。2.2 数据划分按类别均衡切分别让验证集“开天窗”划分数据集时不能只做纯随机要保证每一类缺陷在train、val、test里的比例大致相同。钢材表面缺陷数据里各类总数虽然接近但纯随机划分很容易让某一类里较特殊的样本全跑进val导致训练时见过的形态和验证时完全不一致mAP 低得莫名其妙。我一般用固定随机种子按类别分层抽样保证每个子集里六类都有。import os import random import shutil random.seed(2024) data_root NEU-DET # 原始数据目录 out_root steel_data # 输出目录 split_ratio {train: 0.8, val: 0.1, test: 0.1} # 先按图片名收集names.txt 里的每一行应该是图片文件名不含扩展名 with open(os.path.join(data_root, names.txt), r) as f: names [line.strip() for line in f if line.strip()] random.shuffle(names) n len(names) n_train int(n * split_ratio[train]) n_val int(n * split_ratio[val]) for phase, name_list in [ (train, names[:n_train]), (val, names[n_train:n_train n_val]), (test, names[n_train n_val:]), ]: img_dir os.path.join(out_root, images, phase) xml_dir os.path.join(out_root, annotations, phase) os.makedirs(img_dir, exist_okTrue) os.makedirs(xml_dir, exist_okTrue) for name in name_list: # 假设原始图片是 .jpg标注是 .xml按实际扩展名改 shutil.copy(os.path.join(data_root, images, name .jpg), os.path.join(img_dir, name .jpg)) shutil.copy(os.path.join(data_root, annotations, name .xml), os.path.join(xml_dir, name .xml))这段逻辑的核心是按比例分层拆分并把图片和标注同步拷贝保证同一个样本不会跨train和val出现。random.seed(2024)是固定随机种子换机器重跑也得到同样划分后面排查数据泄漏时有据可查。split_ratio里 test 占 0.1是因为这类数据集本身不大test 留足一个类别的代表性样本数量即可。注意原数据的扩展名不一定是.jpg有的是.bmp或.png脚本里按实际扩展名改。提示划分完成后检查每类在三个子集里的数量如果某一类在val里不足 10 张建议调整随机种子重跑一次避免验证时统计噪声过大。3. 把VOC标注转成yolov5的txt转换脚本与目录结构yolov5训练自己的数据集时读取的标签文件不是 XML而是每个图片对应的同名.txt文件。每行开头是类别 id后面跟着归一化后的中心点 x、中心点 y、宽、高共四个浮点数。转换这件事听起来容易但坐标来源、边界钳制、空标签处理三处做不好训练阶段就会给你演一出“loss很低但什么都检测不出来”的戏。3.1 为什么yolov5只认txt标签在 yolo 系列的设计里训练脚本不会去解析 XML 或 JSON它只认与图片同名的 txt。这样做的好处是读取速度快、格式统一缺点是标注格式转换成了绕不开的预处理步骤。如果环境还没配好先把 yolov5 源码拉下来装好 requirements跑通官方detect.py确认环境没问题再继续下面的转换。这一步不顺后面再着急也是空转。转换时最稳妥的信息来源是 XML 里的size节点它记录了图片原始宽高。不要从图片文件名去猜尺寸有些数据集里的图片被二次压缩过文件名和实际尺寸对不上一旦猜错所有归一化坐标全部偏移。3.2 转换脚本读XML、归一化、写txt下面这个脚本是我在钢材缺陷数据上常用的转换方式直接照着改路径就能跑。import xml.etree.ElementTree as ET import os class_map { crazing: 0, inclusion: 1, patches: 2, pitted_surface: 3, rolled-in_scale: 4, scratches: 5, } def convert_xml(xml_path, out_txt_path): tree ET.parse(xml_path) root tree.getroot() # 宽高一定从 xml 里读不要从文件名猜 size root.find(size) width int(size.find(width).text) height int(size.find(height).text) lines [] for obj in root.iter(object): name obj.find(name).text if name not in class_map: print(fskip unknown class: {name}) continue bndbox obj.find(bndbox) xmin float(bndbox.find(xmin).text) ymin float(bndbox.find(ymin).text) xmax float(bndbox.find(xmax).text) ymax float(bndbox.find(ymax).text) # 防止标注越界或出现负宽高 xmin max(0.0, xmin) xmax min(width, xmax) ymin max(0.0, ymin) ymax min(height, ymax) cx ((xmin xmax) / 2.0) / width cy ((ymin ymax) / 2.0) / height bw (xmax - xmin) / width bh (ymax - ymin) / height if bw 0 or bh 0: continue lines.append(f{class_map[name]} {cx:.6f} {cy:.6f} {bw:.6f} {bh:.6f}) with open(out_txt_path, w, encodingutf-8) as f: f.write(\n.join(lines)) # 批量转换 xml_dir steel_data/annotations/train label_dir steel_data/labels/train os.makedirs(label_dir, exist_okTrue) for xml_name in os.listdir(xml_dir): if not xml_name.endswith(.xml): continue stem xml_name[:-4] convert_xml( os.path.join(xml_dir, xml_name), os.path.join(label_dir, stem .txt) )这段逻辑里有三个地方值得说明。第一宽高从 XML 的size读取并把 xmin/xmax/ymin/ymax 先钳到图片边界内避免标注本身的越界错误被原样带入训练。第二归一化后的值是 0 到 1 之间的浮点数写入时保留 6 位小数精度完全够用类名不在class_map里的目标直接跳过并打印不能静默忽略不然你会漏掉一整类缺陷。第三宽高为 0 的框直接丢弃这类坏标注通常会触发后续训练的 NaN 或 loss 异常。参数上xml_dir和label_dir是必改的两个路径class_map在你自己扩充类别时再增加映射。3.3 写data.yaml并把目录结构检查一遍转换完成后在steel_data目录下建data.yaml这是 yolov5 训练时的数据入口。train: steel_data/images/train val: steel_data/images/val test: steel_data/images/test nc: 6 names: 0: crazing 1: inclusion 2: patches 3: pitted_surface 4: rolled-in_scale 5: scratchestrain和val指向图片目录yolov5 会自动去同级目录下找labels。注意路径用相对路径时命令必须在steel_data的上一级目录执行否则 yolo 会找不到文件。names列表的索引必须和上一节class_map里的 id 一一对应这是整个流程里最容易出“类别错位”的地方。检查目录时我一般跑下面这行命令find steel_data/labels -name *.txt -empty -print | head这条命令找出所有空标签文件。空标签在训练里会被当成无目标的背景图如果转换脚本出错大量图片的 txt 都是空文件模型训练初期就会明显异常。同时还要核对图片和标签是否同名同数这一步放在后面的避坑清单里细说。4. 用yolov5训练钢材缺陷模型超参数、anchors与loss判断数据准备好之后才进入训练阶段。钢材表面缺陷数据集的图像普遍不大原始图通常只有 200×200 左右的分辨率这和公开 COCO 数据里的自然图像差异很大。所以训练配置不能照搬网络上的默认参数需要针对小尺寸图像和细密纹理做调整。4.1 模型选型与训练命令为什么用yolov5s而不是lyolov5 的目标检测模型按规模从大到小有 n、s、m、l、x钢材表面缺陷数据量不大类别数只有 6我用yolov5s作为主力。s 模型特征提取能力足够记住六类纹理差异显存占用和推理速度也更适合后面迁移到边缘设备换成 l 或 x 在小数据上反而更容易过拟合训练时间翻倍mAP 还不一定更高。python train.py \ --data steel_data/data.yaml \ --weights yolov5s.pt \ --img 384 \ --batch 16 \ --epochs 120 \ --patience 20 \ --project runs/steel这里最关键的是--img 384。原始图只有 200×200直接上 640 会把图片拉伸放大三倍缺陷形态被过度插值模型学到的全是被平滑过的假纹理384 相当于放大约两倍细节保留得足够显存占用也更友好。--batch 16在 8GB 显存上可以稳定跑显存再小就降到 8但梯度噪声会变大建议同步把学习率调低。--patience 20表示连续 20 个 epoch 验证集指标不提升就提前停止钢材缺陷数据训练到 60 个 epoch 左右往往会收敛没必要傻跑满 120。4.2 yolov5超参数怎么设lr0、mosaic、fliplr的边界yolov5超参数里改动对结果影响最直接的是学习率、数据增强和类别损失权重。我常用的起步超参数如下表你可以根据第一次训练的结果再做微调。参数推荐值理由lr00.001小数据集下默认0.01容易震荡第一轮先压低求稳lrf0.1让学习率在后期衰减到初始值的1/10mosaic1.0四图拼接能增强小目标上下文但对200×200图作用有限fliplr0.5钢材缺陷左右翻转后仍是真实样本可以用scale0.5缩放幅度太大容易把缺陷缩成模糊小点0.5足够copy_paste0.1钢材缺陷边缘复杂粘贴后轮廓容易失真开太低lr0是这组参数里最值得花时间的。钢材缺陷数据量小学习率太高时前几个 epoch 损失就会跳到 nan或者收敛到局部极小点后 mAP 停在低位。我一般第一轮用 0.001 跑通确认 loss 曲线平滑后再试着提到 0.005看训练是否更快收敛。mosaic对多目标自然图像帮助很大但原始图本身只有 200×200拼接之后单个缺陷被进一步缩小所以保持默认即可不要为了增强而增强。4.3 anchors与输入尺寸小缺陷漏检的关键yolov5 在训练时会根据数据集标注自动做 kmeans 聚类重新计算 anchors。流程上你不需要手动指定 anchors但你需要注意如果是小像素缺陷占比高默认 640 尺寸下算出来的 anchors 整体偏大小目标的召回率会明显偏低。我的做法是先把训练尺寸固定到 384让聚类算法自动生成一套适配这个尺寸的 anchors跑完第一个完整训练后再看各类 mAP如果pitted_surface和patches这类小区域缺陷的召回率低于 0.5我会手动把锚框尺寸列表中偏小的三组数值再缩小一档重新训练。这个操作不能盲目做每次只改一档观察一次完整训练的验证集指标再决定是否继续。4.4 训练时怎么判断模型没跑偏训练日志里同时有train/box_loss、train/obj_loss、train/cls_loss和对应的val_*指标。钢材缺陷场景里我最看重val/obj_loss它反映模型对“有没有目标”的判断能力。正常曲线上 obj_loss 应当稳步下降并最终稳定在一个低位如果 train loss 一直在降val loss 在某个 epoch 后反而上升说明模型开始死记训练样本此时早停触发比硬着头皮多跑几个 epoch 有用。训练结束后重点看两个指标mAP0.5和各类的 precision/recall。mAP0.5在钢材缺陷数据上达到 0.8 以上算是模型可用的起点如果某个类别召回率低先不要急着调模型回头检查该类的样本数量和标注质量。5. 钢材表面缺陷检测避坑清单五条让模型翻车的坑前面几章讲的是标准流程这一章写我在钢材表面缺陷数据集上实际踩过、也看别人踩过的坑。每条都按现象、原因、解决的顺序来写方便你对照排查。5.1 图片和标签对不上loss很低但mAP几乎为零现象训练日志里 loss 一路下降看起来一切正常但验证集 mAP 只有 0.1 甚至 0。原因数据划分或转换时某些图片缺失对应的 txt 标签文件。yolov5 加载图片后找不到标签会把它当成无目标的纯背景图。钢材缺陷数据集里的负样本极少模型被迫从大量“假背景”里学习最终学成“什么都别检测”。解决训练前检查每个图片目录下是否有同名标签文件。for img in steel_data/images/train/*.jpg; do base$(basename $img .jpg) if [ ! -f steel_data/labels/train/$base.txt ]; then echo missing label: $base fi done这段逻辑是遍历训练集所有 jpg逐个核对同名 txt 是否存在。缺标签的文件名字会打印出来逐个补齐后再训练。图片扩展名如果是.bmp或.png把.jpg替换成对应扩展名。5.2 类别名写错下划线和连字符导致的类别错位现象训练完后查看 PR 曲线各类的顺序和你脑子里对不上或者有两个类别的 mAP 恰好像是被“交换”了。原因钢材缺陷类别名里存在pitted_surface和rolled-in_scale这种带下划线或连字符的名称。XML 里是rolled-in_scaledata.yaml 里如果手写成rolled_in_scaleclass id 顺序就会整体错位yolov5 不会报错只会安安静静把标签对应到错误类别。解决以class_map为唯一事实源转换脚本和 data.yaml 都从同一份类别清单生成不要手工维护两份。改类别名时先看原数据里的确切写法复制粘贴进脚本不要凭印象手打。5.3 灰度图被误读成单通道训练或推理时维度不一致现象图片预处理时报 shape 维度错误或者训练能跑但推理时检测框数量少得异常。原因钢材表面缺陷数据集大多是灰度图但 yolov5 源码里用 OpenCV 读取图片时默认IMREAD_COLOR会拿到三通道数组如果某些脚本用 PIL 读取或重新保存成单通道 PNG输入维度就变成(H, W)batch 拼接时直接报错。解决统一用 OpenCV 读图并在预处理里强制转成三通道。检查你的数据增强或自定义 Dataset 代码里有没有np.expand_dims之类的操作如果有删掉让输入保持(H, W, 3)。重新保存图片时用cv2.imwrite不要用 PIL 的L模式。5.4 小缺陷目标漏检尤其是点蚀和灰斑现象pitted_surface和patches的召回率明显低于其他四类模型像“眼瞎”一样漏掉那些小色块。原因这两类缺陷在原始图里往往只占几十个像素经过 384 尺寸的缩放后更小。默认 anchors 偏大小目标的先验框匹配不上模型在训练阶段就没机会学习到它们的特征。解决先把训练尺寸降到 320 或 384让缺陷在输入图像中占比更大保留mosaic增强它在拼接时会强制模型看到更强的上下文训练后单独看test集的混淆矩阵确认是哪一类出了问题再微调 anchors不要凭感觉全盘调整。5.5 loss变成nan模型训练中途报废现象epoch 还没跑到 10train/box_loss直接跳到 nan之后所有指标全部失联。原因两个常见来源。一是lr0设置过高加上batch太小某个异常样本的梯度把权重带飞二是数据里有坏标注比如宽高为 0 的框、坐标超出图片边界的框经过归一化后产生非法值。解决先按 4.2 节的参数把lr0降到 0.001、batch设为 16跑一次训练确认正常再检查转换后的 txt 文件里有没有负数和大于 1 的坐标值。转换脚本里的边界钳制和宽高判断就是专门防这个问题的不要为了图省事删掉。6. 部署前验证后处理阈值怎么调mAP怎么看模型训练完成不代表可以直接上产线。钢材表面缺陷检测的落地验证我习惯先从一张产线上没见过的钢板图片开始而不是直接看整体 mAP。python detect.py \ --source samples/real_line.jpg \ --weights runs/steel/exp/best.pt \ --conf 0.25 \ --iou 0.45--conf是置信度阈值低于这个值的框会被后处理过滤掉--iou是 NMS 的 IoU 阈值控制两个重叠框保留谁的策略。钢材缺陷里patches和pitted_surface经常紧挨着出现iou设到 0.5 以上时相邻框会被合并容易把两个独立缺陷并成一个0.45 一般能保持较好的分离度。conf的调整逻辑是先设 0.5 看有没有漏检再设 0.25 看误检数量选一个误检可以接受的最高阈值作为产线参数。光盯着 mAP 没用同一批钢板良品被误判为缺陷比缺陷漏检更让产线崩溃因为人工复检的工作量会成倍增加。mAP0.5和mAP0.5:0.95是两个不同口径。mAP0.5只要求预测框和真实框的 IoU 超过 0.5 就算命中适合产线“检出来就行”的需求mAP0.5:0.95对定位精度要求更严检测框稍微偏一点都会拉低分数。钢材缺陷检测里如果只是做检出和分类前者达到 0.8 就可以进入产线实测如果后面要做机械臂定位打磨才需要追求后者。验证脚本跑通之后再考虑 yolo 模型往边缘设备迁移比如树莓派5上部署自己训练的yolov5模型做实时抽检。PC 上的检测流程与边缘端一致差异主要在模型导出格式和推理框架上优先用小尺寸的 s 模型和 int8 量化能省一半以上的延迟。我的习惯是每次部署新模型前把同一批测试图片用不同阈值各跑一遍结果截图留档标出哪些是漏检、哪些是误检再决定要不要调训练参数。这比盯着训练曲线猜问题高效得多。希望帮到你。本文还有配套的精品资源点击获取
返回列表