ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

铁路轨道实例分割数据集实战:YOLOv8-seg训练与缺陷分级

铁路轨道实例分割数据集实战:YOLOv8-seg训练与缺陷分级 简介这份铁路轨道物体实例分割数据集面向铁路安全监控、智能交通与计算机视觉算法开发者提供真实轨道环境下的多类别实例分割样本可用于障碍物检测、事故预警及自动驾驶感知模型的训练与验证。数据集共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.txtimages下放原图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(parentsTrue, exist_okTrue) (dst_root / labels / split).mkdir(parentsTrue, exist_okTrue) # 假设原始数据按 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(parentsTrue, exist_okTrue) for json_file in json_dir.glob(*.json): with open(json_file, r, encodingutf-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[i1] * 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 python3.9 -y conda activate rail_seg # 安装 PyTorch根据 CUDA 版本调整 pip install torch2.0.1 torchvision0.15.2 --index-url https://download.pytorch.org/whl/cu118 # 安装 Ultralytics pip install ultralytics8.1.0 # 验证安装 yolo checksyolo checks会输出环境信息重点看 CUDA 是否可用、版本是否匹配。如果显示 CPU only检查 PyTorch 的 CUDA 版本和系统驱动是否对应。这一步不通过后面训练会直接报错或慢到无法接受。3.2 训练命令与关键参数YOLOv8-seg 的训练入口是yolo segment train核心参数如下yolo segment train \ modelyolov8s-seg.pt \ data./railway_track_seg/data.yaml \ epochs100 \ imgsz640 \ batch16 \ lr00.01 \ lrf0.01 \ patience20 \ device0 \ workers4 \ project./runs/segment \ namerailway_v1逐个说参数含义。model选yolov8s-seg.pt是权衡速度和精度的结果铁路场景目标小但类别少s 版本够用如果裂缝这种细长目标漏检多换yolov8m-seg.pt或yolov8l-seg.pt。imgsz640是默认输入尺寸小目标多的话可以提到 1024但显存占用翻倍。batch16在 8G 显存上比较稳12G 以上可以到 32。lr0初始学习率 0.01 是 YOLO 系列的常规起点lrf最终学习率因子 0.01 表示衰减到初始值的 1%。patience20是早停耐心值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 \ conf0.25 \ iou0.5 \ saveTrue \ save_txtTrue \ project./runs/predict \ nameval_checkconf0.25是置信度阈值低于这个值的检测被过滤。铁路场景里扣件缺失这种目标置信度普遍偏低可以降到 0.15 试试但会引入误检。iou0.5是 NMS 的 IoU 阈值密集小目标场景下可以提到 0.6 或 0.7减少误抑制。save_txtTrue会把预测结果存成 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 被迫调小现象batch16时报 CUDA out of memory降到 8 能跑但训练慢。原因imgsz640时 YOLOv8s-seg 的显存占用大约 6-8G如果图片长宽比差异大letterbox 后实际输入可能更大。另外workers设太高也会占显存。解决优先降imgsz到 512 或 480比降 batch 对训练速度影响小。如果必须保持 640用梯度累积模拟大 batchbatch8加accumulate2效果接近batch16。另外把workers降到 2 或 0减少数据加载进程的显存开销。4.5 验证集指标虚高但实际推理差现象验证集mask mAP50到 0.85但拿现场新图片推理漏检严重。原因训练集和验证集来自同一批数据分布一致但现场图片的光照、角度、背景和训练集差异大。铁路场景尤其明显白天和夜间、晴天和雨天的轨道外观完全不同。解决划分验证集时按场景分层比如白天/夜间各占一半直线/曲线各占一半。如果数据量够留出一个独立测试集完全不参与训练和调参。另外推理时开 TTA测试时增强yolo segment predict加augmentTrue能提升 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, conf0.25, iou0.5, retina_masksTrue) 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_masksTrue让掩码分辨率对齐原图面积计算更准。分级阈值0.001和0.005是经验值需要根据你的数据分布调整——跑一遍训练集看面积占比的直方图取 33% 和 66% 分位数作为阈值更合理。这个技巧的价值在于铁路巡检报告里“扣件异常”这种描述太粗工务段需要知道是“轻微松动”还是“完全缺失”。掩码面积和紧凑度能直接映射到维修优先级轻度的下次巡检再看重度的当天派工。从那以后我每次做完分割训练都会跑一遍分级脚本把结果按等级排序先看重度样本的掩码质量确认没有误分级再交付。希望帮到你。本文还有配套的精品资源点击获取
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进