☰
铁路轨道缺陷检测实战:COCO标注转YOLOv8训练与避坑指南
2026/9/28 1:49:02 网站建设 项目流程

简介:面向铁路基础设施巡检与计算机视觉目标检测、实例分割场景,这份轨道缺陷数据集完整提供了四千二百七十八张原始轨道图像及对应的COCO JSON格式标注,像素级标注可识别轨道表面裂缝、间隙等典型缺陷,适合用于训练、验证与评测缺陷检测模型,可有效支撑裂缝检测、间隙测量等任务。资源包共包含两千个文件,由一千九百九十七张jpg图像与三个json标注文件组成,压缩后约二百七十二兆,图像覆盖不同光照、角度与场景环境,标注文件遵循COCO标准,可配合YOLO、MMDetection等主流框架直接开始训练,文件结构与标注字段一目了然。目前已有一千三百六十六人学习下载,数据命名方式保留了采集场景与增强信息,便于筛选、划分训练集或进行针对性扩充。对于高校实验室、职业技术培训以及工业巡检算法研究者,这份数据集能节省大量现场采集和人工标注时间,帮助快速验证模型在不同轨道缺陷上的检测效果,是开展实践教学与算法调优的实用资源。

1. 铁路轨道缺陷数据集:4278张图能做什么,不能做什么

铁路轨道缺陷数据集,4278张原始图片,附带COCO JSON格式的标注,检测目标就两个:有没有裂缝、有没有间隙缺陷。这个规模在工业缺陷检测里不算小,但离“拿到就能训”还差得很远。我拿到类似数据的第一反应从来不是直接训练,而是先拆开标注看一遍——COCO JSON格式虽然规范,但实际里面可能藏着类别ID混乱、标注框偏移、缺陷目标只有几个像素的问题。这篇笔记讲清楚COCO格式怎么读、怎么转成YOLO训练格式、哪些参数对轨道缺陷最敏感,以及怎么验证模型不是只在验证集上好看。想用这份数据把缺陷检测落地的人,按这个路径走能少踩一半坑。

2. COCO JSON标注结构拆解:为什么缺陷检测选它而不是VOC或TXT

2.1 五个顶层字段:图片、标注、类别是怎么关联起来的

COCO JSON是一份完整的标注文件,里面通过ID把图片和标注关联起来。顶层有五个关键字段:info、licenses、images、annotations、categories。实际训练里最常用的是后三个,前两个更多是元信息。下面是一份精简的结构示例,和你拿到的轨道缺陷数据集标注格式基本一致:

{ "info": { "description": "rail track defect dataset", "version": "1.0" }, "licenses": [], "images": [ { "id": 1, "file_name": "track_0001.jpg", "width": 1920, "height": 1080 } ], "annotations": [ { "id": 1, "image_id": 1, "category_id": 1, "bbox": [512.5, 380.2, 45.1, 12.3], "area": 555.73, "iscrowd": 0 } ], "categories": [ { "id": 1, "name": "crack", "supercategory": "defect" }, { "id": 2, "name": "gap", "supercategory": "defect" } ] }

这段结构里最关键的是images和annotations的关联方式。每张图片有一个唯一id,每条标注也带一个image_id,指向它属于哪张图。bbox是[x, y, width, height],注意不是YOLO那种中心点加宽高的写法,而是左上角坐标加框的宽高。这里x和y是像素坐标,width和height是框的实际像素宽高,没有归一化。area在纯框标注里通常用width * height算,但如果后面要用pycocotools评估mAP,这个字段必须和bbox一致,否则会报错或算出的AP不对。

另一个容易忽略的是categories的id。COCO官方数据集的类别ID是从1开始的,但很多工具和脚本习惯从0开始。你这个轨道数据集如果categories里crack的id是1,gap的id是2,那么转换成YOLO格式时就得做一次减一映射,否则训练时候类别就错了。我后面会给出专门的转换脚本。

2.2 与VOC/YOLO格式对比:COCO在缺陷场景的三个实际优势

