☰
铁路轨道实例分割数据集实战:YOLOv8-seg训练与缺陷分级
2026/10/11 17:44:09 网站建设 项目流程

简介:这份铁路轨道物体实例分割数据集面向铁路安全监控、智能交通与计算机视觉算法开发者,提供真实轨道环境下的多类别实例分割样本,可用于障碍物检测、事故预警及自动驾驶感知模型的训练与验证。数据集共928张图片,其中训练集873张、验证集55张,覆盖Cylinder、animal、human、train、tree、vehicle六类目标,兼顾自然与人造物体,场景多样性有助于提升模型泛化能力。压缩包内共1858个文件,以928个jpg图像与928个txt标注文件为主,另含1个yaml配置和1个docx说明文档,整体约58.15MB,标注采用YOLO格式的多边形实例分割,兼容主流深度学习框架,便于快速加载训练。目前已有122人学习下载。对于从事铁路安全监控、基础设施巡检或实例分割算法研究的读者,可直接获得一套标注精细、任务适配性强的行业数据集,用于模型训练、算法对比与工程落地验证。

1. 铁路轨道实例分割数据集:从“能跑”到“能用”的第一道坎

铁路轨道场景的视觉检测有个很反直觉的地方:轨道本身是两条无限延伸的平行线,但真正要检测的目标——扣件、道钉、绝缘接头、轨道板裂缝——全是小目标且密集排列。用普通目标检测框去标,框和框之间重叠得一塌糊涂,NMS 一压就漏检。这就是为什么铁路巡检场景里,实例分割比纯检测框更实用:它输出的是像素级掩码,扣件缺失、断裂这种需要看形状的缺陷,掩码比边界框能多带一倍的信息量。

这份铁路轨道物体实例分割数据集,就是冲着这个场景来的。它按实例分割的标注格式组织,覆盖轨道场景下的多类目标,可以直接喂给 YOLOv8-seg、YOLOv11-seg 这类实例分割模型做训练和验证。适合两类人:一是做铁路巡检、工务检测的算法工程师,手里有模型但缺场景数据;二是想拿一个真实工业场景练实例分割全流程的开发者,比 COCO 那种通用数据集更贴近落地。下面从数据组织、格式转换、训练配置到踩坑排查,按能复现的路径走一遍。

2. 数据集结构与标注格式:先搞清楚你拿到的是什么

2.1 目录组织与文件构成

拿到压缩包解压后,常见的组织方式是这样:

railway_track_seg/ ├── images/ │ ├── train/ │ └── val/ ├── labels/ │ ├── train/ │ └── val/ ├── data.yaml └── classes.txt

images下放原图,labels下放同名.txt标注文件,这是 YOLO 系列实例分割的标准布局。data.yaml定义训练/验证路径和类别名,classes.txt是类别列表的纯文本备份。如果你拿到的包结构不同,比如图片和标注混在一起,或者标注是 COCO JSON 格式,先别急着改代码,用下面这个脚本把结构对齐。

import os import shutil from pathlib import Path def reorganize_dataset(src_root, dst_root): """ 把散落的图片和标注整理成 YOLO 分割标准结构 src_root: 原始数据根目录 dst_root: 整理后的输出目录 """ src_root = Path(src_root) dst_root = Path(dst_root) # 建立目标目录 for split in ['train', 'val']: (dst_root / 'images' / split).mkdir(parents=True, exist_ok=True) (dst_root / 'labels' / split).mkdir(parents=True, exist_ok=True) # 假设原始数据按 8:2 划分,这里按文件名前缀或随机划分 all_images = list(src_root.glob('*.jpg')) + list(src_root.glob('*.png')) split_idx = int(len(all_images) * 0.8) for i, img_path in enumerate(all_images): split = 'train' if i < split_idx else 'val' # 复制图片 shutil.copy(img_path, dst_root / 'images' / split / img_path.name) # 复制同名标注 label_path = img_path.with_suffix('.txt') if label_path.exists(): shutil.copy(label_path, dst_root / 'labels' / split / label_path.name) else: print(f"警告:{img_path.name} 没有对应标注文件") if __name__ == '__main__': reorganize_dataset('./raw_data', './railway_track_seg')

这段脚本做三件事:建目录、按 8:2 切分、图片和标注配对复制。参数上,src_root指向你解压后的原始目录,dst_root是整理后的输出。注意标注文件必须和图片同名同后缀(.txt),如果原始标注是 JSON 或 XML,需要先转成 YOLO 分割格式,转换逻辑在下一节展开。

2.2 YOLO 分割标注格式拆解

YOLO 实例分割的标注行格式是:

<class_id> <x1> <y1> <x2> <y2> ... <xn> <yn>

