简介面向铁路设施智能运维与计算机视觉研究者标题标注为4278张原始图片的轨道缺陷数据集以COCO JSON格式提供标注可用于裂缝、间隙等常见缺陷的识别与定位支撑目标检测、实例分割等模型训练与算法预研。压缩包共2000个文件包含1997张jpg图像与3个json标注文件整体大小约271.95MB图片与标注一一对应方便接入YOLO、MMDetection等主流检测框架。资源描述中还附有配套说明链接有助于理解采集场景、标注规则与数据组织方式降低陌生数据集的上手成本。目前已有1372人浏览学习适合作为钢轨表面巡检、缺陷分类等项目的补充训练资料。对于需要快速获取带标注轨道图像的开发者这份数据能显著节省人工标注时间并可用于模型效果对比与调参验证。1. 铁路轨道缺陷数据集为什么拿到手先别急着训练做工业缺陷检测的同行应该都有这种体验从网上扒到一个数据集第一反应不是看标注格式而是先解压、翻图片、猜类别。这个铁路轨道缺陷数据集一共4278张原始图片标注是COCO JSON格式可识别裂缝和间隙缺陷——听起来挺常规但真拿去训练时很多人会在第一步就翻车。原因不是数据质量问题而是COCO格式和大多数缺陷检测项目默认的YOLO格式之间存在一大堆隐性差异包括坐标精度、类别ID起点、是否包含未标注图片等。这篇文章不打算把COCO当成一个格式标准来讲而是把它当成你接下来三天要打交道的数据文件来拆每一层结构对应什么物理含义哪些字段在画框、训练、转格式时会被真正用到以及我在实际跑通Detectron2和YOLOv8时踩过的几个具体坑。适合手里已经有这个数据集、想快速验证能不能用来训练或者打算拿它做预训练再投入生产场景的工程师。2. 先搞清这个数据集的构成4278张图里到底藏了什么2.1 图片与标注的目录关系不是所有图片都有标注这个数据集最常见的下载解压形态是一个images目录装JPG一个JSON文件装标注偶尔还会附带一个类别说明txt里面写crack, gap之类的关键词。我第一次拿到手时犯了个错——直接用脚本扫了images目录下的文件数量和JSON里登记的图片数量做对比发现对不上以为数据集不完整。后来才明白COCO格式的JSON里有一个images字段数组里面每一张图都有独立的id、file_name、width、height。关键点在于images数组里的条目数才是纳入标注体系的图片数而images目录里可能还残留着下载时的缩略图、损坏文件或者重复命名文件。也就是说目录里的物理文件数不等于数据集的有效图片数。用Python检查一下JSON里的图片条目是否都能在磁盘上找到对应文件是拿到数据集后的第一步也是最容易被跳过的一步import json import os with open(annotations/train.json, r, encodingutf-8) as f: coco json.load(f) img_dir images/train missing [] for item in coco[images]: path os.path.join(img_dir, item[file_name]) if not os.path.exists(path): missing.append(item[file_name]) print(fJSON登记图片数: {len(coco[images])}) print(f缺失文件数: {len(missing)}) if missing: print(missing[:10])这段代码做的事情非常朴素但价值很高它把JSON里声明的图片与磁盘上的实际文件对齐避免后续训练时因为路径错误导致读图失败。很多训练框架比如Detectron2在数据加载阶段遇到缺图报错信息会非常隐晦说IndexError: list index out of range你看半天看不出是缺文件白白浪费时间。2.2 类别与标注字段裂缝和间隙是怎么被记录的COCO格式的核心在于categories和annotations两个字段。categories定义了所有缺陷类别通常长这样{ categories: [ {id: 1, name: crack, supercategory: defect}, {id: 2, name: gap, supercategory: defect} ] }注意这里的id习惯上从1开始不是0。这个细节在转YOLO格式时特别容易出错因为YOLO的类别ID是从0开始计数的。如果你直接用COCO的category_id去写YOLO的class_id裂缝会被写成1、间隙会被写成2而YOLO默认类别0是背景——整个训练逻辑就乱了。annotations字段是每一张图上所有缺陷框的集合。一个典型的标注条目包含id、image_id、category_id、bbox和segmentation其中bbox的格式是[x, y, width, height]单位是像素且为浮点数。segmentation在某些版本里是一个包含多边形顶点坐标的数组在另一些版本里是空数组——因为有些标注工具只画框不画多边形。对于轨道缺陷检测这个场景segmentation不是必须的。如果你的最终目的是训练目标检测模型YOLO、Faster R-CNN、Detectron2只需要bbox就够。但如果你打算做实例分割比如想把裂缝的轮廓精确抠出来就需要segmentation数据齐全。所以拿到数据集后建议立刻统计一下annotations里有多少条标注带segmentation多少条不带这决定了后续技术选型的边界。2.3 样本量与类别分布直接决定你的评估策略4278张图听上去量不小但量化到裂缝和间隙两个类别后情况可能完全不同。常见的分布问题是间隙缺陷的样本量远小于裂缝或者某些图片里同时存在多个小缺陷而大部分图片只有背景。我通常拿到COCO标注后会先做一个快速统计——统计每个类别有多少个标注框以及每张图平均有几个标注框from collections import Counter cat_id_to_name {c[id]: c[name] for c in coco[categories]} cat_counter Counter() img_counter Counter() for ann in coco[annotations]: cat_counter[cat_id_to_name[ann[category_id]]] 1 img_counter[ann[image_id]] 1 print(类别-标注框数量:, dict(cat_counter)) print(有标注的图片数:, len(img_counter))如果发现间隙只有两三百个框那么训练时就需要考虑数据增强特别是几何增强、focal loss或者换个评估指标。这类前置统计不写出来后面模型训练完了再发现类别不平衡调参就非常被动——你可能连损失函数都已经调完了回头一看是数据分布问题等于白调。3. 把COCO JSON读进内存详解annotations与images的关联逻辑3.1 从image_id到文件路径的完整映射链路COCO格式里annotations中的每条标注通过image_id关联到images数组中的某张图而images数组里又有file_name指向磁盘路径。这三级映射听起来简单但实际代码里很容易出现一次性通过image_id做字典索引时把结构搞复杂的情况。我的习惯是上来就构建一个以image_id为键、图片信息为值的字典再用它把所有标注框聚合到对应图片下。这样后续要画图、转格式、做数据统计都只需要查这个字典# 构建 image_id - image_info 映射 img_id_to_info {img[id]: img for img in coco[images]} img_id_to_anns defaultdict(list) for ann in coco[annotations]: img_id_to_anns[ann[image_id]].append(ann) # 示例: 取第一张有标注的图片的完整信息 for img_id, anns in img_id_to_anns.items(): if anns: sample_img img_id_to_info[img_id] print(图片文件:, sample_img[file_name]) print(图片尺寸:, sample_img[width], sample_img[height]) print(框数量:, len(anns)) print(第一个框bbox:, anns[0][bbox]) break这里用defaultdict(list)来聚合是因为一张图上可能有多处裂缝必须用list结构才能全存下。如果你用普通字典加赋值第二次往同一张图加标注时会把第一次覆盖掉——这是处理COCO时非常典型的新手错误。另外注意img_id_to_info里的width和height。有些数据集在标注时候图片尺寸和实际磁盘文件尺寸不一致——比如标注工具处理过旋转、或者下载过程中图片被压缩过。训练时如果拿JSON里的尺寸去归一化bbox而实际读入的图片尺寸不同框就会整体偏移。所以画图验证这一步绝不能省。3.2 bbox与segmentation的取舍两个都读还是只读一个上一节提到segmentation字段可能是空的。具体到这个铁路轨道缺陷数据集我建议以实际文件内容为准不要先入为主。如果segmentation是非空的多边形建议把多边形转换成mask再做训练因为轨道裂缝的形状本身是细长、不规则的用矩形框做检测容易把相邻的间隙缺陷也包进同一个框里。如果segmentation为空数组那就要认命老老实实用bbox顶多做数据增强时稍微裁掉一些无关背景区域。还有个折中方案用bbox作为标注来训练一个目标检测模型模型推理出的框如果覆盖范围过大再用图像分割后处理比如阈值分割收紧边界。这个方案在轨道场景下我是见过的效果比直接上实例分割模型稳定因为数据格式限制摆在那。下面这段代码演示如何通过检查segmentation长度来判断数据集类型seg_count 0 bbox_only_count 0 for ann in coco[annotations]: if ann.get(segmentation) and len(ann[segmentation]) 0: seg_count 1 else: bbox_only_count 1 print(f带segmentation的标注: {seg_count}, 仅bbox: {bbox_only_count}) # 如果seg_count极低, 说明数据集是纯检测格式 # 后续模型选型优先考虑目标检测而非实例分割这个统计结果直接决定你接下来用哪个算法。如果你手里的是纯bbox格式却非要去训练Mask R-CNN模型会因为没有mask监督信号而无法收敛。这不是调参能解决的是任务定义问题。反过来如果segmentation很齐全却强行转成bbox用YOLO等于把标注信息砍掉一半精度上限也会受限。4. 数据可视化与体检训练前必做的三件核对4.1 把标注画到图上眼见为实的验证脚本数据集的标注质量光靠看数值是不够的必须把bbox和segmentation画回原图上人眼核对几轮。推荐使用opencv来做加载快、画框简单代码也不长import cv2 def visualize_coco_image(coco, img_id_to_info, img_id_to_anns, img_id, save_path): img_info img_id_to_info[img_id] img_path os.path.join(images/train, img_info[file_name]) img cv2.imread(img_path) for ann in img_id_to_anns[img_id]: bbox ann[bbox] x, y, w, h [int(v) for v in bbox] cat_name cat_id_to_name[ann[category_id]] color (0, 0, 255) if cat_name crack else (0, 255, 255) cv2.rectangle(img, (x, y), (x w, y h), color, 2) cv2.putText(img, cat_name, (x, y - 5), cv2.FONT_HERSHEY_SIMPLEX, 0.6, color, 2) cv2.imwrite(save_path, img) # 抽样画10张不同图片 count 0 for img_id in img_id_to_anns: if count 10: break visualize_coco_image(coco, img_id_to_info, img_id_to_anns, img_id, fvis_{count}.jpg) count 1这段代码里有两个值得留意的细节。第一bbox坐标转成int后再画矩形因为opencv的rectangle不接受float。第二类别用不同颜色区分红vs黄人眼扫一遍就能发现有没有标错——比如裂缝框画到了枕木上、间隙框把两块钢轨同时包了进去。如果发现大量框的坐标位置和缺陷实际位置对不上那这个数据集就要先做坐标修正而不是直接进训练流程。视觉核对大约花你半小时但能避免你花三天训练一个垃圾模型这笔账一定要算清楚。4.2 检查图片尺寸一致性统一resize策略的前提条件工业数据集的图片尺寸常常不一致。这个铁路轨道缺陷数据集里的图片有的可能是1918×1280有的可能是960×720甚至有些标注工具导出时会把竖图旋转成横图。如果所有图片尺寸不一致训练时必须统一resize而resize策略会直接影响小目标的检测效果。快速统计宽高分布wh_set set() for img_info in coco[images]: wh_set.add((img_info[width], img_info[height])) print(f不同尺寸种类数: {len(wh_set)}) for wh in list(wh_set)[:10]: print(wh)如果种类只有一两种训练时候统一resize到较大尺寸比如1280×1280是安全的如果种类很多建议采用letterbox填充而非直接拉伸否则裂缝这类细长缺陷的宽高比会被破坏检测框会变形。这个决策点应该在训练脚本里明确体现而不是依赖框架默认的resize行为。4.3 重复与相似样本检测防过拟合的提前预防4278张图片的轨道数据集里最隐蔽的问题是相似样本过多——同一段轨道从不同角度、不同光照条件下拍了几十张导致模型在测试集上表现好换一段新轨道就完全失灵。我的做法是计算图片的感知哈希快速找出完全重复或高度相似的样本对然后人工抽看是否存在数据泄漏风险import imagehash from PIL import Image def compute_phash(img_path): return imagehash.phash(Image.open(img_path)) # 只对前50张图做示例演示 phash_dict {} for img_info in coco[images][:50]: path os.path.join(images/train, img_info[file_name]) phash compute_phash(path) phash_dict[img_info[file_name]] phash # 找出汉明距离小于5的相似图像对 similar_pairs [] files list(phash_dict.keys()) for i in range(len(files)): for j in range(i 1, len(files)): if phash_dict[files[i]] - phash_dict[files[j]] 5: similar_pairs.append((files[i], files[j])) print(f相似图片对数: {len(similar_pairs)})感知哈希的汉明距离越小代表图片越相似。如果发现大量相似对那么在划分训练集和验证集时就要按轨道段来分——而不是按图片随机分——否则同一段轨道既在训练集又在验证集验证指标会虚高实际部署到新场景时精度掉得很惨。这个坑在公开数据集上尤其常见因为数据采集方往往是一个地方连续拍了很多照片。5. 避坑COCO标注转YOLO格式时的五个血泪经验5.1 类别ID从1开始YOLO从0开始直接减1这是转格式时最容易翻车的一点。COCO的categories id通常从1开始而YOLO的class id从0开始。如果直接拿COCO的category_id写进YOLO的label文件会多偏移一个数。正确做法是在映射表里显式转换# coco_category_id - yolo_class_id id_map {1: 0, 2: 1} yolo_class_id id_map[ann[category_id]]别嫌这个映射表写起来简单而省略它一旦数据集后续更新类别没有映射表你的代码就是死代码。5.2 bbox坐标系COCO是左上角宽高YOLO是中心点宽高归一化基准别搞错COCO的bbox是[x, y, width, height]其中x、y是框左上角坐标。YOLO格式的label是[class_id, center_x, center_y, width, height]所有值都除以图片宽高归一化到0~1之间。转换公式def coco_bbox_to_yolo(bbox, img_w, img_h): x, y, w, h bbox center_x (x w / 2) / img_w center_y (y h / 2) / img_h norm_w w / img_w norm_h h / img_h return center_x, center_y, norm_w, norm_h这里要注意的坑是归一化的分母必须用原始图片的宽高而不是resize后的宽高。如果你打算把图片resize到640×640再训练应该在resize前先用原图尺寸完成归一化然后在训练时让模型自己处理letterbox。如果先resize再归一化并且分母用了resize后的尺寸数学上其实是一样的——但如果你忘了resize、或者resize逻辑与label生成逻辑不一致框就全部错位。5.3 浮点精度标注框坐标是浮点数保存时至少要保留6位小数COCO JSON里的bbox坐标都是浮点数很多是像x: 531.729, y: 284.145, w: 14.487, h: 8.992这样带三位小数的值。转成YOLO格式后如果保存label文件时只保留2位小数小目标的中心点坐标可能偏移好几个像素对于10×10像素级别的裂缝缺陷来说这是致命的。建议写入时用f{value:.6f}格式化。5.4 空标注图片有些图片没有缺陷训练时被全背景输出主导这个数据集虽说是缺陷数据集但可能包含一些完全没有缺陷的干净轨道图片——或者标注者在标注时认为某些轻微表面纹理不够缺陷标准而略过。这些图片在image字段数组里存在但在annotations中没有对应的标注。转YOLO格式时如果对这些图片生成空的label文件训练时有些框架会把文件名缺失的label文件当作错误跳过导致这些图片不参与训练等于压缩了训练数据量。处理方式有两种一是把这类图片单独放入一个background目录在训练配置里显式处理背景样本二是直接过滤掉这些图片不参与训练。我建议选择前者因为轨道场景里没有缺陷本身就是很常见的负样本信号模型需要见过足够的负样本才能抑制误检。5.5 验证集划分随机划分会在缺陷检测里产生误导缺陷检测数据集普遍存在同一场景连续拍摄、重复内容多的问题。随机划分训练集和验证集可能导致验证集里有和训练集几乎一模一样的图片只是裁剪位置或曝光略有不同验证精度虚高。正确做法是先按轨道线路段或者拍摄批次分组再把组划分到训练或验证集确保同一个地点的照片全集都在同一侧。具体到这个铁路轨道数据集我一般会先查看file_name里是否包含拍摄批次或位置编号有的话优先利用这些信息分组。6. 用Detectron2快速跑通一个检测基线从标注到mAP的最后一公里把数据转换好之后可以先用Detectron2跑一个标准Faster R-CNN或RetinaNet基线验证数据集本身的可训练性。为什么不先上YOLO因为Detectron2的COCO数据加载器是原生的少一层格式转换的干扰可以更快定位出是数据问题还是模型问题。跑通基线后再转到YOLO做工程化部署是更稳的节奏。Detectron2注册自定义数据集的代码from detectron2.data import DatasetCatalog, MetadataCatalog from detectron2.structures import BoxMode import random def get_rail_dicts(json_path, img_dir): with open(json_path, r, encodingutf-8) as f: coco json.load(f) img_id_to_info {img[id]: img for img in coco[images]} img_id_to_anns defaultdict(list) for ann in coco[annotations]: img_id_to_anns[ann[image_id]].append(ann) dataset_dicts [] for img_id, anns in img_id_to_anns.items(): record {} img_info img_id_to_info[img_id] record[file_name] os.path.join(img_dir, img_info[file_name]) record[image_id] img_id record[height] img_info[height] record[width] img_info[width] objs [] for ann in anns: bbox ann[bbox] obj { bbox: [bbox[0], bbox[1], bbox[0] bbox[2], bbox[1] bbox[3]], bbox_mode: BoxMode.XYXY_ABS, category_id: ann[category_id], iscrowd: 0 } objs.append(obj) record[annotations] objs dataset_dicts.append(record) return dataset_dicts DatasetCatalog.register(rail_train, lambda: get_rail_dicts(annotations/train.json, images/train)) DatasetCatalog.register(rail_val, lambda: get_rail_dicts(annotations/val.json, images/val)) MetadataCatalog.get(rail_train).set(thing_classes[crack, gap])这里将COCO的XYWH格式转换成了Detectron2要求的XYXY格式——左上x、左上y、右下x、右下y。注意category_id不需要减1因为Detectron2的Metadata按COCO的类别ID来对应。跑通这个注册后训练脚本和官方示例基本一样改一下数据集名和输出目录即可。最后说一个我自己的习惯**每次拿到新的缺陷数据集我都会先花一个晚上跑通检测基线、画出十到二十张预测可视化再决定要不要继续深入调参。**如果可视化里出现大量漏检或误检先回头检查数据转换逻辑和样本分布而不是急着换backbone或堆训练技巧。这个流程帮我在轨道裂缝、焊缝缺陷、表面划痕等多个项目上少走了很多弯路。数据集的坑永远比模型参数的坑多先跟数据较劲再跟模型较劲才是缺陷检测项目里性价比最高的顺序。希望这份踩坑手记能帮你在轨道缺陷检测方向少消耗一些无效调试时间。本文还有配套的精品资源点击获取
阅读完成 · 觉得有帮助?