工业缺陷检测场景里,最常见的数据格式有三种:VOC XML、YOLO TXT、COCO JSON。很多人问为什么不用更简单的TXT,我的回答是:简单不等于可靠。YOLO TXT每张图一个文本文件,每个文件里几十行类别 cx cy w h,一旦图片文件名和标注文件名对不上,或者图片被resize过,所有坐标就全废了。而COCO JSON把图片元信息和标注放在一起,谁和谁对应一目了然。

第一个优势是数据完整性。COCO JSON里每张图都带着width和height,转换脚本可以通过这个字段自动校验标注坐标有没有超出图片边界。这个在轨道缺陷数据里特别重要。4278张图里面如果混着一批1920x1080和一批1280x720的图,YOLO TXT没法感知尺寸变化,但COCO JSON可以。

第二个优势是扩展性。annotations里除了bbox,还能存segmentation多边形、iscrowd标志、area。像裂缝这种细长目标,矩形框里会混进大量背景,如果后续想用分割模型或者混合标签训练,COCO格式不用重新标,直接在原文件上加字段就行。VOC格式虽然也能存多边形,但文件散落太多,维护成本高。

第三个优势是工具链成熟。CVAT、labelImg、X-AnyLabeling这些标注工具都支持COCO JSON导入导出,pycocotools可以直接算mAP和混淆矩阵。我自己做数据检查时,习惯用一行Python脚本把COCO标注画回图片上,这个操作在YOLO TXT时代要自己写解析器,现在直接用cocoapi就行。对于这个轨道缺陷数据集来说,用COCO格式等于给自己留了后悔药,后面想切模型、加类别、换评估方式都不用重标数据。

3. 从COCO JSON到YOLOv8训练:转换脚本与三个必须调的参数

3.1 第一步:用Python脚本校验COCO标注,先看数据再动手

拿到数据别急着训练,先跑一轮数据校验。我一般会写一个脚本统计三件事:图片数量、标注数量、类别分布。顺带检查有没有图片文件名对不上、标注框越界、类别ID空白这些低级问题。过去在好几个数据集上翻过车,原因都是某个标注文件里混了一条category_id不存在的记录,训练时loss直接异常。

import json from pathlib import Path from collections import Counter coco_path = Path("annotations/instances_train.json") coco = json.loads(coco_path.read_text(encoding="utf-8")) print("图片数量:", len(coco["images"])) print("标注数量:", len(coco["annotations"])) print("类别:", [(c["id"], c["name"]) for c in coco["categories"]]) # 统计每张图上的标注数量,找出异常样本 img_ann_count = Counter(ann["image_id"] for ann in coco["annotations"]) print("标注数最多的5张图:", img_ann_count.most_common(5)) # 检查bbox是否越界 img_map = {img["id"]: img for img in coco["images"]} bad_boxes = [] for ann in coco["annotations"]: img = img_map[ann["image_id"]] x, y, w, h = ann["bbox"] if x + w > img["width"] + 1 or y + h > img["height"] + 1: bad_boxes.append((ann["id"], img["file_name"])) print("越界标注数量:", len(bad_boxes))

这段代码的逻辑很简单:先加载整个JSON,打印基础统计量,然后用Counter统计每张图的标注数,最后遍历所有bbox判断有没有超出图片边界。最后的加1是容忍浮点误差。这个脚本跑完,你基本能判断这个轨道缺陷数据集是干净还是需要返工。

参数说明:encoding="utf-8"必须加,Windows下默认编码可能是GBK,JSON里有中文类别名时会解码失败。越界判断里的容忍值看具体情况,COCO里有些标注工具会生成比图片宽1到2像素的框,如果超过5像素,说明标注时图片尺寸和实际模型输入尺寸不匹配,后面转换时要重点处理。

3.2 第二步:COCO转YOLO标注的脚本和类别ID偏移