所有坐标是归一化到 0-1 之间的多边形顶点,按顺时针或逆时针顺序排列。一行代表一个实例,一张图有多少个目标就有多少行。和检测框格式的区别在于:检测框只有 5 个值(类别 + 中心点 + 宽高),分割是 1 + 2n 个值,n 是多边形顶点数。

铁路轨道场景里,扣件这种小目标通常用 4-8 个点就能勾出轮廓,轨道板裂缝可能需要 20 个点以上。顶点数不固定是正常的,YOLO 训练时会统一重采样到固定点数(默认 32 或 64)。如果你自己标注,常见做法是用 LabelMe 或 CVAT 画多边形,导出后转 YOLO 格式。

import json import os from pathlib import Path def labelme_to_yolo_seg(json_dir, output_dir, class_map): """ 把 LabelMe 的 JSON 多边形标注转成 YOLO 分割格式 json_dir: LabelMe JSON 文件目录 output_dir: 输出 txt 目录 class_map: {'类别名': class_id} 映射字典 """ json_dir = Path(json_dir) output_dir = Path(output_dir) output_dir.mkdir(parents=True, exist_ok=True) for json_file in json_dir.glob('*.json'): with open(json_file, 'r', encoding='utf-8') as f: data = json.load(f) img_w = data['imageWidth'] img_h = data['imageHeight'] lines = [] for shape in data['shapes']: label = shape['label'] if label not in class_map: continue class_id = class_map[label] points = shape['points'] # 归一化坐标 normalized = [] for x, y in points: normalized.append(str(round(x / img_w, 6))) normalized.append(str(round(y / img_h, 6))) lines.append(f"{class_id} " + " ".join(normalized)) # 写入同名 txt out_file = output_dir / (json_file.stem + '.txt') with open(out_file, 'w') as f: f.write("\n".join(lines)) # 使用示例 class_map = {'扣件': 0, '道钉': 1, '绝缘接头': 2, '裂缝': 3} labelme_to_yolo_seg('./labelme_json', './labels/train', class_map)

关键参数是class_map,它把标注时的中文类别名映射成训练用的数字 ID。顺序要和data.yaml里的names列表严格一致,否则训练出来的模型会把类别搞混。归一化用round(x / img_w, 6)保留 6 位小数,精度足够,文件也不会太大。

2.3 data.yaml 配置与类别对齐

data.yaml是训练入口,内容通常长这样:

path: ./railway_track_seg train: images/train val: images/val nc: 4 names: 0: 扣件 1: 道钉 2: 绝缘接头 3: 裂缝

nc是类别数,names是 ID 到名称的映射。这里有个容易翻车的地方:如果你用 LabelMe 转格式时class_map写的是{'扣件': 0, '道钉': 1},但data.yaml里names顺序写反了,训练 loss 会正常下降,但推理时类别全错。验证方法很简单:训练前用脚本抽查几张图的标注,把类别 ID 画出来看一眼。

import cv2 import numpy as np def visualize_seg_label(img_path, label_path, class_names): """把分割标注画到原图上,检查类别和轮廓是否正确""" img = cv2.imread(img_path) h, w = img.shape[:2] with open(label_path, 'r') as f: lines = f.readlines() colors = [(0,255,0), (255,0,0), (0,0,255), (255,255,0)] for line in lines: parts = line.strip().split() class_id = int(parts[0]) coords = list(map(float, parts[1:])) points = [] for i in range(0, len(coords), 2): x = int(coords[i] * w) y = int(coords[i+1] * h) points.append([x, y]) pts = np.array(points, np.int32) cv2.polylines(img, [pts], True, colors[class_id % len(colors)], 2) cv2.putText(img, class_names[class_id], tuple(pts[0]), cv2.FONT_HERSHEY_SIMPLEX, 0.6, colors[class_id % len(colors)], 2) cv2.imwrite('check_vis.jpg', img) print(f"已保存可视化结果,共 {len(lines)} 个实例") visualize_seg_label('./images/train/001.jpg', './labels/train/001.txt', ['扣件', '道钉', '绝缘接头', '裂缝'])

跑一遍这个脚本,打开check_vis.jpg,如果轮廓贴合目标、类别文字正确,说明标注和配置对齐了。这一步花两分钟,能省掉后面几小时的排查。

3. YOLOv8-seg 训练配置:从预训练权重到收敛

3.1 环境准备与依赖版本

实例分割训练对版本比较敏感,常见做法是锁死一套能跑通的组合。我一般用 Python 3.9 + PyTorch 2.0 + Ultralytics 8.1.x,这个组合在铁路数据集这种中小规模场景下比较稳。

# 创建虚拟环境 conda create -n rail_seg python=3.9 -y conda activate rail_seg # 安装 PyTorch(根据 CUDA 版本调整) pip install torch==2.0.1 torchvision==0.15.2 --index-url https://download.pytorch.org/whl/cu118 # 安装 Ultralytics pip install ultralytics==8.1.0 # 验证安装 yolo checks

