铁路轨道病害检测:COCO转YOLO数据集与训练实战指南
简介一套用于铁路轨道病害检测研究的COCO格式图像数据集面向计算机视觉方向的研究者、算法工程师及铁路安全监测从业者为训练和评估目标检测、实例分割等深度学习模型提供标注数据。资源共115个文件含114张JPEG病害图像与1个COCO标注JSON压缩包约189.77MB。图像以近距离拍摄为主分辨率较高可清晰呈现钢轨、道床等关键部位的典型病害细节JSON标注覆盖目标类别、位置与轮廓信息可直接适配Faster R-CNN、YOLO、Mask R-CNN等主流算法。数据集包含多种病害类型、不同拍摄角度与场景差异文件名中保留唯一ID和哈希值便于区分拍摄条件、筛选训练样本作者同时承诺可根据需求补充更多相关数据训练集可进一步扩展。目前已有2472人学习下载适合作为模型调参、算法对比及铁路安全检测研究的基础资源可明显降低数据采集与人工标注成本加快实验迭代。1. 铁路轨道病害检测缺的不是模型是带COCO标注的数据做轨道巡检方向的算法工程师普遍有个感受模型结构、训练框架都是现成的照着开源仓库改一改就能跑真正卡住进度的反而是标注好的病害数据。这份铁路轨道病害检测数据集采用COCO格式标注意味着你能直接拿pycocotools读取、可视化也能按本文的思路转成YOLO训练用的txt文件。它覆盖扣件、轨面、道床等常见病害类型适合正在做基础设施巡检落地的算法工程师、数据标注审核人员以及需要真实场景数据做课题的学生。先说明白一点这不是一个拿回来就能直接上线的模型而是一份能让你把训练流程完整跑起来、并且能校验标注质量的资源。2. 数据集结构解析COCO JSON三段式与病害目标分布统计2.1 COCO JSON在轨道病害场景下长什么样COCO格式的核心是一个JSON文件所有标注信息都装在里面。拿到这份数据集先别急着转格式建议花十分钟把JSON结构看透因为后面的格式转换、训练配置、坑位处理全建立在这个结构上。典型的三段式结构是images、annotations、categories三个数组。images数组里维护了每张图的元信息包括id、file_name、width、height。这里的id是全局唯一索引不是文件名它是连接图像和标注的桥梁。annotations数组是重点每一条对应一个病害框包含image_id指向某张图、category_id指向某个病害类别、bbox四个数x、y、width、height单位是像素、area框面积以及iscrowd标志。categories数组定义了类别ID和类别名称的映射关系轨道病害数据集的类别一般在3到8类之间视采集范围而定。轨道病害场景和通用物体检测有一个明显区别病害目标的绝对尺寸普遍偏小尤其在整轨高清大图上一个扣件缺陷可能只有几十乘几十像素。这意味着JSON里记录的box数量多、面积小、分布密处理时不能套用通用检测那种“一张图里两三个大框”的思维。提示先确认JSON里categories的id是否从0开始多数COCO数据集从1开始这一步直接决定后面转YOLO格式时要不要做offset处理很多人在这一步翻了车。2.2 用pycocotools统计类别分布与目标尺寸在动转换脚本之前先把数据集的“家底”摸清楚每个类别有多少框、小目标占比多高、图像分辨率分布在什么区间。常见做法是用pycocotools做一轮统计这比手写JSON解析省事得多直接拿到官方封装好的接口。from pycocotools.coco import COCO ann_path annotations/train.json coco COCO(ann_path) cat_ids coco.getCatIds() cats coco.loadCats(cat_ids) print(类别列表:, [c[name] for c in cats]) box_num {} areas_all [] for cid in cat_ids: ann_ids coco.getAnnIds(catIdscid) anns coco.loadAnns(ann_ids) box_num[cid] len(anns) for a in anns: areas_all.append(a[area]) # MS COCO 通用目标尺度划分小目标 32x32中等 32x32 ~ 96x96 small len([a for a in areas_all if a 1024]) medium len([a for a in areas_all if 1024 a 9216]) large len([a for a in areas_all if a 9216]) print(各类别框数量:, box_num) print(小/中/大目标数量:, small, medium, large)这段代码先加载JSON建立索引再按类别遍历所有标注框统计每个类别的框数同时把所有框面积收集到一个列表里做尺度分层。输出结果能直接指导后面的训练参数如果small占比超过60%输入分辨率就得往1280靠不能再死守640。关于参数主要关注两处。第一COCO类的getAnnIds每次调用会遍历一次索引在几十万框的数据集上会稍慢但对轨道病害这个规模的数据集完全够用。第二面积阈值参考MS COCO官方定义框面积小于32×321024像素算小目标大于96×969216算大目标。如果你手里的图像本来就是裁切后的轨枕局部图这个划分就需要自己重新定义以病害在图像中的实际占比为准。统计完成后建议把结果记录成一张表方便后续训练时对照。典型占比大概长这样病害类别框数量占比平均面积区间典型尺寸扣件类缺陷40%左右600~4800 px小于64×64轨面类缺陷25%左右1200~8000 px中等偏小道床类异常20%左右3000~30000 px中等偏大异物侵入类15%左右800~15000 px分布较广具体数字以你看的这份数据集实际统计为准但结构上大概率是这样扣件类框多、面积小道床类框少、面积大。这种不均衡结构直接影响第4章的loss权重配置。3. 从COCO到YOLO转换脚本、坐标归一化与边界处理3.1 为什么不能拿COCO直接开训COCO格式是通用交换格式适合做可视化、评测和跨框架迁移但主流检测框架的训练入口并不直接吃JSON。YOLO系列训练需要的是每张图对应一个同名txttxt里每行一个目标格式是“类别ID cx cy w h”全部归一化到0到1之间。目标检测训练流程里这步转换躲不开。有人觉得多此一举直接在DataLoader里现场解析JSON不就行了吗真做过一次就会发现训练过程中反复读取解析JSONIO开销大而且后续做数据筛选、类别重映射、剔除异常标注时txt文件改起来比改JSON省事得多。把转换作为数据预处理的一步独立出来训练流程更干净。另外轨道病害数据集的图像通常尺寸大且分辨率高转换时对坐标做归一化的同时还得检查边界越界。很多标注工具允许标注框的右下角等于图像宽高比如x100width100算出来xwidth恰好等于图像宽度这在像素坐标系里合法但归一化后可能出现值等于1.0的情况YOLO训练时对这类框的解析比较敏感容易直接丢弃或产生NaN。3.2 转换脚本从JSON到归一化txt下面这个脚本是COCO转YOLO的常用实现处理了类别ID偏移、iscrowd过滤和边界钳制三个关键点。import json from pathlib import Path def coco2yolo(json_path, out_dir): with open(json_path, r, encodingutf-8) as f: data json.load(f) # 类别ID重映射COCO的category_id通常从1开始YOLO要求从0开始 cat_id2label {cat[id]: i for i, cat in enumerate(data[categories])} # 建立 image_id 到 (文件名, 宽, 高) 的映射 img_id2info {} for img in data[images]: img_id2info[img[id]] (img[file_name], img[width], img[height]) Path(out_dir).mkdir(parentsTrue, exist_okTrue) for ann in data[annotations]: if ann[iscrowd] 1: # 跳过群体标注 continue img_file, w, h img_id2info[ann[image_id]] x, y, bw, bh ann[bbox] # 像素坐标 - 归一化中心点坐标 cx (x bw / 2) / w cy (y bh / 2) / h nw bw / w nh bh / h # 边界钳制防止归一化后越界 cx min(max(cx, 0.0), 1.0) cy min(max(cy, 0.0), 1.0) nw min(max(nw, 0.0), 1.0) nh min(max(nh, 0.0), 1.0) label cat_id2label[ann[category_id]] txt_path Path(out_dir) / (Path(img_file).stem .txt) with open(txt_path, a) as f: f.write(f{label} {cx:.6f} {cy:.6f} {nw:.6f} {nh:.6f}\n) coco2yolo(annotations/train.json, labels/train)脚本逻辑分三块。第一块做类别ID重映射enumerate从0开始编号天然满足YOLO对类别索引的要求这里的原始category_id不管从1还是从100开始都会被压缩成0到N-1。第二块建立image_id到图像尺寸的映射因为annotations里只有image_id真正要读的宽高存在images数组里。第三块是核心转换先把bbox的左上角坐标加宽高一半换算成中心点坐标再分别除以图像宽高完成归一化最后做一次min/max钳制。参数说明x、y、bw、bh来自COCO的bbox字段注意这是像素坐标不是归一化坐标单位必须与images数组里的width、height一致。center点坐标保留六位小数足够过多会撑大txt体积过少会在训练时造成精度损失。另外这个脚本用追加模式写txt意味着重复执行同一个输出目录会累积重复行。建议每次转换前清空out_dir或者在文件名后加时间戳。3.3 转换后的落空检查画框回验与统计转换脚本跑完不算完必须做一轮回验。常见做法是随机抽20张图把txt里的归一化坐标换算回像素坐标画在原图上人工扫一遍重点看框的位置是否偏移、宽高是否异常。另一个更高效的检查是统计每张图的框数量与原JSON里的框数量做差值比对。脚本执行后可以用下面的命令快速对比# 统计每个txt的行数之后再与JSON里的标注数做对照 find labels/train -name *.txt -exec wc -l {} | sort -k2 # 找出哪些图在JSON里有标注但生成的txt为空文件 find labels/train -name *.txt -size 0 | wc -l第一行命令按文件名排序输出每个txt的行数第二行统计空txt数量。如果空文件数量异常偏多大概率是images里的file_name和实际图片文件名对不上。还有一种常见情况图片是.jpg但JSON里写的是.JPG后缀这在Windows环境下不影响读取在Linux服务器上会直接找不到文件转换阶段就要把文件名后缀一并统一。注意画框回验时别只挑前几张图轨道病害数据集的图像是按线路段落采集的前20张可能是同一天的同一段轨道代表性差。用随机抽样或按类别分层抽样每一类至少抽2张。4. 训练配置怎么做小目标、类别失衡与增强策略的参数选择4.1 病害检测和通用检测的三个本质差异把轨道病害检测当成普通的目标检测任务来配参数是我见过最多的误用。这个场景有三个特点直接在参数层面影响训练效果。第一是漏检的代价远大于误检。轨面裂纹漏检可能直接威胁行车安全所以训练时不能只看mAPrecall在低置信度阈值下的表现更关键。体现在配置上就是confidence阈值要调低NMS的iou阈值要适当放宽给模型更多“宁可多报也不放过”的空间。第二是小目标占绝对主导。前面统计过扣件类缺陷大量集中在32×32到64×64像素区间。当输入分辨率只有640时这类目标在特征图上的有效感受野可能只剩几个像素模型很难学到判别特征。解决方向有两个一是提高输入分辨率二是在backbone的浅层特征上多做文章。实操中把输入分辨率从640提到1280对轨道病害这类小目标场景的提升往往比换一个更大的模型更明显。第三是类别极度不平衡。扣件类框可能占了总量的一半道床异物类可能只有零星几十个框。这种情况下模型天然偏向多数类少数类的recall会非常难看。处理上要同时动loss和采样两个环节。4.2 一份可落地的训练配置参数表基于上面三个特点适合轨道病害检测的一套配置大概长这样。需要注意这不是某个框架的官方推荐值是我在类似巡检数据集上调出来的经验区间。配置项推荐值说明输入分辨率1280×1280 或 960×960小目标优先显存受限时先降960batch size168 梯度累积大分辨率下显存紧张用累积补偿epochs120~150数据量不大太早停容易欠拟合optimizerSGD momentum0.937AdamW收敛快但泛化略差初始学习率0.01warmup 3 epochs大分辨率建议从0.005开始mosaic0.5或关闭对极小目标可能起反作用见5.3mixup0.2轻量增强减少过拟合loss权重少数类加权1.5~2.0具体见4.3conf阈值0.15~0.25漏检敏感场景压到0.15以YOLO系列的data.yaml配置为例文件内容大概是这个结构train: data/train.txt val: data/val.txt nc: 5 names: 0: 扣件缺失 1: 扣件歪斜 2: 轨面裂纹 3: 道床异物 4: 轨枕破损这里的nc必须和转换脚本里labels的类别索引最大值加一相等names只影响训练日志和可视化不影响loss计算但建议填准确因为后续坏case分析要靠名字来归类误检类型。4.3 增强策略怎么配先离线裁图再在线增强轨道病害检测的增强策略和通用场景有一个核心矛盾Mosaic这种多图拼接增强能把大图缩到四分之一对小目标类别伤害明显。如果一张1280的图里有个40像素的扣件缺陷经过Mosaic组合后这个目标可能缩到10像素左右基本学不到特征。想要保留Mosaic的优点又不想牺牲小目标我一般会分两步走。先离线裁图。在数据预处理阶段把大图按滑窗切成若干重叠块比如1280图切四块640的patch病害框跟着切分坐标走。切完的patch里小目标的像素尺寸没有变小但目标在patch里的相对占比变高了相当于把“小目标问题”改写成“中等目标问题”。切图时滑窗的overlap建议控制在10%~20%避免目标正好落在切缝边缘被截断。这一步之后做在线增强Mosaic在patch上的负担就小很多。在线增强的部分我习惯关闭大尺度下采样的增强项把Mosaic的概率降到0.5mixup保持0.2左右。另外hsv色域增强和随机平移翻转保留因为轨道病害在不同光照条件下表现差异大颜色扰动对鲁棒性有帮助。翻转要注意朝向轨道图像是横向延伸的上下翻转会把钢轨位置翻转成道床位置物理语义错乱通常只保留左右翻转。类别不平衡的处理除了loss加权另一个有效手段是做类别采样。训练时把少数类样本做重复采样多数类做降采样保证每个batch里各类别的样本数量比例不超过3比1。这个策略在实践里比单纯调loss权重更稳因为loss权重加太大会让少数类的loss震荡剧烈。5. 避坑排查五个在轨道病害数据集上常见的翻车现场5.1 类别ID错位导致模型学到张冠李戴的特征现象训练完的效果看着还行一到验证集上发现某个类别老是预测成它相邻的ID人工抽图一看扣件缺陷的框里一半是轨面裂纹。原因COCO的categories数组里的id字段不一定从0开始不少标注工具导出的是从1开始还有从5、从10这种不连续ID。转换脚本里直接拿原始category_id当YOLO类别索引没有做重映射模型把所有类别都往偏移后的位置上套。解决转换时用字典显式做一次重映射核心就是第3章脚本里的这行代码cat_id2label {cat[id]: i for i, cat in enumerate(data[categories])}改完之后无论原始ID是1、5还是100映射出来的标签都是0、1、2这种连续索引。另外在训练前打印一行类别对应关系确认一遍别只信代码里的逻辑。5.2 坐标越界导致训练时丢框或loss异常现象转换脚本跑完统计框数量比JSON里少了一批百思不得其解。打开训练日志发现某些图片的目标数量为0或者loss出现NaN。原因标注框右下角坐标等于图像宽高边界框本身就贴边像素坐标是合法的。归一化之后值等于1.0某些框架对边界值处理不严会直接丢弃这一行标注。另一种情况是标注框左上角有负坐标归一化后为负值进一步引发loss计算错误。解决在转换时对所有归一化坐标做clamp钳制到0~1区间。这行代码不能省触及边缘的框在轨道病害里不少因为轨枕裂纹、扣件缺损往往就发生在图像边缘区域。5.3 Mosaic增强把病害目标“吞”没现象开了Mosaic增强训练loss降得很快但验证集上的小目标recall反而比不开Mosaic时低尤其是扣件类小缺陷漏检严重。原因Mosaic会把四张图缩放到原图的四分之一再拼接原图里40像素的小目标在Mosaic拼接图里变成10像素特征消失。轨道病害场景里小目标占比高这个副作用被放大了。解决第一选择是降低Mosaic概率到0.5以下第二选择是训练初期mosaic增大最后10个epoch关闭这个策略在很多检测框架里有现成参数第三选择是走4.3的先离线切patch再训练让小目标在patch里相对变大。5.4 显存OOM不一定是分辨率的问题现象明明输入分辨率只从640提到960显存直接爆掉。查模型参数没变数据加载也没问题怀疑是采样器出了错。原因特征图大小随分辨率平方级增长960相比640的空间尺寸大了约2.25倍特征图存储、中间激活全跟着涨不是线性关系。很多人只算了输入图像的显存忽略了backbone中间层的占用。解决先把batch size从16降到8配合梯度累积保持等效batch不变。还不行就把分辨率降到896或832对比一下小目标recall下降多少。很多时候960和1280之间的差异没有想象中大而显存占用差了几倍性价比要算清楚。5.5 iscrowd没过滤导致同一位置重复框训练现象训练时发现某些图的loss异常大画图一看同一个病害位置叠了两个框一个贴着目标一个包了半张图。原因COCO格式里iscrowd1的标注表示群体目标这类框通常覆盖区域特别大且与目标级框重叠。转换脚本里如果只按行解析、不判断iscrowd字段就会把一个目标转换成两个标注行训练时一正一负互相打架。解决在转换脚本里加上iscrowd判断等于1直接跳过。这是COCO转YOLO的标准过滤条件无论用现成脚本还是自己写都要检查这一行有没有漏。6. 上线前的验证动作坏case回灌与误检率统计训练收敛后最忌讳只看验证集mAP就收工。轨道病害场景的验证动作我习惯按四步走每一步都会暴露不同的问题。第一步在测试集上按目标尺寸分层看AP。只报一个mAP等于把问题藏起来了按small、medium、large三档分别算指标如果小目标AP明显低于另外两档优先回头调分辨率而不是换更大的模型。第二步抽取高置信误检样本。把置信度大于0.4但GT为空的框全部导出成图片人工过一遍。这一步最能暴露模型学了什么不该学的东西比如把螺栓阴影当裂缝、把水渍当异物。常见做法是在推理脚本里加一个导出逻辑# 推理并保存带置信度的检测结果 python detect.py --weights last.pt --source test_imgs --conf 0.2 --save-txt --save-conf # 抽取出置信度高于0.4但没匹配到GT的框整理成待审核图集 python analyze_fp.py --det runs/detect/exp/labels --gt annotations/test.json --conf 0.4第三步高置信误检回灌训练集。人工筛选出那些看起来确实像病害但被标注漏掉的误检框补标后加回训练数据。这一步对收敛的意义很大因为它相当于直接消灭了模型的“虚假负样本”模型不再需要在“报还是不报”之间纠结。第四步做鲁棒性检查。轨道巡检的图像采集受光线、天气、季节影响大拿一批夜间、逆光、雨天样本跑一遍推理统计recall下降幅度。如果下降明显在在线增强里补上亮度扰动和灰度变换再单独微调十几个epoch。上面这套流程最后沉淀下来的核心资产其实是那份回灌过的数据而不是模型本身。有一次我在某条线路数据上跑实验模型在验证集上想来想去拿了高分上线试运行却发现异物类误检成倍增长后来逐张翻误检图才发现标记工具导出的道床异物框有大量非语义标注——把阴影也框进去了。从那以后我每次评估新版本模型都强制走一遍坏case抽图和回灌流程先看错在哪再谈精度高低。这套方法放在你手里的这份数据集上也一样适用希望帮到你。本文还有配套的精品资源点击获取