1. 项目背景与整体思路拆解
先说结论:这个项目不是那种“拿着公开数据集跑个demo、晒个mAP就完事”的练手作品,而是一套从数据到部署都走通的钢轨表面缺陷检测方案。基于YOLOv5框架,配套了标注好的钢轨缺陷数据集,还提供了训练完的模型权重。拿来就能做推理,或者直接在自己的数据集上微调。做钢轨巡检、工业质检、机器视觉方向的朋友,应该能从这里找到不少可以直接抄作业的东西。
1.1 为什么钢轨缺陷检测要上深度学习
钢轨表面缺陷检测,听上去是个很传统的工业视觉问题,但真正做过的人才知道它有多难缠。传统方案一般是两类:一类是基于数字图像处理的,用灰度阈值、边缘检测、纹理分析来定位缺陷;另一类是基于传统机器学习的,手工提取特征再扔给分类器。这两种方案我都试过,刚开始在实验室的“干净”样本上效果还行,一旦上了现场就原形毕露。
主要痛点有三个:
- 光照环境不可控。户外钢轨检测是白天黑夜、阴晴雨雪都要跑的,同一个缺陷在不同光照下,灰度分布完全是两个模样。固定阈值方案在这种场景下直接失效。
- 缺陷形态千奇百怪。钢轨表面缺陷有裂纹、擦伤、剥离、掉块、锈斑等等,同一类缺陷的大小、走向、深浅差异也很大。手工设计特征根本枚举不完各种形态。
- 实时性要求高。巡检车跑起来速度不低,图像采集帧率一高,传统图像处理算法那点算力根本跟不上。
这种情况下,深度学习目标检测算法天然就是更合理的选项。它不需要人工设计特征,端到端地从数据里学;泛化能力比固定规则强太多;GPU推理速度也能满足实时要求。而在当前的目标检测模型选择里,YOLOv5是综合性价比最高的一个,后面细说。
1.2 YOLOv5选型:为什么不选Faster R-CNN,也不选YOLOv8
模型选型是项目正式动手之前的第一个关键决策。我先对比了主流的目标检测方案,筛选逻辑如下:
| 模型 | 速度 | 精度 | 部署难度 | 生态成熟度 |
|---|---|---|---|---|
| Faster R-CNN | 慢 | 高 | 中 | 高 |
| YOLOv3/v4 | 中 | 中 | 中 | 高 |
| YOLOv5 | 快 | 高 | 低 | 极高 |
| YOLOv8 | 快 | 高 | 低 | 中(较新) |
| 各类Transformer检测器 | 中 | 高 | 高 | 中 |
Faster R-CNN的精度确实不错,尤其是小目标召回上有优势,但两阶段的架构决定了它速度上不去。钢轨检测这类场景,后续要部署到巡检设备上做实时推理,速度这条必须优先满足。所以直接放弃了。
YOLOv8当时也考虑过,但有几个现实问题:一是项目启动时v8发布还不久,社区踩坑积累不够;二是v5的生态极其完善,从训练到导出再到部署,各种优化教程一大把,遇到问题容易找到解决方案;三是我手上已有的很多工具脚本都是基于v5写的,迁移成本也不低。综合评估下来,YOLOv5是当时最稳的选择。实际做下来,这个决策没让我后悔。
1.3 项目最终交付了什么
这个项目的成果物围绕“一条完整可用链路”来组织:
- 钢轨表面缺陷数据集:包含数千张标注图像,覆盖裂纹、擦伤、剥离、掉块、锈斑等典型缺陷类别,附带YOLO格式的标注文件,可直接用于训练,也可以做划分后的训练集/验证集/测试集。
- 训练好的模型权重:在自有数据集上完成训练和验证,mAP指标达到了可用水平,模型可直接加载推理。
- 推理脚本与交互逻辑:提供了基于PyTorch的推理脚本,支持图片、视频流输入,输出检测结果可视化。
- 数据标注规范与流程图:标注工作流转不起来,后面全白搭,所以数据和流程分开交付。
一句话概括:别的地方给你一个孤零零的权重文件,我这里把“原料+配方+成品+使用说明”一次性备齐了。
2. 数据集的构建与预处理
2.1 数据从哪来:采集策略决定效果上限
很多人不重视数据采集,觉得网上找个公开数据集下载下来就能训练。但工业场景和通用场景的差异非常大。公开数据集里的“钢轨”大多是旅行博主拍的风景照,几乎没有近景的、大光比的、带油污遮挡的钢轨表面图像。用公共数据集训练出来的模型,一上现场就漏检,一点不奇怪。
我的数据来源主要有两个渠道:
第一是自己搭建模拟采集环境。用线阵相机配合特定光源搭建了一条模拟钢轨表面采集平台,把实际钢轨样本放到平台上,以固定速度通过,模拟巡检车的工作方式。这样做的好处是采集条件可控,可以人为制造不同光照、角度、速度下的图像,覆盖测试工况中的大部分变量。
第二是从现场巡检视频中抽帧。用巡检车在试验线路上跑了几趟,采集了一部分现场视频数据,然后按帧提取。这里面有个坑:视频帧之间的信息高度冗余,直接全部抽出来会导致训练集和验证集之间出现“数据泄露”——同一块缺陷的相邻几帧既进了训练集又进了验证集,指标虚高得离谱。
处理方法是:连续帧按间隔抽样,具体来说每10帧取1帧,然后再用图像哈希算法做去重筛选,把高度相似的帧过滤掉。这个步骤很多人忽略,但它对指标真实性的影响非常大。
2.2 标注规范的制定:比动手标注更重要的事
数据标注是整个项目里耗时最长、也最容易返工的环节,没有之一。我踩过最大的坑就是:一开始没定标注规范,几个人各标各的,结果训练完发现模型对同一个缺陷的检测框一会儿大一会儿小,边界模糊得厉害。后来花了两周时间返工重标,代价惨痛。
现在把标注规范的核心要点整理出来,你们做类似项目时可以直接抄:
- 检测框必须紧贴缺陷的真实边界。缺陷检测不像行人检测,框的边缘贴着缺陷边界走,不要为了“框住整个区域”多留背景像素。背景像素混得越多,模型越难学清楚缺陷的真实特征。
- 对长条形缺陷,拆分成多个框进行标注,而非用一个细长的大框覆盖全部。因为YOLO系列的anchor机制对细长目标的回归天然存在劣势,拆开标注反而更好收敛。
- 标记类别优先按照“形态+成因”来分,而非按严重程度分。比如“裂纹”和“擦伤”要分开,不要统称“表面缺陷”。“严重裂纹”和“轻微裂纹”也建议合成一类,因为严重程度是连续变量,不适合做成离散类别。
- 标注争议区域统一用“疑似缺陷”规则处理,无法判断的先标记为待定,定期开标注评审会一起定。宁可丢一个样本,也不要标错一个样本。
标注工具用的是开源的LabelImg,操作简单、上手快。唯一要注意的是输出格式默认为Pascal VOC的XML格式,需要再转一步成YOLO格式的TXT标注文件。我在项目里写了一个转换脚本,批量做了格式转换,同时顺便检查了有没有越界坐标、空标注文件等低级错误。
2.3 数据扩增的艺术:不是每种扩增都有效
数据扩增是提升模型泛化能力的重要手段,但很多人陷入了一个误区:扩增方法越多越好,参数越狠越好。实际并不完全是这样。
以钢轨表面缺陷为例,我试过的扩增组合及效果如下:
| 扩增方法 | 是否采用 | 原因 |
|---|---|---|
| 随机水平翻转 | 采用 | 钢轨缺陷没有方向性语义,翻转不影响标注含义 |
| 随机亮度/对比度调整 | 采用 | 模拟不同光照条件,提升鲁棒性 |
| 高斯噪声/运动模糊 | 采用 | 模拟传感器噪声和运动模糊,缺陷是微小纹理,这部分特别关键 |
| 随机旋转小角度 | 采用 | 巡检图像中缺陷姿态略有变化,小角度旋转有效 |
| 马赛克增强 | 采用 | YOLOv5内置支持,丰富上下文信息 |
| 上下翻转 | 不采用 | 钢轨在图像中的方向一般是固定的,上下翻转会生成现实中不会出现的样本 |
| 随机裁剪大比例 | 不采用 | 大比例裁剪会破坏缺陷的上下文语义,反而增加误检 |
这里多说一句:扩增不是为了“变多”,而是为了让模型见过的图像分布更接近真实部署时的分布。比如巡检车在高速运动时图像会产生运动模糊,那就在训练时人为加模糊;光照变暗了,就调低亮度。这才是扩增的本质。
2.4 数据集划分与平衡处理
划分数据集时,我坚持了一个原则:同一块钢轨的缺陷图像只能出现在一个集合里,禁止跨集合混用。按照这个原则,把源头采集到的图像划分成训练集、验证集、测试集,比例约7:2:1。
关于类别不平衡问题,钢轨表面缺陷中“擦伤”占比很大,“掉块”占比很低。如果直接训练,模型会对多数类过拟合,少数类经常漏检。解决方案是先统计各类别实例数量,然后做了两类操作:
- 对样本量少的类别,提高其参与训练时被重复采样的概率;
- 对样本量大的类别,适当降采样,避免模型在训练中过度偏向。
在YOLOv5中,可以通过调整每个epoch的数据加载权重来实现,也可以用更简单的“过采样少数类”的办法。我把两种方式都试了,最终是在数据加载阶段使用了类别权重的方式,效果更平滑一些。
3. 模型训练全流程拆解
3.1 模型配置与预训练权重选择
YOLOv5提供了从小到大几个型号:n、s、m、l、x。模型越大,精度越高,但推理延迟也更大。在钢轨缺陷这个场景下,缺陷虽然不算极小的目标,但很多“裂纹”在整张图像里的像素占比很小,所以模型容量不能太小。我最后选的是YOLOv5m作为折中方案,单张图像推理时间在GPU上大概十几个毫秒,既能跑在边缘设备上,又有足够的容量去拟合细节特征。
训练时没有从随机初始化开始,而是加载了在大型通用数据集上的预训练权重。这背后的原理是:预训练模型已经学到了大量底层视觉特征(边缘、纹理、朝向等),这些特征在钢轨缺陷检测任务中依然成立。在预训练基础上做微调,比从头训练收敛快得多,而且不容易被小数据量带偏。
有个参数容易忽略:freeze冻结层数。开始训练前10个epoch冻结了主干网络,只更新检测头部分,让损失先降下来,之后再解冻主干做全量微调。这个操作能让训练过程更稳定,也能减少显存占用。实测下来,冻结合并微调的最终精度比直接全量训练略微高一点,虽然差异不算显著,但训练过程确实平稳很多。
3.2 关键训练参数的经验取值与调优心得
YOLOv5训练时的参数很多,我挑几个影响最大的说一下,附带我的经验取值:
图像尺寸imgsz。我做的是640。钢轨缺陷的物理尺寸一般在毫米到厘米级别,如果图像分辨率太低,细小裂纹会直接消失。640是一个精度和速度都比较平衡的点。之前试过1280,精度确实有提升,但训练显存占用翻了一倍不止,推理速度也明显下降。考虑到后续要部署做实时,最终锁定了640。
批大小batch-size。选了32,这是由显存决定的。批大小影响的是梯度的稳定性,太小了loss震荡严重,太大了容易跑满显存。在有限的硬件条件下,batch-size在16到64之间对最终效果影响不是特别大,但会影响训练速度和显存占用。建议优先满足显存约束,再尽量取大。
学习率策略。初始学习率设0.01,配合余弦退火策略。实际训练中,我观察过学习率曲线对loss的影响:固定学习率训练,后期容易在局部最优附近反复横跳;余弦退火则能逐渐降步长,让模型在后期更细腻地收敛。这里没有用自动寻找最优学习率的工具,直接凭过往项目的经验值起步,然后观察第一个epoch的loss变化,如果出现发散就调小初始学习率,如果收敛太慢就调大。
训练轮数epochs。设了200,但这不是硬性的。我建议每训练几十个epoch就保存一次权重,然后综合观察训练loss、验证loss和mAP曲线的走势,如果验证集指标连续20个epoch没有提升,就可以提前停止了。我在实际训练中大约是第140个epoch左右达到最佳验证指标,后续继续训练基本是浪费算力。
YOLOv5自带的早停机制(patience参数)就是这个用处。设patience=20,它会自动判断验证集指标是否长时间没有提升,如果是就停止训练。这个功能相当实用。
3.3 训练中必看的指标与曲线
训练过程不是跑起来完事儿,每个关键节点都要盯指标。我最常用的是这几个:
- Box Loss:定位损失的收敛情况。如果box loss降不下去,说明检测框回归不准确,常见原因包括anchor设置不合适、标注框质量差等。
- Class Loss:分类损失。如果分类损失居高不下,说明模型对缺陷类别区分度不足,大概率是类别标注质量或样本量分配出了问题。
- mAP@0.5和mAP@0.5:0.95:最终模型性能的判断标准。前者是宽松指标,后者更严格。钢轨缺陷检测场景中,我主要看mAP@0.5,因为缺陷检测允许的定位误差一般没有自动驾驶那么苛刻。
- Precision和Recall曲线:这两个指标对工业缺陷检测尤其重要。钢轨检测有个特点:漏检的代价远超误检。裂纹漏检可能导致重大安全事故,而多标一个锈斑最多让运维人员多看一眼。所以我在调模型时,会刻意把置信度阈值调低来提升召回率,宁可误检多一些。
训练日志的记录同样重要。我会把每一轮的验证指标写入CSV文件,方便训练结束后做趋势分析。YOLOv5本身就输出results.csv,拉出来用Excel或者一个小脚本绘图就够了。
3.4 模型评估与失效案例分析
训练完成后,除了看总体的mAP,我会做细致的“按类分解”评估。统计每一类的精确率和召回率,找出“拖后腿”的类别。在我的案例里,表现最差的是“剥离”这一类,召回率比其他类低了不少。
分析原因发现:一方面“剥离”样本数量偏少,不足“擦伤”的三分之一;另一方面“剥离”与“掉块”在视觉形态上非常接近,边界本来就模糊。优化思路是在训练时给“剥离”类适当增加样本权重,同时把标注规范中“剥离”和“掉块”的边界描述得更明确,重新标注了一批样本。第二版模型训练后,“剥离”的召回率明显改善。
评估阶段还有一个重要动作是可视化错误案例。把漏检和误检的图像抽取出来,逐张查看模型到底错在哪里:
- 如果漏检的多为暗光环境下的小裂纹,说明模型对低对比度目标的表达不足,可以增加对应的数据增强或者提升图像采集的前端照明质量;
- 如果误检的多为表面锈斑被当成掉块,说明类别间的语义距离太近,需要从标注定义或模型结构上想办法。
做完这一步,才敢说模型是真的“懂了”缺陷检测,而不只是指标好看。
4. 推理部署与工程化落地
4.1 权重导出与推理脚本实现
YOLOv5训练完成得到的是PyTorch格式的权重文件,后续使用有两种路径:一种是用torch.hub直接加载PyTorch模型进行推理;另一种是导出成其他格式做部署。我先把PyTorch这条路径跑通,因为它改起来最方便,适合在开发阶段验证效果。
推理脚本的核心逻辑很直接:
import torch model = torch.hub.load('./yolov5', 'custom', path='best.pt', source='local') model.conf = 0.25 model.iou = 0.45 results = model(img_path) results.show()这里有两个参数要特别注意:conf是置信度阈值,iou是NMS的IoU阈值。要在线检测场景中,不要把conf设得太高。巡检车拍下的钢轨表面,很多缺陷本身对比度就不高,模型对这些目标的置信度天然偏低。把conf从0.25调到0.1,召回率能提很多,代价是误检框变多。这个不可一概而论,需要根据实际项目对漏检和误检的容忍度来定。
4.2 部署形态考量:边缘设备与中心服务器
钢轨巡检的部署形态,一般分成两种思路:
边缘实时检测:在巡检设备上直接挂GPU模块或边缘计算盒子,模型跑到摄像头的视频流上,实时输出检测结果并叠加到画面中,同时保存关键帧。优点是响应快、不依赖网络;缺点是设备成本高、算法升级要上现场刷机。
中心离线分析:巡检车只负责采集原始图像,回传后由中心服务器统一跑模型分析。优点是部署维护方便、算力不受边缘限制;缺点是回传数据量很大,而且分析有时间延迟。
实际项目中通常两者结合:边缘端做粗筛,发现可疑帧就保存;中心端做细判,对可疑帧做二次高精度分析。这在工业场景中是很常见的“两级检测”架构,用较低的算力成本换取全链路精度。
4.3 实测性能与落地效果
我在最终模型上跑了一批现场的验证数据,这里列出几个有代表性的指标值(约值):
| 指标 | 数值 |
|---|---|
| 精度(Precision) | 87% 左右 |
| 召回率(Recall) | 82% 左右 |
| mAP@0.5 | 85% 左右 |
| 单帧推理耗时(GPU) | 12ms 左右 |
| 单帧推理耗时(CPU) | 120ms 左右 |
这个结果说明模型在GPU设备上跑实时检测完全没问题,在CPU设备上则更接近准实时。如果CPU设备要跑满帧率,还需要做模型剪枝或量化,这块后面可以单独展开讲。
高召回率对钢轨检测场景极其重要:宁可让运维人员看到几个错误的预警,也不能漏掉一处真实裂纹。
5. 常见问题与排查技巧实录
5.1 训练侧典型问题速查表
| 问题现象 | 可能原因 | 解决方案 |
|---|---|---|
| 训练loss不降 | 学习率过高或过低 | 调整初始学习率,或使用学习率预热策略 |
| mAP指标虚高 | 训练集和验证集之间存在数据泄露 | 按源头划分数据,严禁同一缺陷的不同帧跨集合 |
| 某类缺陷召回率特别低 | 样本数量不足或类别语义冲突 | 增加样本量、调整类别权重、细化标注规范 |
| 检测框偏移严重 | anchor机制与目标形状不匹配 | 使用聚类算法重新计算anchor,或用--noautoanchor关闭自动锚框 |
| 训练后期过拟合 | 模型容量过大而数据量不足 | 增加正则化、增强数据扩增、降低模型规模 |
5.2 推理侧典型问题速查表
| 问题现象 | 可能原因 | 解决方案 |
|---|---|---|
| 漏检多 | 置信度阈值太高 | 降低conf参数,优先保证召回率 |
| 一个缺陷被框两次 | NMS的IoU阈值太高 | 适当降低iou参数 |
| 光照变暗时检测失效 | 训练数据中暗光样本不足 | 增加亮度扰动扩增或补充暗光数据 |
| 模糊帧检测效果差 | 训练时缺乏运动模糊模拟 | 加入高斯模糊、运动模糊扩增 |
| 大量误检为锈斑 | 锈斑与其他缺陷特征相似 | 细化标注标准、考虑增加锈斑负样本或单独成类 |
5.3 两个最值得分享的实践经验
第一个经验是关于数据泄露的隐蔽形式。使用视频抽帧构建数据集时,即使做了抽帧,相邻帧之间依然可能存在相似内容。我处理这类问题时,不只依赖人工判断,还会计算图像间的感知哈希距离,设置一个阈值自动过滤掉相似度超标的图像。这样做的收益在验证集mAP的可信度上体现得很明显——对比过滤前后的验证集指标,夸张的时候能差8个点以上。
第二个经验是注重训练复现性。训练神经网络有随机性,同一个代码、同一个数据,两次训练结果也会有一些波动。如果只是跑一次训练得到一份权重就完事,无法判断这个结果到底是模型能力还是随机运气。我在项目中固定了随机种子,每次训练记录下核心超参数,同样的配置至少跑三次取统计结果。这样才能获得有信心的性能结论。
结语
坦白讲,钢轨表面缺陷检测不是一个“跑通YOLOv5就完事”的项目,它真正考验的是对数据质量的理解、对业务场景的判断和对工程细节的把控。现阶段我已经把基于YOLOv5的检测链路完整跑通,测试集指标可以支撑现场试用。后续要做的其实是两件事:一是持续扩充现场返回的真实缺陷数据,尤其是那些“长尾”的、罕见的缺陷形态;二是往更轻量、更快的方向走,尝试模型量化和剪枝,让它在更廉价的边缘设备上也能达到实时。这两个方向都有不少坑,等我把它们填平了,再来分享新一轮的心得。