yolo checks会输出环境信息,重点看 CUDA 是否可用、版本是否匹配。如果显示 CPU only,检查 PyTorch 的 CUDA 版本和系统驱动是否对应。这一步不通过,后面训练会直接报错或慢到无法接受。

3.2 训练命令与关键参数

YOLOv8-seg 的训练入口是yolo segment train,核心参数如下:

yolo segment train \ model=yolov8s-seg.pt \ data=./railway_track_seg/data.yaml \ epochs=100 \ imgsz=640 \ batch=16 \ lr0=0.01 \ lrf=0.01 \ patience=20 \ device=0 \ workers=4 \ project=./runs/segment \ name=railway_v1

逐个说参数含义。model选yolov8s-seg.pt是权衡速度和精度的结果,铁路场景目标小但类别少,s 版本够用;如果裂缝这种细长目标漏检多,换yolov8m-seg.pt或yolov8l-seg.pt。imgsz=640是默认输入尺寸,小目标多的话可以提到 1024,但显存占用翻倍。batch=16在 8G 显存上比较稳,12G 以上可以到 32。lr0初始学习率 0.01 是 YOLO 系列的常规起点,lrf最终学习率因子 0.01 表示衰减到初始值的 1%。patience=20是早停耐心值,20 轮验证指标不提升就停,避免过拟合。

训练过程中重点看三个指标:box_loss、seg_loss、mask mAP50。seg_loss是分割掩码的损失,比box_loss更能反映实例分割质量。如果seg_loss下降但mask mAP不涨,通常是标注轮廓质量有问题,回到第 2 章的可视化脚本抽查。

3.3 推理验证与结果解读

训练完成后,用验证集跑一遍推理:

yolo segment predict \ model=./runs/segment/railway_v1/weights/best.pt \ source=./railway_track_seg/images/val \ conf=0.25 \ iou=0.5 \ save=True \ save_txt=True \ project=./runs/predict \ name=val_check

conf=0.25是置信度阈值,低于这个值的检测被过滤。铁路场景里扣件缺失这种目标,置信度普遍偏低,可以降到 0.15 试试,但会引入误检。iou=0.5是 NMS 的 IoU 阈值,密集小目标场景下可以提到 0.6 或 0.7,减少误抑制。save_txt=True会把预测结果存成 YOLO 格式,方便和真值对比算指标。

结果解读看runs/predict/val_check下的可视化图。重点检查三类问题:漏检(真值有但预测没有)、误检(预测有但真值没有)、掩码偏移(框对了但轮廓偏了)。掩码偏移通常是标注轮廓和实际边缘差了几个像素,小目标上影响不大,大目标上需要重新标注。

4. 避坑与排查:铁路轨道分割的五个血泪经验

4.1 类别不均衡导致小类漏检

现象:扣件和道钉的 mAP 正常,但绝缘接头和裂缝的 mAP 接近 0。

原因:铁路场景里扣件数量远多于绝缘接头,裂缝更少。YOLO 默认的损失函数对类别不均衡没有特殊处理,小类样本被大类淹没。

解决:两个方向。一是数据层面,对小类样本做过采样,或者用 mosaic、copy-paste 增强小类实例。二是损失层面,YOLOv8 支持通过cls参数调整分类损失的权重,但更直接的做法是在data.yaml里复制小类图片到训练集,简单粗暴但有效。我一般先把小类图片复制 3-5 倍,再跑一轮看 mAP 变化。

4.2 标注轮廓自相交导致训练崩溃

现象:训练几个 epoch 后 loss 突然变成 NaN,或者报polygon area is zero错误。

原因:LabelMe 画多边形时手抖,轮廓线交叉了,或者顶点顺序乱了。YOLO 在计算掩码损失时对自相交多边形处理不好,直接产生无效梯度。

解决:训练前用脚本过滤掉自相交的标注。常见做法是计算多边形面积,面积接近 0 或为负的直接剔除。更稳妥的是用shapely库检查多边形有效性:

from shapely.geometry import Polygon def is_valid_polygon(coords): """coords: [(x1,y1), (x2,y2), ...] 归一化坐标""" if len(coords) < 3: return False poly = Polygon(coords) return poly.is_valid and poly.area > 1e-6

把无效标注对应的图片和标注一起从训练集移除,或者重新标注。这一步在数据清洗阶段做,别等到训练时报错才回头查。

4.3 图片尺寸不一致导致掩码错位

现象:训练时 loss 正常,但推理可视化里掩码整体偏移,框和轮廓对不上。

原因:原始图片尺寸不一,有的 1920x1080,有的 1280x720,但标注归一化时用的尺寸和实际训练时 letterbox 后的尺寸不一致。YOLO 训练时会统一 resize 到imgsz,如果标注归一化基准错了,resize 后坐标全偏。

