
简介面向铁路运维人员与计算机视觉研究者这是一份基于YOLOv5的钢轨缺陷检测项目资源可自动识别人工巡检难以察觉的裂纹、磨损、剥离等钢轨表面缺陷并输出缺陷位置与类别项目同时涉及YOLOv7模型的对比实验。压缩包共2000个文件以txt标注数据为主辅以Python脚本如数据划分、VOC标签转换、模型配置文件yaml及README说明文档包体约28.77MB结构清晰便于离线训练与结果复现。已有166人学习浏览。资源包含split.py、voc_labelhrsc.py等实用工具覆盖从标注数据处理、格式转换到模型训练与推理的完整流程可在此基础上尝试引入注意力机制或多尺度检测进行改进用于算法对比和课题研究对提升钢轨缺陷检测实战能力很有帮助。1. 拿到“基于yolov5钢轨缺陷检测.zip”先别急着解压训练钢轨缺陷检测是铁路巡检里最刚需的落地场景之一而YOLOv5又是目标检测里被验证过无数次、部署生态最成熟的那个选择。这个zip包大概率是有人把yolov5源码、钢轨缺陷的标注数据集、训练好的权重文件甚至包括迁移学习用的预训练模型全部打成一个压缩包。它的价值在于你不用自己蹲在铁轨旁边拍几百张图也不用熬夜用LabelImg画上千个框解压以后按步骤走就能复现一个能用的钢轨缺陷检测模型。适合刚接触目标检测、想在钢轨场景快速出结果的同学也适合做铁路巡检方案预研的工程师。但我见过太多人拿到zip就双击解压、然后直接train.py结果跑出个mAP只有0.1的模型还找不着原因。所以先冷静下来花二十分钟把包里的家底盘点清楚这个动作能救你后面好几个晚上。2. 解压与数据盘点把zip里的内容物安全落地2.1 跨平台解压Windows、Linux和“伪加密”zip的处理如果你用的是Windows右键“解压到当前文件夹”是最省事的但碰到两种情况就得换工具一是文件名带中文、解压出来乱码二是zip故意用“伪加密”标记普通解压工具会弹窗要密码。我自己习惯在Windows上装7-Zip它能识别绝大多数乱码编码而且对损坏zip的容忍度比系统自带的更高。在Linux服务器上终端的处理方式更可控unzip 基于yolov5钢轨缺陷检测.zip -d rail_defect cd rail_defect参数说明-d指定解压目标目录避免把一堆文件直接撒在当前目录里保证解压成功后再进入目录如果解压失败后面命令不会执行。如果你发现解压出来的文件名是乱码一般是zip里的文件名编码是GBK而系统解码用UTF-8可以尝试unzip -O CP936 基于yolov5钢轨缺陷检测.zip -d rail_defect-O参数在常规unzip里不一定默认支持需要安装unzip的iconv版本常见做法是用7-Zip在Windows端先转一次或者直接用Python的zipfile库配合encoding参数处理。再来说说“伪加密”。有时候你运行unzip提示需要密码但你试了常见密码都不对甚至问作者要密码对方说“没设密码”。这很可能是zip的通用标志位里把“加密”比特位置为1但数据本身根本没有加密。你可以用一个简单脚本把标志位改回去import struct import zipfile def remove_fake_encryption(zip_path, output_path): with open(zip_path, rb) as f: data bytearray(f.read()) # 定位EOCD之后按顺序扫描本地文件头 offset 0 while offset len(data) - 4: if data[offset:offset4] bPK\x03\x04: # 通用位标志在本地文件头偏移6处占2字节 flag struct.unpack(H, data[offset6:offset8])[0] if flag 0x1: data[offset6] flag 0xFE # 清除第0位 offset 1 with open(output_path, wb) as f: f.write(data) remove_fake_encryption(钢轨缺陷.zip, 钢轨缺陷_fixed.zip)代码的逻辑很简单扫描所有PK\x03\x04本地文件头读取通用位标志把第0位加密位清零。这个方法只对“伪加密”有效真正加密的zip文件这样改会导致数据损坏所以操作前最好复制一份备份。处理完这个常见的“zip密码移除”需求后再解压就畅通无阻了。2.2 内容物盘点看目录、看文件、看类别分布解压完成后别急着进train.py先用一个命令把目录结构摸清楚find . -maxdepth 3 -type d | sort或者用你能记忆的方式ls -R .常见的一个“基于yolov5钢轨缺陷检测”压缩包内部结构往往是这样的rail_defect/ ├── data/ │ ├── images/ │ │ ├── train/ │ │ └── val/ │ ├── labels/ │ │ ├── train/ │ │ └── val/ │ └── rail_defect.yaml ├── weights/ │ └── best.pt ├── yolov5/ │ ├── train.py │ ├── detect.py │ └── requirements.txt └── README.md需要重点关注三件事图片数量、标签存在性、类别分布。很多zip包是从开源数据集里二次整理来的可能缺图、标签对不上图片或者类别只有两类但yaml里写了五类。这时候先用一个Python脚本统计一下标签的实测类别数和每个类别的框数from pathlib import Path import collections label_dir Path(data/labels/train) class_counter collections.Counter() file_count 0 empty_files 0 for label_file in label_dir.glob(*.txt): file_count 1 lines label_file.read_text().strip().splitlines() if not lines: empty_files 1 continue for line in lines: parts line.split() if len(parts) 5: class_counter[int(parts[0])] 1 print(f标签文件数: {file_count}) print(f空标签文件数: {empty_files}) print(f类别id及框数: {sorted(class_counter.items())})这段脚本会输出一共多少个标签文件、里面有多少是空文件以及每个类别id出现多少次。如果你发现标签文件里最大的类别id是2而yaml里写了nc: 5说明数据集本身类别不齐训练时要么补样本要么先改成实际类别数。钢轨缺陷常见的类别有轨面裂纹、擦伤、剥落、光带异常、接头掉块等不同来源的数据集命名差异很大但核心原则是标签类别id必须从0开始连续不能出现0和2但没有1的情况。2.3 数据格式校准VOC、COCO、YOLO三种格式的换算很多zip包里的原始标注是VOC XML或者COCO JSON而YOLOv5只认txt格式。如果你打开labels目录发现里面是空的但images旁边有个annotations文件夹那大概率是还没转换。三种格式的区别用一个表格就能看明白格式文件形态坐标表示适合场景VOC每张图一个XML左上角x,y和右下角x,y以像素为单位手工标注工具默认导出COCO整个数据集一个JSON左上角x,y和宽w、高h以像素为单位科研数据集统一格式YOLO每张图一个txt归一化后的中心x,y和宽w、高h取值0~1YOLOv5原生训练格式VOC转YOLO的最核心换算公式是x_center (xmin xmax) / 2 / img_width y_center (ymin ymax) / 2 / img_height w (xmax - xmin) / img_width h (ymax - ymin) / img_height转换脚本通常要读取图片尺寸从XML里拿标注然后写txt文件。我一般这样处理import xml.etree.ElementTree as ET from pathlib import Path def voc_to_yolo(xml_path, out_path, class_names): tree ET.parse(xml_path) root tree.getroot() img_w int(root.find(size/width).text) img_h int(root.find(size/height).text) lines [] for obj in root.findall(object): cls obj.find(name).text if cls not in class_names: continue cls_id class_names.index(cls) bnd_box obj.find(bndbox) xmin float(bnd_box.find(xmin).text) ymin float(bnd_box.find(ymin).text) xmax float(bnd_box.find(xmax).text) ymax float(bnd_box.find(ymax).text) x_center (xmin xmax) / 2.0 / img_w y_center (ymin ymax) / 2.0 / img_h w (xmax - xmin) / img_w h (ymax - ymin) / img_h lines.append(f{cls_id} {x_center:.6f} {y_center:.6f} {w:.6f} {h:.6f}) out_path.write_text(\n.join(lines))参数说明class_names是类别名列表它的索引就是YOLO格式里的类别id这个列表必须和后面yaml配置里的names保持一致否则训练时类别错位是肉眼看不出来的。转换完记得抽几组图用可视化脚本画框检查一下别到了训练结束才发现标签全错位了。3. 环境配置与数据集准备把yolov5跑起来的前置条件3.1 conda环境与依赖避开torch和CUDA版本雷区“基于yolov5钢轨缺陷检测.zip”里的yolov5源码通常是某个历史版本的clone可能基于PyTorch 1.8到2.0不等。强烈建议用conda独立环境别把原生Python环境当试验田conda create -n rail_yolo python3.8 -y conda activate rail_yolo pip install torch1.13.1cu117 torchvision0.14.1cu117 --extra-index-url https://download.pytorch.org/whl/cu117 cd yolov5 pip install -r requirements.txt参数说明python3.8是为了兼容性yolov5在3.8上是验证最充分的版本torch1.13.1cu117对应CUDA 11.7这是目前兼容性比较好的组合既能跑RTX 30/40系显卡也不影响CPU推理。如果你的机器是纯CPU环境直接pip install torch --index-url https://download.pytorch.org/whl/cpu即可别硬装CUDA版白费空间。requirements.txt里的核心依赖不外乎opencv-python、numpy、matplotlib、pandas、seaborn、pyyaml、tqdm等。跑完安装后验证环境是否就位python -c import torch; print(torch.__version__, torch.cuda.is_available())输出True说明CUDA可用。如果你发现输出的是False先去nvcc -V看看CUDA驱动版本再用nvidia-smi看驱动支持的CUDA版本两者必须覆盖你要装的PyTorch版本。这里最容易翻车的是驱动老了但PyTorch要求新CUDA训练到一半kernel崩溃。3.2 数据目录与标签检查缺标签、空标签、类别号越界yolov5训练时对数据目录的默认要求是images和labels两个文件夹平行且内部结构完全一致。比如data/ ├── images/ │ ├── train/ │ │ ├── img_001.jpg │ │ └── img_002.jpg │ └── val/ │ └── img_val_001.jpg └── labels/ ├── train/ │ ├── img_001.txt │ └── img_002.txt └── val/ └── img_val_001.txt图片和txt同名同目录这是yolov5的硬性约定。很多zip包从别的框架转过来目录名可能叫JPEGImages和Annotations那就需要建符号链接或者复制目录。我一般用一条命令处理ln -s /path/to/JPEGImages data/images/train ln -s /path/to/labels_yolo data/labels/train路径里的“解压内容物”的绝对路径要补全这是避免相对路径错乱最简单的办法。接下来跑一个更完整的检查脚本把“图片存在但标签缺失”和“标签越界”都揪出来from pathlib import Path import random img_dir Path(data/images/train) label_dir Path(data/labels/train) imgs list(img_dir.glob(*.jpg)) list(img_dir.glob(*.png)) missing_label [p.stem for p in imgs if not (label_dir / (p.stem .txt)).exists()] print(f缺标签的图片: {len(missing_label)}) for name in missing_label[:5]: print(name) # 检查类别id是否超出配置 max_allowed 4 # 假设 nc5, 最大id为4 bad_files [] for label_file in label_dir.glob(*.txt): for line in label_file.read_text().splitlines(): cls_id int(line.split()[0]) if cls_id max_allowed: bad_files.append((label_file.name, cls_id)) break print(f越界标签文件: {len(bad_files)}) if bad_files: print(bad_files[:3])运行后发现缺标签的图片要么补齐对应txt要么从训练列表里删掉。越界的类别id多半是标签是从1开始编的而yolov5要求从0开始这种情况写个脚本把所有id减1即可。空标签文件可以留着yolov5会把它们当作背景样本参与训练不影响大局。3.3 yaml配置nc、names、train/val路径yolov5的数据配置是一个yaml文件zip包里通常给了一个示例你需要按实际情况改。我常用的钢轨缺陷配置文件长这样# rail_defect.yaml train: data/images/train val: data/images/val nc: 4 names: [crack, corrosion, spalling, abnormal_light_band]关键字段说明train和val必须指向存放图片的目录yolov5会自动去同级的labels文件夹找标签不要写成labels路径那是新手最常犯的错误。nc是缺陷类别总数不是最大id加1而是实际类别个数如果类别id从0到3nc: 4。names的顺序必须和转换脚本里的class_names一致否则类别名和box对不上。如果你把yaml放到某个子目录里train和val建议写绝对路径或者写成相对于yaml文件所在位置的相对路径。yolov5支持相对路径但它是相对于yolov5工程运行目录来解析的不是相对于yaml文件本身这一点很容易踩坑。解压的zip如果移动过位置绝对路径是最保命的。4. 训练与调参让模型在钢轨缺陷上收敛4.1 最小训练命令与训练流程环境配好、数据检查通过后训练就一个命令的事python train.py --data ../data/rail_defect.yaml --weights yolov5s.pt --img 640 --batch 16 --epochs 100 --workers 4参数说明--weights yolov5s.pt表示从COCO预训练权重开始迁移学习这是小数据集最靠谱的冷启动方式比从随机权重开始训练能快好几倍最终精度也更高。--img 640是输入分辨率钢轨缺陷多数是小目标比如细微裂纹这时候640可能不够可以试960或1280但显存占用会按平方上涨。--batch 16在8G显存下跑yolov5s刚好再大就OOM了。--workers 4是数据加载线程数Windows上如果报错就改成0Linux上4~8都行。训练过程中终端每隔一段时间会输出一张表包含epoch、train_loss、val_loss、P、R、mAP.5、mAP.5:.95等指标。你主要盯mAP.5钢轨缺陷这种单类目标检测场景mAP.5上0.85才算及格低于0.7说明数据或参数还有问题。4.2 yolov5超参数锚框、学习率、数据增强怎么调yolov5的超参数大多数不用动但有四个参数值得你亲手调一调它们对你的钢轨缺陷ziny数据集影响最大。# hyp.scratch.yaml 关键片段 lr0: 0.01 lrf: 0.2 mosaic: 1.0 hsv_h: 0.015 hsv_s: 0.7 hsv_v: 0.4 fl_gamma: 0.0参数说明lr0是初始学习率0.01适合从预训练权重开始但如果你的数据集只有几百张图我建议降到0.005否则前面十几个epoch loss会震荡得很厉害。lrf是最终学习率相对于初始学习率的系数0.2表示最后收敛到0.01*0.20.002训练轮次足够多时能更好收敛。mosaic是马赛克增强把4张图拼成一张训练对提升小目标鲁棒性很有帮助但如果你发现训练损失不降尝试把mosaic改成0.5有些钢轨缺陷样本彼此差异太大强拼在一起反而学不到特征。hsv_*是颜色抖动的强度钢轨表面通常是暗色金属过度颜色增强会把锈迹和裂纹的色差抹掉建议把hsv_s从0.7降到0.3hsv_v从0.4降到0.2。另一个容易被忽略的是锚框。yolov5默认从COCO的锚框初始化如果你的目标尺寸和COCO差异大比如钢轨缺陷的框普遍很窄很长最好让yolov5重新算锚框python train.py --data ../data/rail_defect.yaml --weights yolov5s.pt --img 640 --batch 16 --epochs 100 --noautoanchor不加--noautoanchor时yolov5会在训练前自动用k-means重新计算锚框是正常的。但如果你发现训练日志里AutoAnchor一直在报警说anchor不匹配那就手动调一遍--anchor参数或者修改模型yaml里的anchors配置。我一般直接让autoanchor跑它多数时候比手工改更优。4.3 训练过程中的信号判断训练不是等100个epoch结束就完事中间要时刻观察信号。我用tensorboard看曲线yolov5训练时已经自动集成了tensorboard --logdir runs/train浏览器打开后看两组曲线train loss和val loss。健康的曲线是两者一起下降最后趋于平缓。常见的问题有两个一是train loss一直降val loss在某个epoch后掉头向上这是过拟合。原因是模型容量太大但样本太少。解决办法增加数据增强、加--dropout、或者提前用--patience 20早停。yolov5自带的--patience参数可以这样用python train.py --data ../data/rail_defect.yaml --weights yolov5s.pt --img 640 --batch 16 --epochs 300 --patience 20patience 20表示连续20个epoch没有改善就自动停止。第二个问题是loss曲线像锯齿一样剧烈震荡波动幅度超过10%这通常是学习率太高或者batch太小。先降lr0到0.003再把batch加大如果显存不够用--device 0 --batch 8配合梯度累积也行但yolov5没有内置梯度累积需要改代码所以更推荐直接换更小的模型比如yolov5n。训练结束后runs/train/exp/weights/里会生成best.pt和last.pt。best.pt是验证集上mAP最高的一版部署和测试都用它别用last.pt除非你想继续训练。5. 钢轨缺陷检测的避坑现象、原因、解决5.1 解压后训练图片全黑、标签错位现象训练时第一epoch的batch图预览里图片全黑或者是一堆碎花标签框画在完全无关的位置。原因zip包里的图片可能是灰度图或者16位深度的图像yolov5默认按3通道8位读取灰度图会被OpenCV读成单通道直接被某些转码逻辑丢弃或涂黑。标签错位多半是转换脚本用错了坐标基准比如用了cv2读图宽高但实际图片是旋转过的或者图片本身包含EXIF旋转信息YOLOv5不理会EXIF导致坐标全部偏移。解决训练前用批量脚本把图统一转换为RGB三通道JPEG同时把EXIF旋转信息先烘焙进像素再保存。处理完后重新跑标签检查脚本随机抽20张图把val集可视化用yolov5自带的detect.py对几张验证图跑一遍已训练完毕的best.pt用肉眼对比框和实际缺陷是否吻合。这个过程虽然土但比任何指标都可靠。5.2 OOM显存不足但调小batch后mAP骤降现象训练到一半报CUDA out of memory只能把batch从16降到4但这样跑了100轮mAP只有0.5远不如预期。原因batch太小导致batch normalization的统计量不稳定梯度噪声大模型难以收敛。钢轨缺陷数据集的样本本身就有背景复杂、目标大小差异大的特点batch太低很大概率学崩溃。解决先不要无脑降batch而是降输入分辨率。把--img 640改成--img 544或--img 512显存占用能降30%~40%batch保持16。如果还放不下就换更小的模型yolov5s换成yolov5n参数量只有s的1/3左右对钢轨缺陷这种中等难度任务够用。改模型只需改--weights yolov5n.pt其他配置不变。另外检查一下是否在训练其他无关进程占了显存用nvidia-smi看一眼。5.3 样本不均衡裂纹样本多、剥落样本少模型只检出大缺陷现象训练完测试的时候裂纹、擦伤这些大而明显的缺陷都能检出但剥落、细小裂纹的precision和recall都很低甚至完全检不出来。原因zip里的数据集来自不同巡检线路各类别出现频率极度不均比如裂纹占70%剥落只占5%。导致模型把小目标误以为背景或者对少数类别的特征没有建立有效映射。解决先看类别统计确认不均衡比例超过10倍就处理。简单粗暴的办法是复制少数类样本但我更建议用数据增强对少数类别做“过采样”加“局部裁剪”让它们以更多样化的形式出现。调整yolov5超参数里的fl_gamma这是focal loss的gamma系数默认0.0表示用BCE loss把它改成1.5可以让模型更关注难分类的少数类fl_gamma: 1.5别忘了同时降低多数类的损失权重。用两次训练对比一次fl_gamma: 0.0一次fl_gamma: 1.5看验证集上少数类别的recall有没有上升。数据层面的短板光调参数补不回来最终还是要补样本。5.4 伪加密与权限导致数据读不出来现象解压时报密码错误或者解压后某几个子目录显示空白训练时提示No labels found。原因前面提到的伪加密是最常见的部分压缩工具会把“加密标志”写错。另一个原因是解压后文件权限不对比如从zip里解出来的目录是rwx------但你的训练进程是用另一个用户跑的读不了labels目录。解决先用本文2.1节的Python脚本去掉伪加密标志。文件权限问题用一条命令解决即可chmod -R 755 rail_defect然后在项目根目录跑一次find . -name *.txt | head -5确认标签能正常列出。如果发现解压后个别文件大小为0说明zip包本身损坏让发包方重新压缩时用zip -r而不是zip -0不压缩模式。5.5 部署时模型尺寸与实时性矛盾现象训练好的best.pt大约14MBpyTorch推理一帧要80ms但在巡检小车上要求每帧50ms以内换成树莓派更卡每帧要300ms。原因yolov5s对于钢轨缺陷这种只需检测3~4类目标的场景参数冗余太多。解决先做模型剪枝用yolov5官方自带的prune脚本按通道剪掉30%参数再fine-tune几十轮。如果不想剪枝直接换用yolov5n从头训练或者导出为TensorRT等优化格式。这个矛盾在后续部署章节细说。重点是别拖到部署阶段才开始考虑模型规模训练时就要想好推理设备是什么。6. 把训练好的模型搬到树莓派5上导出与部署验证钢轨缺陷检测的最终归宿往往是边端设备树莓派5是性价比很高的验证平台。yolov5提供的导出工具链可以把best.pt转成TorchScript省去PyTorch运行时python export.py --weights runs/train/exp/weights/best.pt --include torchscript --img 640导出的best.torchscript可以直接被Python调用不需要yolov5工程环境只装torch和torchvision就行。树莓派5上我建议装CPU版PyTorchpip install torch --index-url https://download.pytorch.org/whl/cpu推理脚本可以这样写import torch from PIL import Image model torch.jit.load(best.torchscript) model.eval() img Image.open(test_rail.jpg).convert(RGB).resize((640, 640)) results model([img]) # results.xyxy[0] 是[x1,y1,x2,y2,conf,cls] for x1, y1, x2, y2, conf, cls in results.xyxy[0]: if conf 0.4: print(f缺陷类别{int(cls)} 置信度{conf:.2f} 坐标{(int(x1), int(y1), int(x2), int(y2))})脚本里把results.xyxy的tensor直接解包树莓派5的CPU推理yolov5n大约能跑到150ms一帧基本满足低速巡检需求。如果还嫌慢可以把输入分辨率降到416或者用--include tflite导出更轻量的格式。我自己一般会在树莓派上放几十张不同光照、不同磨损程度的历史图跑一遍导出模型统计每张图的召回率和误检数画成表格和服务器上PyTorch推理结果对比。两者差异超过5%说明导出过程丢了算子需要重新检查torch版本对齐。部署验证完我会顺手把模型文件和推理脚本按照日期归档下次换现场数据时直接复用这套流程。这条路一路趟下来最大的感触是钢轨缺陷检测的瓶颈从来不在模型结构而在数据真实性、标签质量和部署时的边界条件。把这三样盯紧剩下的都是水到渠成。希望帮到你。本文还有配套的精品资源点击获取