简介这份资源面向铁路巡检、工业视觉方向的开发者与深度学习学习者提供一套基于YOLOv7的铁轨缺陷检测完整项目资料可用于快速识别铁轨裂纹、剥落等安全隐患适合具备一定目标检测基础、希望落地工业检测场景的中高级读者参考。压缩包共2000个文件以1994个txt标注与数据说明文件为主另含3个Python脚本、2个Markdown说明文档和1个yaml配置文件整体约28.92MB覆盖数据划分、标签转换与训练配置等环节。目前已有377人学习下载。读者可从中获取数据预处理与标注组织方式、VOC格式转换脚本、训练验证集划分逻辑以及模型配置模板便于对照复现YOLOv7在铁轨缺陷数据上的训练流程并理解多尺度检测、动态锚框等策略在细小缺陷识别中的实际作用为后续模型调优与工程部署提供可借鉴的目录结构与排错思路。1. 基于YOLOv7铁轨缺陷检测从标注文件到可训练数据集的落地拆解铁轨表面缺陷检测这活儿真正干过的人都知道模型结构反而是最省心的部分。YOLOv7 在 COCO 上什么表现换到钢轨裂纹、剥离掉块、压溃这些场景里大概率要打折扣——不是网络不行是数据管线没搭对。我见过太多人拿到一份标注文件兴冲冲跑train.py结果 loss 直接 NaN或者训完 mAP 只有零点零几回头一查标签路径错了、类别 ID 从 1 开始、图片和 txt 对不上号。这份资源给的东西很实在split.py、voc_labelhrsc.py、split_train_val.py三个脚本加上inclusion_130.txt、inclusion_287.txt、inclusion_125.txt、patches_187.txt、inclusion_88.txt这几个标注/划分文件本质上是一套把原始铁轨图像整理成 YOLOv7 可吃格式的预处理工具链。适合谁手里有铁轨巡检图、想快速跑通 YOLOv7 训练闭环的算法工程师和铁路方向的研究生。下面我按自己拆包的顺序把每个文件干什么、怎么改、哪里容易翻车讲清楚。2. 数据管线拆解三个脚本各自管什么2.1 split.py 与 split_train_val.py 的分工先看split.py。这个脚本通常负责最粗粒度的一步把一个大目录里的图片和对应标注按比例切成 train / val 两个子集。它的输入一般是一个包含images/和labels/的根目录输出是images/train、images/val、labels/train、labels/val这样的结构。我一般会先确认它用的是随机种子还是固定种子——如果是随机切分跑两次结果不一样后面复现实验就对不上。常见做法是在脚本开头加random.seed(42)保证每次切分一致。split_train_val.py则更细一层。它往往处理的是已经切好的 train 集内部再按某个比例比如 8:2分出 train 和 val或者反过来把 val 再拆成 val 和 test。这两个脚本名字像但作用域不同split.py管全局split_train_val.py管局部。如果你拿到的是已经分好 train/val 的目录那split.py可以跳过直接用split_train_val.py做二次划分。操作上我习惯先跑一遍 dry-run把路径打印出来看# split.py 核心逻辑示意 import os, random, shutil random.seed(42) root railway_defect img_dir os.path.join(root, images) lbl_dir os.path.join(root, labels) train_ratio 0.8 imgs [f for f in os.listdir(img_dir) if f.endswith((.jpg, .png))] random.shuffle(imgs) split_idx int(len(imgs) * train_ratio) for phase, subset in [(train, imgs[:split_idx]), (val, imgs[split_idx:])]: for img in subset: stem os.path.splitext(img)[0] # 图片和标签必须同名只是扩展名不同 shutil.copy(os.path.join(img_dir, img), os.path.join(root, images, phase, img)) shutil.copy(os.path.join(lbl_dir, stem .txt), os.path.join(root, labels, phase, stem .txt))这段代码的关键参数就三个root指向你的数据集根目录train_ratio控制训练集比例random.seed保证可复现。逻辑说明先列出所有图片打乱后按比例切然后分别复制图片和同名 txt 到对应子目录。注意shutil.copy不会自动建目录跑之前得手动mkdir -p images/train labels/train这些路径否则直接报FileNotFoundError。我一般会在脚本里加os.makedirs(..., exist_okTrue)省得每次手动建。2.2 voc_labelhrsc.py 的转换逻辑与类别映射voc_labelhrsc.py这个名字透露了两个信息一是它处理 VOC 格式的 XML 标注二是它跟 HRSC 数据集有关。HRSC 是遥感舰船检测的经典数据集但铁轨缺陷检测里借用它的转换脚本很常见——因为很多铁轨标注工具导出的就是 VOC XML。这个脚本干的事就一件把Annotations/*.xml里的bndbox坐标读出来归一化成 YOLO 需要的class_id x_center y_center width height格式写到labels/*.txt。核心转换公式# voc_labelhrsc.py 坐标转换核心 import xml.etree.ElementTree as ET def convert_bbox(size, box): dw 1.0 / size[0] dh 1.0 / size[1] x (box[0] box[1]) / 2.0 y (box[2] box[3]) / 2.0 w box[1] - box[0] h box[3] - box[2] return (x * dw, y * dh, w * dw, h * dh) # 类别映射铁轨缺陷常见类别 classes [crack, spalling, squat, joint] tree ET.parse(Annotations/001.xml) root tree.getroot() size (int(root.find(size/width).text), int(root.find(size/height).text)) for obj in root.iter(object): cls_name obj.find(name).text cls_id classes.index(cls_name) # 从 0 开始 bbox obj.find(bndbox) b (float(bbox.find(xmin).text), float(bbox.find(xmax).text), float(bbox.find(ymin).text), float(bbox.find(ymax).text)) bb convert_bbox(size, b) # 写入 txt保留 6 位小数 with open(labels/001.txt, a) as f: f.write(f{cls_id} { .join([f{v:.6f} for v in bb])}\n)参数说明classes列表的顺序决定了class_id必须和后续data.yaml里的names完全一致错一个位置整个训练就废了。convert_bbox里先算中心点再归一化注意 VOC 的xmin/ymin是左上角xmax/ymax是右下角别搞反。常见坑是 XML 里有些bndbox坐标超出图片边界转换后x_center可能大于 1 或小于 0YOLOv7 训练时虽然不会直接报错但会拉低精度。我一般会在写入前加一句x max(0, min(1, x))做裁剪。2.3 inclusion 与 patches 文件的作用inclusion_130.txt、inclusion_287.txt、inclusion_125.txt、inclusion_88.txt这几个文件从命名看是不同批次或不同缺陷类型的 inclusion 列表——可能是图片文件名清单也可能是标注框的统计信息。patches_187.txt则大概率是 patch 级别的划分记录比如把大图切成小 patch 后每个 patch 的归属。这些文件在训练脚本里通常被当作train.txt或val.txt的替代品直接喂给 YOLOv7 的--data参数。我一般会先head -5看一眼格式head -5 inclusion_130.txt # 输出可能是 # data/images/130_001.jpg # data/images/130_002.jpg # ...如果是纯路径列表那直接改data.yaml里的train:指向它就行。如果是带标签的就得先解析再转成 YOLO 格式。这里有个血泪经验这些 txt 里的路径可能是相对路径而 YOLOv7 默认从--data指定的 yaml 所在目录找路径不对就会报No such file or directory。解决办法是用os.path.abspath转绝对路径或者在 yaml 里写相对于项目根目录的路径。3. 从原始标注到 YOLOv7 训练完整复现步骤3.1 环境准备与目录结构对齐YOLOv7 官方仓库对目录结构有约定我一般会整理成下面这样railway_yolov7/ ├── data/ │ ├── images/ │ │ ├── train/ │ │ └── val/ │ ├── labels/ │ │ ├── train/ │ │ └── val/ │ └── railway.yaml ├── scripts/ │ ├── split.py │ ├── split_train_val.py │ └── voc_labelhrsc.py └── yolov7/ ├── train.py └── ...railway.yaml内容train: ../data/images/train val: ../data/images/val nc: 4 names: [crack, spalling, squat, joint]注意nc必须等于names长度train和val写图片目录YOLOv7 会自动去找同级的labels目录。如果 labels 不在同级得用--img-path或者改源码里的img2label_paths函数。我一般保持同级省事。3.2 跑通转换脚本并检查标签按顺序执行# 1. 先跑 VOC 转 YOLO python scripts/voc_labelhrsc.py # 2. 再切分 train/val python scripts/split.py # 3. 如果需要二次划分 python scripts/split_train_val.py跑完后必须做一次标签校验。我常用的检查脚本import os label_dir data/labels/train img_dir data/images/train for lbl in os.listdir(label_dir): path os.path.join(label_dir, lbl) with open(path) as f: lines f.readlines() for i, line in enumerate(lines): parts line.strip().split() if len(parts) ! 5: print(f{lbl} 第 {i} 行格式错误: {line}) continue cls_id int(parts[0]) coords [float(x) for x in parts[1:]] if any(c 0 or c 1 for c in coords): print(f{lbl} 第 {i} 行坐标越界: {coords}) # 检查图片是否存在 stem os.path.splitext(lbl)[0] if not any(os.path.exists(os.path.join(img_dir, stem ext)) for ext in [.jpg, .png, .jpeg]): print(f{lbl} 没有对应图片)这个检查能揪出三类问题格式不对、坐标越界、图片缺失。跑一遍没输出说明标签基本干净。3.3 启动训练与关键参数YOLOv7 训练命令python train.py \ --weights yolov7.pt \ --cfg cfg/training/yolov7.yaml \ --data data/railway.yaml \ --hyp data/hyp.scratch.p5.yaml \ --epochs 100 \ --batch-size 16 \ --img-size 640 640 \ --device 0参数说明--weights用预训练权重铁轨缺陷数据量通常不大从头训容易过拟合--batch-size根据显存调16 是 8G 显存的保守值--img-size铁轨裂纹属于细长目标640 够用想提小目标可以上 1280但显存翻倍。--hyp用默认的hyp.scratch.p5.yaml就行里面mosaic和mixup对铁轨这种纹理单一的场景有帮助但mixup别开太大否则缺陷特征被混没了。训练过程中重点看mAP0.5和box_loss。如果box_loss一直不降大概率是标签有问题如果mAP卡在 0.1 以下检查nc和names是否对齐。4. 避坑与排查铁轨缺陷检测里最容易翻车的五件事4.1 类别 ID 从 1 开始导致全盘错位现象训练 loss 正常下降但推理时所有框都标成同一个类别或者 mAP 极低。 原因VOC XML 里类别名映射时用了classes.index(name) 1而 YOLOv7 要求从 0 开始。 解决检查voc_labelhrsc.py里cls_id的计算确保classes.index(name)没有加 1同时data.yaml的names顺序和classes完全一致。4.2 图片与标签文件名不匹配现象训练报AssertionError: train: No labels found或者训练集数量远小于预期。 原因图片是001.jpg标签是001.txt但中间多了个空格或大小写不一致001.JPGvs001.txt。 解决用os.path.splitext统一去扩展名批量重命名时加strip()去空格。我一般会跑一遍diff (ls images/train | sed s/\..*//) (ls labels/train | sed s/\..*//)看差异。4.3 坐标越界未裁剪现象训练不报错但验证时某些框跑到图像外面mAP 虚低。 原因VOC 标注时框超出了图片边界转换后x_center或y_center不在 [0,1] 区间。 解决在convert_bbox返回前加x min(max(x, 0), 1)对四个值都做裁剪。注意w和h也要限制在 (0,1]。4.4 路径写相对路径导致找不到文件现象FileNotFoundError: data/images/train/xxx.jpg。 原因inclusion_*.txt里写的是相对路径而训练时工作目录变了。 解决在生成 txt 时用os.path.abspath转绝对路径或者在data.yaml里写相对于 yaml 文件的路径。我习惯在train.py开头加os.chdir(os.path.dirname(os.path.abspath(__file__)))固定工作目录。4.5 小目标漏检严重现象大缺陷检测正常细裂纹几乎全漏。 原因YOLOv7 默认 anchor 基于 COCO对细长目标不友好输入分辨率 640 下裂纹只有几个像素。 解决用--img-size 1280 1280提分辨率同时用 k-means 重新聚类 anchor。铁轨裂纹的宽高比通常大于 5:1聚类时把anchor_t调到 5 以上。另外hyp里的box损失权重可以适当调高。5. 进阶技巧用 inclusion 文件做增量训练与模型验证inclusion_130.txt这类文件除了当训练列表还有一个用法是增量训练。铁轨巡检数据是持续积累的今天标了 130 张下周又标了 287 张没必要每次从头训。我一般会保留一个base_weights.pt然后用新 inclusion 文件做微调python train.py \ --weights runs/train/exp/weights/best.pt \ --data data/railway_incremental.yaml \ --epochs 30 \ --batch-size 8 \ --img-size 640 640 \ --device 0 \ --freeze 10--freeze 10冻结前 10 层只训检测头适合新数据量少的情况。railway_incremental.yaml里的train指向新的 inclusion 文件转换后的路径。这样每次新增数据只需跑几十个 epoch省时间。验证阶段我习惯用test.py生成预测结果然后跟patches_187.txt里的 patch 划分做交叉比对python test.py \ --weights runs/train/exp/weights/best.pt \ --data data/railway.yaml \ --img-size 640 \ --conf-thres 0.25 \ --iou-thres 0.45 \ --save-txt \ --save-conf--conf-thres铁轨场景建议 0.25 起步太低误检多太高漏检多。--save-txt会把预测框存成 YOLO 格式方便跟patches_187.txt里的真值做逐 patch 对比。如果某个 patch 的预测框数量跟真值差太多就单独把那个 patch 拎出来看大概率是光照或油污干扰。从那以后我每次拿到新的 inclusion 文件都强制先跑一遍标签校验脚本再head看路径格式最后才启动训练。这个习惯帮我省了至少三次通宵重训。希望帮到你。本文还有配套的精品资源点击获取