解决:确保标注归一化时用的img_w和img_h是原图实际尺寸,不是 resize 后的。用第 2 章的 LabelMe 转换脚本时,data['imageWidth']和data['imageHeight']就是原图尺寸,直接用没问题。如果手工改过标注,用 OpenCV 读图确认尺寸:

import cv2 img = cv2.imread('001.jpg') print(img.shape) # (height, width, channels)

4.4 显存不足导致 batch 被迫调小

现象:batch=16时报 CUDA out of memory,降到 8 能跑但训练慢。

原因:imgsz=640时 YOLOv8s-seg 的显存占用大约 6-8G,如果图片长宽比差异大,letterbox 后实际输入可能更大。另外workers设太高也会占显存。

解决:优先降imgsz到 512 或 480,比降 batch 对训练速度影响小。如果必须保持 640,用梯度累积模拟大 batch:batch=8加accumulate=2,效果接近batch=16。另外把workers降到 2 或 0,减少数据加载进程的显存开销。

4.5 验证集指标虚高但实际推理差

现象:验证集mask mAP50到 0.85,但拿现场新图片推理,漏检严重。

原因:训练集和验证集来自同一批数据,分布一致,但现场图片的光照、角度、背景和训练集差异大。铁路场景尤其明显,白天和夜间、晴天和雨天的轨道外观完全不同。

解决:划分验证集时按场景分层,比如白天/夜间各占一半,直线/曲线各占一半。如果数据量够,留出一个独立测试集,完全不参与训练和调参。另外推理时开 TTA(测试时增强),yolo segment predict加augment=True,能提升 2-3 个点,但速度慢一倍。

5. 进阶技巧:用掩码面积做缺陷分级

实例分割比检测多出来的信息是掩码面积。铁路场景里,扣件缺失和扣件断裂的掩码面积差异明显,裂缝的长度和宽度也能从掩码里算出来。这一节说一个具体技巧:用预测掩码的面积和形状因子做缺陷分级,把二分类的“有缺陷/无缺陷”升级成“轻度/中度/重度”。

先跑推理拿到掩码,然后对每个实例算两个指标:面积占比和紧凑度。面积占比是掩码像素数除以图片总像素数,紧凑度是4π * 面积 / 周长²,圆形接近 1,细长裂缝接近 0。

import cv2 import numpy as np from ultralytics import YOLO def defect_grading(model_path, img_path): """ 对单张图片做实例分割并分级 返回每个实例的类别、面积占比、紧凑度、等级 """ model = YOLO(model_path) results = model(img_path, conf=0.25, iou=0.5, retina_masks=True) img = cv2.imread(img_path) h, w = img.shape[:2] total_pixels = h * w grades = [] for r in results: if r.masks is None: continue masks = r.masks.data.cpu().numpy() # (n, h, w) classes = r.boxes.cls.cpu().numpy().astype(int) for i, mask in enumerate(masks): mask_bin = (mask > 0.5).astype(np.uint8) area = mask_bin.sum() area_ratio = area / total_pixels # 计算周长 contours, _ = cv2.findContours(mask_bin, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) if not contours: continue perimeter = cv2.arcLength(contours[0], True) # 紧凑度 compactness = (4 * np.pi * area) / (perimeter ** 2 + 1e-6) # 分级规则:面积占比和紧凑度组合 if area_ratio < 0.001: grade = '轻度' elif area_ratio < 0.005: grade = '中度' else: grade = '重度' # 裂缝类目标用紧凑度修正 if classes[i] == 3 and compactness < 0.2: grade = '重度' if area_ratio > 0.002 else '中度' grades.append({ 'class': classes[i], 'area_ratio': round(area_ratio, 6), 'compactness': round(compactness, 4), 'grade': grade }) return grades # 使用 results = defect_grading('./runs/segment/railway_v1/weights/best.pt', './test_images/001.jpg') for r in results: print(r)

这段代码的逻辑是:先拿到每个实例的掩码,二值化后算面积和周长,再用面积占比和紧凑度两个维度做分级。参数上,retina_masks=True让掩码分辨率对齐原图,面积计算更准。分级阈值0.001和0.005是经验值,需要根据你的数据分布调整——跑一遍训练集,看面积占比的直方图,取 33% 和 66% 分位数作为阈值更合理。

这个技巧的价值在于:铁路巡检报告里,“扣件异常”这种描述太粗,工务段需要知道是“轻微松动”还是“完全缺失”。掩码面积和紧凑度能直接映射到维修优先级,轻度的下次巡检再看,重度的当天派工。从那以后我每次做完分割训练,都会跑一遍分级脚本,把结果按等级排序,先看重度样本的掩码质量,确认没有误分级再交付。希望帮到你。

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

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

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

立即咨询