YOLO系列训练需要TXT格式标注,每行一个目标,格式是类别索引 归一化中心x 归一化中心y 归一化宽度 归一化高度。转换时最容易出错的点有两个:归一化时除的是图片原始宽高,不是数据集里最大宽高;类别ID要从COCO的id映射到从0开始的索引。

import json from pathlib import Path def coco_to_yolo(coco_json_path, output_dir): coco = json.loads(Path(coco_json_path).read_text(encoding="utf-8")) output_dir = Path(output_dir) output_dir.mkdir(parents=True, exist_ok=True) # COCO的category_id到YOLO索引的映射,必须是连续0..n-1 cat_ids = sorted({c["id"] for c in coco["categories"]}) cat_id_to_idx = {cat_id: i for i, cat_id in enumerate(cat_ids)} img_map = {img["id"]: img for img in coco["images"]} anns_by_img = {} for ann in coco["annotations"]: anns_by_img.setdefault(ann["image_id"], []).append(ann) for img_id, img in img_map.items(): w = img["width"] h = img["height"] lines = [] for ann in anns_by_img.get(img_id, []): x, y, bw, bh = ann["bbox"] cx = (x + bw / 2) / w cy = (y + bh / 2) / h nw = bw / w nh = bh / h lines.append(f"{cat_id_to_idx[ann['category_id']]} {cx:.6f} {cy:.6f} {nw:.6f} {nh:.6f}") if lines: stem = Path(img["file_name"]).stem Path(output_dir / f"{stem}.txt").write_text("\n".join(lines), encoding="utf-8") coco_to_yolo("annotations/instances_train.json", "labels/train")

这个脚本的核心在于cat_id_to_idx映射。如果你直接拿category_id当YOLO类别索引,而COCO里crack的id是1、gap的id是2,那么训练时类别索引1会在names: ["crack", "gap"]里对应gap,全部错位。用排序后的enumerate生成连续索引,就从根源上堵住这个问题。

归一化时还有一个容易忽略的细节:x + bw / 2这一步。YOLO用的中心点坐标,而COCO给的是左上角坐标。如果你只拿x直接除宽,模型会学到一个偏左上方的目标中心,推理时框会整体偏移。我见过好几个新手在这个地方翻车,损失函数看着在下降,但预测框位置总是不对。

3.3 第三步:训练配置里的分辨率、batch和类别权重

数据转换完,接下来是训练配置文件。用YOLOv8训练自己的数据集,需要一个YAML文件描述数据路径和类别。对于铁路轨道缺陷数据集,我的标准配置是这样:

# rail_track.yaml path: ./datasets/rail_track train: images/train val: images/val nc: 2 names: ["crack", "gap"]

训练命令我一般这样跑:

yolo train data=rail_track.yaml model=yolov8n.pt imgsz=1280 batch=16 epochs=150 patience=20

三个参数对缺陷检测影响最大,第一个是imgsz。轨道裂缝和间隙缺陷很多是细长条,宽度只有十几个像素,如果按默认的640输入,这些小目标在特征图里可能只剩1到2个像素,模型根本学不到纹理。我建议至少用1280,显存不够就降低batch,但不要动分辨率。imgsz=1280配合YOLOv8的mosaic增强,对小目标召回率的提升是肉眼可见的。

第二个是batch。这个参数不是越大越好,而是取决于你的显存和图片分辨率。1280分辨率下,16张图大约需要12GB显存。如果你只有8GB,把batch降到8,否则会出现CUDA out of memory,这不算玄学,是实际计算量决定的。

第三个是类别权重。如果这个数据集里裂缝有3000条标注,而间隙缺陷只有300条,模型会偏向学裂缝。这时可以在loss里提高稀有类别的权重,或者在yolo train命令里调cls=1.5这类参数。更稳妥的做法是先看数据分布再决定,别盲目调权重。

4. 轨道缺陷数据集避坑指南:裂缝和间隙标注的5个典型事故

4.1 裂缝被标成多个小框,模型学成“碎渣”

