news 2026/9/29 18:48:32

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

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
钢材表面缺陷检测实战:基于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_scale
1inclusion颗粒状夹杂,灰度比背景亮或暗patches
2patches大块灰斑,边缘模糊inclusion
3pitted_surface小而深的点状凹坑patches
4rolled-in_scale压入的氧化皮,形状不规则crazing
5scratches长条形划伤,方向较一致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_ok=True) os.makedirs(xml_dir, exist_ok=True) 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(f"skip 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", encoding="utf-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_ok=True) 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: scratches

train和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而不是l

yolov5 的目标检测模型按规模从大到小有 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/10
mosaic1.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 有用。

训练结束后重点看两个指标:mAP@0.5和各类的 precision/recall。mAP@0.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_scale,data.yaml 里如果手写成rolled_in_scale,class 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 还没跑到 10,train/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 没用,同一批钢板,良品被误判为缺陷比缺陷漏检更让产线崩溃,因为人工复检的工作量会成倍增加。

mAP@0.5和mAP@0.5:0.95是两个不同口径。mAP@0.5只要求预测框和真实框的 IoU 超过 0.5 就算命中,适合产线“检出来就行”的需求;mAP@0.5:0.95对定位精度要求更严,检测框稍微偏一点都会拉低分数。钢材缺陷检测里,如果只是做检出和分类,前者达到 0.8 就可以进入产线实测;如果后面要做机械臂定位打磨,才需要追求后者。

验证脚本跑通之后,再考虑 yolo 模型往边缘设备迁移,比如树莓派5上部署自己训练的yolov5模型做实时抽检。PC 上的检测流程与边缘端一致,差异主要在模型导出格式和推理框架上,优先用小尺寸的 s 模型和 int8 量化,能省一半以上的延迟。

我的习惯是每次部署新模型前,把同一批测试图片用不同阈值各跑一遍,结果截图留档,标出哪些是漏检、哪些是误检,再决定要不要调训练参数。这比盯着训练曲线猜问题高效得多。希望帮到你。

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

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

从脚本散落到CLI-Anything:构建可扩展的命令行工具链

先说个场景&#xff1a;你电脑里是不是也躺着一堆脚本&#xff0c;build.sh、deploy.py、cleanup.js&#xff0c;名字各异&#xff0c;位置分散&#xff0c;时间一长自己都忘了哪个是干嘛的&#xff1f;我前几年就处在这种状态里&#xff0c;每次要发个版本&#xff0c;得先翻终…

作者头像 李华
网站建设 2026/9/29 18:47:32

项目输入四要素:从标题到摘要,打造清晰博文创作基础

要生成博文&#xff0c;需要你给我完整的项目输入&#xff0c;仅凭“项目标题: 内景 美术馆(Art Gallery)”这一个字段&#xff0c;我只能猜方向&#xff1a;是写美术馆空间摄影技巧、画廊展览策划复盘&#xff0c;还是室内设计案例分析&#xff0c;全都无从落笔。 所以请把下…

作者头像 李华
网站建设 2026/9/29 18:46:33

数据中心机房建设方案:八大子系统设计与UPS、空调负荷计算实操

简介&#xff1a;这份《数据中心机房建设方案》文档面向数据中心规划、机房工程设计与运维人员&#xff0c;以及需要了解IDC建设流程的IT从业者&#xff0c;系统梳理了从需求分析到技术选型的完整建设思路。内容围绕机房基础设施、IT基础设施、业务系统与数据管理三大板块展开&…

作者头像 李华
网站建设 2026/9/29 18:46:31

2026安阳景区古建牌坊检测排名 TOP5 CMA 资质机构提供牌坊裂缝检测、牌坊倾斜检测、老化检测 联系方式推荐

在安阳这座承载殷商文明与北魏石窟的千年古都&#xff0c;散落于景区、乡村与街巷间的古建牌坊&#xff0c;既是石木镌刻的历史坐标&#xff0c;也是文旅传承的视觉脊梁。然而放眼本地检测市场&#xff0c;机构虽鳞次栉比&#xff0c;服务质量却鱼龙混杂。不少无资质团队出具的…

作者头像 李华
网站建设 2026/9/29 18:45:18

多卡GPU训练扩展效率:为什么双卡跑不满两倍速度?

说实话&#xff0c;第一次把第二张 GPU 插进机器、满怀期待地跑起 LLM 训练&#xff0c;然后看到训练时间只缩短了三分之一的时候&#xff0c;我第一反应是怀疑自己买到了假卡。群里一问&#xff0c;才发现这不是我一个人踩过的坑&#xff0c;几乎每个从单卡切到多卡的人都会经…

作者头像 李华
网站建设 2026/9/29 18:44:47

NanoJev:面向结构化决策的并行概率模型

1. 这不是又一个“小模型”——NanoJev 的本质是决策范式的切换你可能已经刷到过“0.6B 参数”“CPU 可跑”“轻量级 LLM”这类标题&#xff0c;但 NanoJev 不是另一个试图在手机上跑通 Qwen 的工程缝合怪。它解决的压根不是“能不能跑”的问题&#xff0c;而是“该不该这么跑”…

作者头像 李华