ARTICLE · INTELLIGENCE

战地情报 · 详情页

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

380张铁路故障图像微调YOLOv8:从数据处理到部署避坑指南

380张铁路故障图像微调YOLOv8:从数据处理到部署避坑指南 简介面向铁路轨道缺陷识别场景的图像分类数据集核心任务是区分轨道『损坏』与『未损坏』两类状态适合正在学习卷积神经网络、目标分类或开展轨道巡检智能化研究的开发者和学生使用。资源约380张已标注图片配套json分类说明文件可直接按标签训练分类模型也可用于复现CNN分类网络或YOLOv5分类流程。压缩包内共387个文件以jpg和jpeg轨道图像为主另有少量webp、png图片、py可视化脚本与json标注文件整体约159.73MB。训练集、验证集、测试集已预先划分同类图片按目录存放运行show脚本可快速预览数据分布便于核对标签与图像质量。已有191人学习下载适合作为铁路设施状态检测、故障识别方向入门实验、课程设计或算法对比研究的现成数据支撑。1. 铁路轨道故障图像识别380张已标注数据够不够训练一个能用的检测模型先说结论380张已标注图像对深度学习目标检测来说是典型的小样本规模如果直接随机初始化训练效果基本不会好但配合预训练权重、合理的数据增强和严格的划分策略这套数据足以产出一个“能试点、能汇报、能暴露问题”的初版检测模型。它解决的是轨交工务巡检里一个很具体的需求用图像识别替代人工巡道自动发现钢轨表面裂纹、扣件缺失、轨枕破损这类故障。适合的人群是铁路工务段的技术人员、做视觉落地的算法工程师以及刚接触目标检测、想用一套真实工业小数据跑通全流程的学生团队。这类数据集的常见现实情况是数量少、类别分布不均、部分标签不干净、拍摄视角和光照条件也不统一。所以做这个题目的第一步不是急着训练而是先把数据集的结构、标注格式和有效目标数摸清楚。后面的章节会按照“读懂数据 → 转换格式与增强 → 模型选型与训练 → 避坑 → 验证部署”的顺序展开每一段都会给可直接复制的命令和脚本。2. 读懂这套数据集标签体系、视觉特征和先验判断2.1 轨道故障图像的标签体系先检查是单类还是多类拿到任何标注数据集第一件事不是看图片而是看标注文件里写了什么。铁路轨道故障图像最常出现的类别大概是这几类钢轨裂纹、轨面剥落、扣件缺失、轨枕裂纹、道床异物。不同团队标注时用的类别名差别很大有的是英文类名如“crack”“missing_fastener”有的是拼音如“guizhen_que”“ganggui_liewen”还有的直接用数字编号。类别名不一致会导致后续转换脚本里映射关系混乱所以第一步要做标签盘点。常见的做法是用一个脚本扫描全部标注文件统计有哪些类别名、每类多少框。如果用的是labelimg导出的VOC XML格式可以按下面的脚本统计import os import xml.etree.ElementTree as ET # 假设所有xml标注文件都在 annotations 目录下 xml_dir annotations class_counter {} for xml_file in os.listdir(xml_dir): if not xml_file.endswith(.xml): continue tree ET.parse(os.path.join(xml_dir, xml_file)) root tree.getroot() for obj in root.findall(object): name obj.find(name).text class_counter[name] class_counter.get(name, 0) 1 print(类别统计:, class_counter)这段代码做的事情很简单遍历每个XML文件取出所有object节点下的name字段按类别名累加。输出的类别统计就是后续所有决策的起点。如果发现单类占比超过90%那么这个数据集本质上是一个单类检测任务如果有多类但其中某类只有十几个框就要做好该类训练不足的心理准备。除了类别名还要看每张图像的尺寸和目标框大小。轨道故障里钢轨裂纹往往只占图像很小一块区域属于小目标检测问题扣件缺失则相对大一些。如果大部分标注框的像素面积小于图像面积的1%后面的训练必须考虑切图或提高输入分辨率否则模型学不到细节。2.2 视觉特征与拍摄条件先知道图像是“什么视角”再定方案轨道故障图像数据集的拍摄视角通常有两类一类是轨检车俯拍视角相机垂直于轨面图像里钢轨居中、扣件在两侧规律排列背景相对干净另一类是手持设备斜拍视角图像里包含道砟、轨枕、杂草背景复杂目标和背景的对比度不稳定。这两种视角对应的图像识别方案差别很大。俯拍视角下钢轨裂纹这类缺陷有明确的方向性和纹理特征适合用检测模型配合灰度增强来做斜拍视角下由于光照角度变化裂纹可能被阴影掩盖直接训练检测模型很容易漏检。拿到数据集后先随机抽二三十张图看一遍把视角和光照情况记录下来。如果全是俯拍视角模型泛化到手持拍摄场景时大概率会掉点如果拍摄距离忽远忽近目标尺度变化大就需要在增强阶段加入随机缩放。这个阶段不需要代码只需要一个动作用系统的图片查看器或写几行OpenCV脚本把图像尺寸分布和平均亮度打印出来对数据集建立直观认知。知道自己拿到的数据是灰度图还是彩色图也很重要灰度图意味着模型只能依赖纹理和形状特征颜色增强手段全部失效。2.3 380张的规模统计算清楚“有效标注密度”再谈训练380张图像只是一个总数真正影响训练效果的是“有效标注密度”——即总共有多少个标注框、平均每张图几个目标。如果380张图里有接近2000个框平均每张5个目标这个数据规模比很多单类小数据集要健康如果380张图总共只有400个框平均每张1个目标那模型学到的东西会很有限。我一般会写一个统计脚本把每张图的框数、框的宽度和高度分布、类别分布一起输出然后根据结果决定数据划分策略。import os import xml.etree.ElementTree as ET import numpy as np xml_dir annotations all_boxes [] # 记录每张图的框数 box_sizes [] # 记录所有框的宽高像素值 for xml_file in os.listdir(xml_dir): tree ET.parse(os.path.join(xml_dir, xml_file)) root tree.getroot() boxes root.findall(object) all_boxes.append(len(boxes)) for obj in boxes: bndbox obj.find(bndbox) xmin int(bndbox.find(xmin).text) ymin int(bndbox.find(ymin).text) xmax int(bndbox.find(xmax).text) ymax int(bndbox.find(ymax).text) w xmax - xmin h ymax - ymin box_sizes.append((w, h)) print(总图像数:, len(all_boxes)) print(总框数:, sum(all_boxes)) print(平均每张框数:, round(np.mean(all_boxes), 2)) print(框宽中位数:, np.median([b[0] for b in box_sizes])) print(框高中位数:, np.median([b[1] for b in box_sizes]))这个脚本会告诉你三个关键信息数据是否集中在少量图上比如有的图标注了20个框有的图1个框都没有目标尺度是中目标还是小目标以及后续是否需要做图像切块。如果框宽中位数只有30到50像素而图像宽度有1280像素那就必须考虑用更大输入分辨率或者先切patch再训练。3. 把标注转成YOLO能吃的格式VOC转YOLO脚本与数据增强边界3.1 labelimg、cvat导出格式与YOLO格式的转换铁路轨道故障数据集的标注格式市面上最常见的三种labelimg导出的VOC XML、CVAT导出的COCO JSON、labelme导出的JSON。YOLOv5和YOLOv8训练需要的格式是每个图像对应一个同名的txt文件每行内容为“类别id 中心点x 中心点y 框宽 框高”坐标都归一化到0到1之间。所以第一步是把不同标注格式统一成YOLO txt。以VOC XML转YOLO为例转换脚本如下import os import xml.etree.ElementTree as ET # 类别映射表根据2.1统计出来的类别名写 class_map {crack: 0, missing_fastener: 1, spalling: 2} xml_dir annotations label_dir labels os.makedirs(label_dir, exist_okTrue) for xml_file in os.listdir(xml_dir): if not xml_file.endswith(.xml): continue tree ET.parse(os.path.join(xml_dir, xml_file)) root tree.getroot() img_w int(root.find(size/width).text) img_h int(root.find(size/height).text) out_lines [] for obj in root.findall(object): name obj.find(name).text if name not in class_map: continue # 跳过未知类别 bndbox obj.find(bndbox) xmin int(bndbox.find(xmin).text) ymin int(bndbox.find(ymin).text) xmax int(bndbox.find(xmax).text) ymax int(bndbox.find(ymax).text) # 归一化到0~1 cx (xmin xmax) / 2.0 / img_w cy (ymin ymax) / 2.0 / img_h bw (xmax - xmin) / img_w bh (ymax - ymin) / img_h out_lines.append(f{class_map[name]} {cx:.6f} {cy:.6f} {bw:.6f} {bh:.6f}) txt_name xml_file.replace(.xml, .txt) with open(os.path.join(label_dir, txt_name), w) as f: f.write(\n.join(out_lines))这段脚本的逻辑是把XML里的绝对坐标转成归一化坐标并映射成类别id。注意class_map必须与第2章统计出来的类别名完全一致否则会出现静默跳框的问题。转换完成后随机抽5个txt文件打开看数字是否都在0到1之间再用OpenCV把框画回图上人工确认一次这一步能过滤掉大量坐标写反、除零这类低级错误。3.2 数据增强组合别把小样本“增”成假样本380张图直接训练过拟合几乎是必然的。数据增强是小样本检测的救命稻草但不能盲目叠加。轨道故障图像里有两个特点要特别注意一是裂纹、剥离这类缺陷有方向性镜像翻转后缺陷形态会改变但语义仍然成立二是扣件缺失这类故障与空间位置关系不大水平翻转对模型理解影响很小。所以基础增强组合建议是水平翻转、随机亮度和对比度扰动、随机尺度缩放、小角度旋转。用albumentations库实现增强的常见做法是这样import albumentations as A import cv2 # 定义增强pipeline概率按经验设置 transform A.Compose([ A.HorizontalFlip(p0.5), A.RandomBrightnessContrast(brightness_limit0.15, contrast_limit0.15, p0.5), A.RandomScale(scale_limit0.2, p0.3), A.Rotate(limit15, border_modecv2.BORDER_CONSTANT, value0, p0.3), A.GaussNoise(var_limit(10.0, 30.0), p0.2), ], bbox_paramsA.BboxParams(formatyolo, label_fields[class_labels])) # 读取原图和对应txt标签后调用 # augmented transform(imageimg, bboxesbboxes, class_labelslabels)参数说明brightness_limit0.15对应亮度变化范围超出这个范围会让钢轨表面反光失真Rotate的limit设为15度是因为轨道故障图像拍摄时相机没有大的倾斜旋转过大会让几何关系失真border_mode用BORDER_CONSTANT填充黑色而不是镜像填充因为镜像填充会生成不存在的轨道结构延续。一个需要留意的边界数据增强不要对每一张图都用同一个随机种子也不要把增强后的图像当作“新采集的数据”去评估模型。增强图像只能参与训练验证集和测试集必须用原始图像否则指标会虚高。3.3 扩充前先看标注是否干净初筛“脏框”的脚本标注数据里最常见的脏框情况有三种坐标越界、框面积过小、框内主体不明确。坐标越界多数是标注时鼠标拖出图像边界导致的框面积过小往往是误点的残留背景框则是把没有目标的区域也标了一个框。这些问题不处理训练时模型会被反复教导“这个位置有目标”推理时就会在原位置输出一堆错误预测。写一个快速检查脚本import os label_dir labels for txt_file in os.listdir(label_dir): with open(os.path.join(label_dir, txt_file)) as f: lines f.readlines() for i, line in enumerate(lines): parts line.strip().split() if len(parts) ! 5: print(f{txt_file} 第{i1}行格式错误: {line.strip()}) continue _, cx, cy, bw, bh parts cx, cy, bw, bh float(cx), float(cy), float(bw), float(bh) if cx 0 or cy 0 or bw 0 or bh 0: print(f{txt_file} 第{i1}行坐标非法: {line.strip()}) if cx 1 or cy 1 or bw 1 or bh 1: print(f{txt_file} 第{i1}行越界: {line.strip()}) # 归一化坐标下框宽小于0.005表示框太小可能标注有误 if bw 0.005: print(f{txt_file} 第{i1}行框过小: {line.strip()})这类脚本能快速列出问题行但“是否真的是脏框”需要回到原图上人工确认。比如钢轨裂纹的框可以非常细长宽度可能小于0.005但长度很长这种是合法标注但如果是宽高都小于0.005的点状框基本可以判定是误标。处理方式是打开原图删除或修正该框而不是直接删掉整个文件。4. 微调YOLOv8跑通380张的故障检测模型选型与关键参数4.1 为什么YOLOv8是小样本轨道故障检测的稳妥起点选定模型之前先明确一个事实380张数据不够从头训练任何深度检测网络必须借助在COCO或ImageNet上预训练过的权重做迁移学习。在迁移学习的框架下选模型主要看三点预训练生态是否成熟、小目标检测能力是否够用、算力需求是否可控。YOLOv8n和YOLOv8s在这三点上比较均衡而且Ultralytics提供的训练接口把数据划分、增强、指标输出全部封装好了对工程落地很友好。和两阶段检测器比如Faster R-CNN相比YOLOv8在钢轨裂纹这类目标上的优势是anchor-free设计不需要预设与数据匹配的anchor尺寸。小样本场景下标注框的形状分布往往不稳定anchor-based模型容易出现anchor分配不均anchor-free的YOLOv8直接把目标中心点作为正样本对细长裂纹和异形剥落更宽容。与YOLOv5相比v8的C2f结构和Decoupled Head在小数据集上收敛更快val指标涨得更稳。4.2 用YOLOv8训练自己的数据集最小可复现命令与yaml配置先把数据按YOLO目录结构放好常见做法是新建一个数据集根目录下面分images和labels两个文件夹各自再分train和val子集。图像和txt标签必须同名同前缀。数据划分可以用脚本也可以用Ultralytics自带的工具。手工划分时注意不要用纯随机要保证同一段轨道连续拍摄的相似帧不要同时落在train和val里。简单做法是先把图像文件名列表排序按序列间隔抽样到val。data.yaml这样写path: ./rail_dataset # 数据集根目录 train: images/train # train图像相对路径 val: images/val # val图像相对路径 nc: 3 # 类别数 names: [crack, missing_fastener, spalling] # 与class_map一致然后执行训练命令yolo detect train \ modelyolov8n.pt \ datarail_dataset/data.yaml \ epochs100 \ imgsz640 \ batch8 \ lr00.01 \ patience20参数说明model指定预训练权重yolov8n.pt是在COCO上预训练过的nano版本imgsz640是输入分辨率如果标注框普遍偏小可以试imgsz1280但显存消耗会明显增加batch8适合单张消费级显卡显存12GB以上可调到16lr00.01是YOLOv8默认初始学习率小数据集不需要调高调高反而容易在开头几个epoch就把预训练权重冲坏patience20是early stopping的容忍轮数val指标连续20轮不涨就停。训练过程中不要每轮都去看训练集loss降到多少重点看val指标。小数据集的典型曲线是训练loss一路下降val的mAP50在某个点开始震荡或下滑这就是过拟合信号。如果epochs100跑不到50轮就触发early stopping不需要强行加epochs。4.3 训练过程侧重点盯住哪几个指标再决定停不停小样本训练时mAP50和mAP50-95都要看但侧重点不同。mAP50考察的是“框位置大致对即可”适合评估扣件缺失这类目标mAP50-95要求预测框与真实框高度重合钢轨裂纹这种长条形目标是很难在这个指标上拿高分的所以不必追求mAP50-95涨到0.8以上。训练结束时先看每个类别的precision和recall。对于轨道故障识别漏检的代价高于误报漏掉一个裂纹可能导致后续安全隐患误报一个裂纹巡道工复核一下就能排除。所以recall的优先级高于precision。如果某类recall低于0.5优先考虑做该类别的针对性增强或单独训练一个二分类模型而不是全局调低置信度阈值。训练完导出的best.pt是val指标最好的权重但val指标好不代表现场好用。建议再用一组完全没参与训练的历史巡检图像做一次回放测试确认模型在真实光照和视角下的表现。第四章先跑通第五、六章会把“val指标虚高”和“现场漏检”这两个问题展开讲。5. 这些“坑”不绕开380张数据会成为你熬夜的起点实测踩坑记录5.1 类别框数极不均衡一类覆盖大半、另一类只有二十个现象训练完成后数量多的类别mAP50到了0.85数量少的类别只有0.2左右看起来像是模型“对某一类完全没学会”。原因380张数据里各类别框数差距过大是常态。比如钢轨裂纹有600个框扣件缺失只有25个框模型在训练时绝大多数正样本来自裂纹扣件缺失学到几次就会在验证时被噪声淹没。解决先按第2章的统计脚本算出每类框数对低频类别单独做离线增强把该类别的样本数量提升到与中位类别接近的水平。如果增强后仍然不足50个框可以单独训练该类别的专用检测模型而不是硬塞进多类模型里。5.2 钢轨细小裂纹被误检成背景纹理现象模型把钢轨表面的反光、划痕甚至道砟纹理识别成裂纹precision很低同时真实裂纹的召回率也不高现场测试漏检明显。原因裂纹是典型的线状小目标在低分辨率下与背景纹理的差异极不显著。输入分辨率不足时模型只能学到“暗色细线”这种粗糙特征无法区分裂纹和阴影边界。解决优先提高输入分辨率从640切到1280如果显存支持可以按裂纹所在区域切patch训练让模型在局部看到更清晰的纹理细节。此外训练阶段加入灰度对比度增强削弱颜色信息对纹理学习的干扰。5.3 用错val划分导致分数虚高连续帧泄漏现象训练过程mAP50稳定在0.9以上换到一段新拍摄的视频回放测试检测结果一塌糊涂漏检和误报同时出现。原因轨道巡检通常是连续拍摄同一段钢轨会在几十张连续图像里反复出现。如果train和val用的是纯随机划分模型实际上已经“见过”val的图像内容val指标反映的是记忆能力而非泛化能力。解决划分数据集时按“拍摄时间线路段”分组。最简单做法是把图像文件名按前缀分组同一个拍摄批次整组进入train或val不做逐张随机。380张数据量小这个操作在复制文件时手动完成也花不了十分钟但能避免后续评估失真。5.4 标注框过紧或漏标导致训练震荡现象训练时mAP50上升后突然掉下来然后下一轮又涨回去曲线像锯齿且反复出现。原因标注框过紧贴合目标但留白太少会让模型在回归边界时难以收敛漏标目标则会被当成背景同一区域一会儿被要求预测目标、一会儿被要求抑制梯度方向互相打架。解决使用标注规范要求标注框外扩3到5像素给模型留出回归空间。漏标问题用第3.3节的脚本配合人工复核过滤一遍至少把面积大、视觉明显的漏标目标补上。这个坑没有捷径只能回到数据上花时间。5.5 直接拿小数据集从头训练自建网络结果差到怀疑人生现象不用预训练模型自己搭一个ResNet或自定义CNN在380张图上从头训练验证集accuracy只有六成且训练过程极不稳定。原因小数据加随机初始化模型一开始就落在参数空间的糟糕区域梯度信号不足以支撑深层网络收敛。故障图像又是高相似度工业场景类间差异小从头训练更难学出有效特征。解决用ImageNet预训练的ResNet或EfficientNet做特征提取器冻结前几层只微调后几层如果还需要快速出结果直接提取预训练模型特征后接线性SVM这种传统方案在小样本上往往比深度网络更稳。这不是退步而是小样本场景下务实的选择。6. 验证与部署技巧用一张没见过的图测出模型的真实“可用度”模型训练完不要急着写报告。先找一张没有出现在train和val里的现场巡检图最好是不同线路段、不同光照条件下的图用best.pt跑一遍推理把置信度阈值从0.25逐步调到0.5观察框的变化。小样本模型通常在0.3到0.4之间表现最稳阈值调太低会出现大量背景误报调太高则细裂纹全部漏掉。这一步能直接告诉你模型在真实场景里的“可用区间”到底有多宽。更系统的验证办法是随机抽20张未标注图像让模型出预测框再让有现场经验的工务人员交叉核对。这里有一个容易被忽视的点模型输出没有框不代表该位置没有故障只能说明模型置信度不够高。对轨道故障这类安全敏感场景我倾向于把recall优先的权重导出为“复核模式”低置信度框也保留并标注为待复核交给人工二次确认。部署方面常见路径是把best.pt转成ONNX后用OpenCV或ONNXRuntime推理推理时保持训练时的输入尺寸和归一化方式一致否则框位会偏移。如果现场只能用CPU优先切到YOLOv8n而不是s推理速度差距在边缘设备上非常明显。最后说一个我自己的习惯拿到任何小规模标注数据集第一周全部时间花在数据盘点、格式清洗和划分策略上绝不动模型。这个习惯帮我避开了大多数“训练跑完才发现数据有硬伤”的翻车现场。380张图不多但如果框干净、划分合理、增强克制它足够支撑一个能演示、能迭代、能说清楚局限性的初版故障识别系统。希望帮到你。本文还有配套的精品资源点击获取
RELATED READING

延伸阅读

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