现象:训练出来的模型在裂缝上输出一堆零散的小框,明明是一条连续裂缝,被检测成好几段。原因:标注员把一条长裂缝手动切成了多个矩形框,每个框只覆盖裂缝的一小段。这是轨道缺陷数据里最常见的问题。解决:在标注规范里明确“一条裂缝算一个目标”,除非中间断裂超过一定像素,否则必须合并。对于已有数据,可以写脚本判断多个框是否有大面积重叠,重叠超过阈值就合并成一个外接框。但这个操作要谨慎,如果两条裂缝平行且靠近,合并后会把中间的正常区域包进去。

4.2 间隙缺陷的框过大,把背景也包进去

现象:模型把轨道间隙周围的道砟、石子误检成缺陷,误报率特别高。原因:标注间隙缺陷时,标注员把框画到了整个缝隙区域,而不是缺陷本身。间隙缺陷的特点是纵向细长,横向宽度不大,如果框的高度包含了太多背景,模型就会把背景纹理也学到。解决:用pycocotools或OpenCV把标注框画回原图,逐张查看间隙缺陷的框是否贴合。如果框宽超过缺陷实际宽度两倍以上,应该改成多边形标注,或者把框收紧到缺陷边缘。CVAT里可以直接调整现有框,比重新标快得多。

4.3 类别ID在COCO和YOLO之间差1,训练直接崩

现象:训练时log里loss正常下降,但验证集mAP一直是0。原因:COCO的category_id从1开始,YOLO的类别索引从0开始。如果你没有做cat_id_to_idx映射,crack的id=1会被当成第二个类别gap,而gap的id=2越界,模型只学了一个类别。解决:在转换脚本里打印映射表,确认{1: 0, 2: 1}这样连续从0开始。这个错误很隐蔽,因为训练不会报错,只有查看预测结果时才发现类别全乱了。我现在每转一次数据,都会随机抽十张图跑一次可视化,把YOLO TXT转回坐标画在原图上人工确认。

4.4 图片EXIF旋转后,标注框整体偏移

现象:训练集和真实场景图片里有手机拍摄的照片,模型在部分图片上预测框整体偏到右上角。原因:部分图片带EXIF旋转信息,显示时是正立的,但OpenCV和PIL读取原始像素时没有自动旋转,导致标注坐标和实际图像内容错位。比如一张旋转90度的图,标注框仍然按旋转前的坐标系画,训练时模型看到的图和标注不一致。解决:在数据预处理阶段统一用ImageOps.exif_transpose处理所有图片,并重新生成图片文件,确保实际像素方向与标注坐标一致。这个坑在公开数据集里很少见,但自己收集的轨道缺陷图片经常遇到。

4.5 部分图片没有任何标注,loss出现NaN

现象:训练跑到一半loss变成NaN,然后模型权重全部失效。原因:数据集里有一批图片没有任何目标的标注,但被放进了训练集。YOLO在损失计算里遇到空目标时会除以零,某些版本直接产生NaN。解决:先用脚本检查每张训练图对应的TXT文件是否为空,把所有空标注的图片从训练集里剔除,或者单独放到一个background类别目录里。另外,检查是否有损坏的图片文件,用cv2.imread读不出来的图也要删掉。这个操作应该放在数据校验阶段,不要等训练崩了再排查。

5. 用CVAT做二次标注:把4278张图的价值榨干

5.1 把COCO JSON导入CVAT,检查并修正误标

标注工具里我推荐CVAT,它对COCO JSON的支持最完整。把现有的instances_train.json导入CVAT,可以直接在Web界面上逐张查看标注框、调整边界、删除误报、补充漏标。具体操作是:创建项目时选“COCO JSON 1.0”作为导入格式,上传压缩包或者标注文件,CVAT会自动识别images和annotations。导入后建议按类别分组检查,先看所有gap的标注,再看crack的,不要混着看,混着看容易漏掉系统性错误。

检查时重点看两类问题:一是框是否和缺陷边缘贴合,二是同一个缺陷有没有被重复标注。轨道裂缝常常延伸到图片边界,如果标注框只画了可见部分,后续模型会把截断处当成目标边界,影响定位精度。CVAT里可以直接拖拽框的端点,把裂缝延伸到图外或补齐到完整目标。

5.2 裂缝该用多边形还是矩形框?标注规范建议

回到这个数据集本身,原始标注用的是COCO矩形框。但裂缝和间隙这两种缺陷形状差异很大,矩形框的适用性完全不同。间隙缺陷形状相对规整,垂直方向宽度稳定,用矩形框没问题。裂缝则不同,它是细长曲线,矩形框会把大量轨道表面背景包进去,导致模型学到的是“图像局部区域的特征”而不是裂缝本身的特征。

我的建议是:如果后续要用YOLOv8这类检测模型,继续用矩形框也可以,但框必须贴合裂缝的方向。不要用一个水平框去包一条45度角的裂缝,那样框面积膨胀很多。更好的方案是在CVAT里把裂缝改成多边形标注,导出的COCO JSON里保留segmentation字段,同时让CVAT自动计算外接bbox。这样检测模型仍然能训练,以后要转实例分割模型也不用重新标。

5.3 类别不平衡与数据增强:4278张不够时的补救方式

4278张原始图片听上去不少,但真正含缺陷的图可能只有几百张。如果你发现crack和gap两类标注数量差距超过5倍,必须处理类别不平衡。常见做法是给少数类提高采样权重,或者在增强时对少数类图片做额外复制。我一般会先统计每类标注数量,再决定用哪种方案,不要一上来就调loss权重。

对于轨道缺陷数据,数据增强要注意场景约束。随机旋转90度对轨道图来说可能是合理的,因为铁轨方向在图片里不一定固定;但上下翻转要谨慎,因为轨面和轨底的纹理差异很大。更有效的是Mosaic增强和Copy-Paste增强:把多个缺陷区域粘贴到正常轨道图上,相当于凭空多出大量正样本。但粘贴时要注意光照和透视一致性,直接硬贴会让模型学到明显的图像拼接痕迹,这在缺陷检测里是个常见翻车点。

6. 验证模型是否真的能用:按缺陷尺寸分层看mAP,再看混淆矩阵

训练完模型后,不要只盯着总mAP。轨道裂缝有很多是小于32x32像素的小目标,总mAP高可能只是大目标拉起来的,小目标实际一塌糊涂。我习惯用pycocotools跑一个分尺寸的评估,按area阈值分成小目标(小于32x32像素)、中目标(32到96)、大目标(大于96),分别看AP。这个操作在YOLOv8的验证输出里没有直接体现,需要写几行脚本手动调。

from pycocotools.coco import COCO from pycocotools.cocoeval import COCOeval coco_gt = COCO("annotations/instances_val.json") coco_dt = coco_gt.loadRes("outputs/predictions.json") evaluator = COCOeval(coco_gt, coco_dt, "bbox") evaluator.evaluate() evaluator.accumulate() evaluator.summarize() # 按尺寸查看AP for area_range, name in [([0, 1024], "small"), ([1024, 9216], "medium"), ([9216, 100000000], "large")]: evaluator.params.areaRng = [area_range] evaluator.evaluate() evaluator.accumulate() evaluator.summarize()

predictions.json是模型推理结果按COCO格式整理后的文件,每项包含image_id、category_id、bbox和score。如果小目标AP明显低于中目标和大目标,说明需要提高输入分辨率,或者用更擅长小目标的模型结构。除了mAP,混淆矩阵也要看。轨道场景里最常见的误检是把裂缝识别成间隙,或者反过来,这两种缺陷在灰度图上确实容易混淆。混淆矩阵能告诉你模型到底错在哪一类上,而不是只知道“精度不够”。

我现在的习惯是:每次训练完,先按缺陷尺寸分层看AP,再画混淆矩阵,最后剪辑一段包含真实轨道视频的测试片段,用模型逐帧跑一遍,观察检测框的稳定性。这三步做完,才敢说这个模型能现场试运行。这个方法救过我很多次,希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询