ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

火车轨道检测数据集实战:COCO标注解析与YOLO训练全流程

火车轨道检测数据集实战:COCO标注解析与YOLO训练全流程 简介这份火车轨道检测数据集面向计算机视觉开发者、轨道交通智能化研究者及深度学习实践者用于训练和评估轨道与障碍物目标检测模型可支撑列车运行安全监测、轨道巡检等场景。资源包共2000个文件以1995张jpg图像为主体另含3个json标注文件与2个txt说明文件压缩包约471.42MB图像采用COCO格式标注共3900张原始图片便于直接接入主流检测框架进行训练与验证。数据集可识别火车轨道和障碍物识别准确率达93.7%标注规范、样本量充足适合作为模型训练、迁移学习与精度对比的可靠数据基础。目前已有2063人学习下载具备一定社区验证度。读者可据此快速搭建轨道检测实验流程完成数据加载、模型微调与效果评估并借助标注文件理解COCO结构为后续优化与部署提供参考。1. 火车轨道检测数据集3900 张 COCO 标注图能跑出什么结果如果你正在做轨道巡检、障碍物侵限预警或者铁路场景的视觉算法验证大概率会遇到同一个问题公开数据集要么是道路场景要么是航拍铁路真正贴着轨面、带障碍物标注的 COCO 格式数据少得可怜。这份火车轨道检测数据集就是冲着这个缺口来的——3900 张原始图片全部按 COCO 标准打标覆盖火车轨道本体和轨道上的障碍物两类目标官方给出的识别准确率是 93.7%。它适合三类人想快速验证检测模型在铁路场景下表现的算法工程师、需要 COCO 格式数据做迁移学习的学生、以及做轨道安全监测产品原型的开发者。文件名里那串T111619_jpg.rf.xxx的哈希后缀说明图片经过统一重命名和去重处理直接丢进训练管线不用再做清洗。下面我从数据组织、格式转换、训练配置到踩坑排查把这份资源完整拆一遍。2. COCO 标注结构拆解从 json 字段到轨道障碍物类别映射拿到一份 COCO 数据集第一件事不是急着训练而是把标注文件读明白。这份数据的核心是一个annotations目录下的 json 文件里面用images、annotations、categories三个顶层字段描述全部信息。轨道检测场景和通用 COCO 的区别在于类别数极少——通常只有rail轨道和obstacle障碍物两类这意味着你在配置模型时要把num_classes从默认的 80 改成 2否则分类头会多出一堆无用参数收敛变慢还容易过拟合。2.1 images 与 annotations 字段的对应关系images数组里每条记录包含id、file_name、width、height。file_name就是你在项目正文里看到的那种T111619_jpg.rf.3c539f85f90ad78b07174836877c09e8.jpg哈希后缀是 Roboflow 类工具导出时的标准命名保证不同批次图片不会重名。annotations数组里每条记录通过image_id关联到具体图片通过category_id关联到类别bbox字段是[x, y, width, height]格式的像素坐标segmentation如果是多边形标注还会带分割点。轨道检测里障碍物往往是不规则形状如果标注时用了多边形segmentation就有值如果只拉框那segmentation为空bbox就是唯一几何信息。用下面这段脚本可以快速统计每类目标的数量和图片分布判断数据是否均衡import json from collections import Counter with open(annotations/instances_train.json, r) as f: data json.load(f) # 类别映射category_id - name cat_map {c[id]: c[name] for c in data[categories]} print(类别列表:, cat_map) # 统计每类标注数量 cat_counter Counter(ann[category_id] for ann in data[annotations]) for cid, cnt in cat_counter.items(): print(f{cat_map[cid]}: {cnt} 个标注) # 统计每张图的目标数分布 img_obj Counter(ann[image_id] for ann in data[annotations]) print(图片总数:, len(data[images])) print(有标注的图片数:, len(img_obj)) print(单图最多目标数:, max(img_obj.values()) if img_obj else 0)这段代码先建立category_id到类别名的映射然后分别统计类别维度和图片维度的分布。如果rail的标注数远大于obstacle说明障碍物样本偏少训练时要么做重采样要么在 loss 里给障碍物类更高权重。单图最多目标数这个指标影响你设max_det参数——如果一张图里轨道加障碍物最多 15 个目标那推理时max_det设 30 就够设太大反而拖慢后处理。2.2 类别 id 不连续时的处理COCO 标准里category_id从 1 开始但有些导出工具会跳号比如只有 1 和 3 两个类别。很多训练框架默认类别 id 从 0 连续排列直接读会报索引越界。稳妥做法是读入后重映射# 建立旧 id 到新连续 id 的映射 old_ids sorted(cat_map.keys()) id_remap {old: new for new, old in enumerate(old_ids)} for ann in data[annotations]: ann[category_id] id_remap[ann[category_id]] for c in data[categories]: c[id] id_remap[c[id]] # 保存重映射后的 json with open(annotations/instances_train_remap.json, w) as f: json.dump(data, f)重映射之后类别 id 变成 0 和 1和大多数检测框架的num_classes2对齐。注意验证集和测试集要用同一套映射表否则评估时类别对不上mAP 会直接掉到 0。2.3 图片路径与 json 的目录约定COCO 数据集常见的目录结构是images/放图annotations/放 jsonjson 里的file_name只写文件名不带路径。但有些导出包会把图片按train/val/test分到子目录json 里却还是裸文件名。训练脚本如果直接拼images/file_name会找不到文件。我一般先跑一遍路径校验import os missing [] for img in data[images]: # 根据实际目录结构调整 path os.path.join(images, img[file_name]) if not os.path.exists(path): missing.append(img[file_name]) print(f缺失图片数: {len(missing)}) if missing: print(前 5 个缺失:, missing[:5])如果缺失数不为零先确认图片是不是在images/train/这类子目录下改os.path.join的第一个参数即可。这一步花两分钟能省掉训练启动后报FileNotFoundError再回头查的麻烦。3. 从 COCO 到 YOLO 格式转换脚本与四个边界坑COCO 格式适合做数据管理和评估但真正训练时很多人用 YOLO 系列因为配置简单、速度快。把 COCO 转成 YOLO 的 txt 格式是这份数据集落地时绕不开的一步。转换的核心是把[x, y, w, h]的绝对像素坐标转成[x_center, y_center, w, h]的相对坐标同时把类别 id 写成行首。听起来简单但实际转的时候有四类坑会让你的标注框整体偏移或者消失。3.1 坐标归一化的正确写法YOLO 要求所有坐标除以图片宽高归一化到 0~1。注意bbox里的w和h是框的宽高不是右下角坐标所以中心点要算x w/2import json import os def coco_to_yolo(json_path, img_dir, out_dir): with open(json_path) as f: data json.load(f) # 图片 id - (宽, 高, 文件名) img_info {img[id]: img for img in data[images]} # 按 image_id 聚合标注 from collections import defaultdict ann_by_img defaultdict(list) for ann in data[annotations]: ann_by_img[ann[image_id]].append(ann) os.makedirs(out_dir, exist_okTrue) for img_id, anns in ann_by_img.items(): info img_info[img_id] W, H info[width], info[height] lines [] for ann in anns: x, y, w, h ann[bbox] # 过滤掉宽高为 0 的无效框 if w 0 or h 0: continue xc (x w / 2) / W yc (y h / 2) / H nw w / W nh h / H # 裁剪到 [0,1] 防止越界 xc, yc min(max(xc, 0), 1), min(max(yc, 0), 1) nw, nh min(nw, 1), min(nh, 1) cls ann[category_id] # 已重映射为 0/1 lines.append(f{cls} {xc:.6f} {yc:.6f} {nw:.6f} {nh:.6f}) txt_name os.path.splitext(info[file_name])[0] .txt with open(os.path.join(out_dir, txt_name), w) as f: f.write(\n.join(lines)) coco_to_yolo(annotations/instances_train_remap.json, images, labels/train)这段脚本做了三件关键事按image_id聚合标注避免重复打开文件、过滤宽高为零的无效框、把归一化坐标裁剪到[0,1]。最后一点尤其重要——有些标注框会略微超出图片边界不裁剪的话 YOLO 训练时可能报坐标越界警告甚至导致该图被跳过。3.2 四个边界坑第一个坑是图片尺寸不一致。COCO json 里的width和height是标注时记录的原始尺寸如果图片后来被缩放但 json 没更新归一化就会错位。解决办法是转换前用 PIL 重新读一遍实际尺寸和 json 里的对比不一致就以实际为准。第二个坑是类别 id 从 1 开始。YOLO 的类别索引从 0 开始如果没做 2.2 节的重映射rail会被写成 1训练时框架会认为有 3 个类0、1、2多出一个空类。转换前务必确认category_id已经连续从 0 开始。第三个坑是文件名带空格或特殊字符。项目正文里的文件名是哈希串一般安全但如果你的数据集混入了原始文件名带空格的图片YOLO 的 txt 路径解析会断。统一用os.path.splitext取主名再拼.txt不要手动字符串切割。第四个坑是空标注图片。有些图片只有轨道没有障碍物或者标注时漏了转换后会生成空 txt。YOLO 默认会跳过空标注图但如果你希望模型学会“没有障碍物”也是一种状态就要保留这些空 txt 并在配置里关掉过滤。两种策略都合理取决于你的业务是否把“无目标”当作有效样本。3.3 转换后的校验转完不要直接开训先抽几张图把 YOLO 框画回去肉眼确认没偏import cv2 def draw_yolo(img_path, label_path, out_path): img cv2.imread(img_path) H, W img.shape[:2] with open(label_path) as f: for line in f: cls, xc, yc, nw, nh map(float, line.split()) x1 int((xc - nw / 2) * W) y1 int((yc - nh / 2) * H) x2 int((xc nw / 2) * W) y2 int((yc nh / 2) * H) cv2.rectangle(img, (x1, y1), (x2, y2), (0, 255, 0), 2) cv2.imwrite(out_path, img) draw_yolo(images/T111619_jpg.rf.3c539f85f90ad78b07174836877c09e8.jpg, labels/train/T111619_jpg.rf.3c539f85f90ad78b07174836877c09e8.txt, check.jpg)打开check.jpg如果绿框紧贴轨道和障碍物边缘说明转换正确如果框整体偏移或大小不对回头检查width/height是否用了实际图片尺寸。4. 训练配置与 93.7% 准确率的复现条件93.7% 这个数字不是随便跑跑就能到的它依赖输入分辨率、batch size、学习率策略和增强方式的组合。这一章把复现这个指标的关键参数拆开讲同时说明在什么条件下这个数字会掉。4.1 输入分辨率与 batch size 的取舍轨道检测的特点是目标细长——轨道在画面里可能只占几十个像素宽障碍物大小不一。输入分辨率太低轨道纹理丢失模型分不清轨道和背景分辨率太高显存吃紧batch size 被迫降到 4 以下BN 层统计不稳定。常见做法是把输入设成 640×640 起步如果显存允许就上 1024×1024。以 640 为例单卡 8G 显存跑 YOLOv8n 可以开到 batch 16跑 YOLOv8m 大概 batch 8。# data.yaml path: ./dataset train: images/train val: images/val nc: 2 names: [rail, obstacle]# 训练命令示例 yolo detect train datadata.yaml modelyolov8m.pt epochs100 imgsz640 batch8 \ lr00.01 lrf0.01 momentum0.937 weight_decay0.0005 warmup_epochs3imgsz640是输入边长batch8是单卡批大小lr0是初始学习率lrf是最终学习率系数最终 lr lr0 × lrf。warmup_epochs3让学习率在前 3 个 epoch 从很小线性升到lr0避免一开始就大梯度把预训练权重冲坏。轨道数据量 3900 张不算大warmup 能明显稳住早期 loss。4.2 数据增强里哪些该开、哪些该关轨道场景的增强有讲究。水平翻转通常可以开因为轨道左右对称但垂直翻转要谨慎倒过来的轨道不符合物理场景可能让模型学到错误特征。Mosaic 增强能提升小目标检测但轨道是长条目标Mosaic 拼接后轨道会被截断反而引入噪声。我的经验是前 80 个 epoch 开 Mosaic最后 20 个 epoch 关掉让模型在真实分布上微调。# 在训练命令里控制增强 yolo detect train datadata.yaml modelyolov8m.pt epochs100 imgsz640 batch8 \ mosaic1.0 close_mosaic20 fliplr0.5 flipud0.0mosaic1.0表示 100% 概率做 Mosaicclose_mosaic20表示最后 20 个 epoch 关闭fliplr0.5是水平翻转概率flipud0.0禁用垂直翻转。这几个参数直接决定模型看到的数据分布调错了 93.7% 可能掉到 85% 以下。4.3 验证集划分与指标解读3900 张图按 8:1:1 分训练 3120、验证 390、测试 390。验证集用来调超参和早停测试集只在最后跑一次。93.7% 如果是 mAP0.5那说明在 IoU 阈值 0.5 时平均精度到了 93.7%如果是准确率accuracy那在目标检测里参考价值有限因为负样本定义模糊。看指标时优先看mAP0.5和mAP0.5:0.95前者反映能不能检出后者反映框得准不准。轨道检测里mAP0.5高但mAP0.5:0.95低说明框的位置还不够精细可能需要调 anchor 或换更高分辨率。4.4 类别不均衡时的 loss 调整如果障碍物标注数只有轨道的十分之一模型会偏向预测轨道。除了重采样还可以在 loss 里给障碍物类加权。YOLO 默认用 BCE 分类 loss可以通过修改cls的权重或者用 focal loss 替代。简单做法是在数据加载时对含障碍物的图片做 oversampling让每个 epoch 里障碍物样本出现频率提高 2~3 倍。5. 避坑与排查训练不收敛、mAP 虚高、推理漏检的常见原因这一章记录我在用这份数据集时真实翻过的车每条按现象、原因、解决写你遇到类似情况可以直接对号入座。现象一loss 从第一个 epoch 就 nan。原因通常是学习率太大或者标注里有非法值。检查 json 里有没有bbox宽高为负、坐标为 NaN 的记录用 2.1 节的统计脚本过滤掉。另外lr00.01对某些模型偏大可以先降到 0.001 跑几个 epoch 看 loss 是否正常下降。现象二训练 loss 降但验证 mAP 一直 0。最常见的原因是验证集 json 的类别 id 和训练集不一致或者验证集图片路径配错导致加载的是空图。先确认data.yaml里val路径下的图片和标签能一一对应再检查两个 json 的categories是否完全相同。还有一个隐蔽原因是验证时conf阈值设太高默认 0.25 如果模型还没收敛所有预测都被过滤掉mAP 自然是 0可以临时把conf降到 0.001 看有没有框出来。现象三mAP 虚高但实际推理漏检。如果验证集和训练集有重叠图片mAP 会虚高。用图片文件名的哈希前缀去重确保三个集合没有交集。另外如果验证集里障碍物样本极少mAP 被轨道类拉高看起来漂亮但障碍物根本检不到。分类别看 AP别只看总体。现象四推理时轨道被检成障碍物。类别混淆通常是因为两类目标在视觉上有重叠——比如轨道上的异物既像轨道又像障碍物。解决办法是检查标注一致性看有没有把轨道本身标成障碍物的脏数据另外可以增加正样本里“轨道障碍物”共存的图片比例让模型学会区分。现象五GPU 显存够但训练报 OOM。不一定是 batch 太大可能是imgsz设太高或者数据加载的 worker 数太多导致内存泄漏。先把workers降到 2 试试如果还 OOM 再降batch。另外验证阶段也会占显存val时的 batch 默认和训练一样可以在配置里单独调小。6. 进阶技巧用 TTA 和分块推理把轨道小目标检出率再提一档基础训练跑通之后如果想把轨道上的小障碍物检出率再往上推有两个技巧值得试测试时增强TTA和分块推理。TTA 是在推理时对同一张图做多种变换水平翻转、多尺度缩放把多次预测融合通常能涨 1~2 个点 mAP代价是推理时间翻几倍。分块推理是把高分辨率大图切成带重叠的小块分别检测再合并适合轨道在画面里很细、直接缩放到 640 会丢失细节的场景。TTA 在 YOLO 里可以直接开yolo detect predict modelbest.pt sourcetest_images imgsz640 augmentTrue conf0.25 iou0.5augmentTrue就是开启 TTA框架会自动做水平翻转和多尺度融合。注意 TTA 只在最终评估或对精度要求极高的离线检测里用实时巡检场景开了会拖慢帧率。分块推理需要自己写合并逻辑核心是切块时保留 20% 重叠合并时用 NMS 去重import cv2 import numpy as np def sliding_window_detect(model, img, tile640, overlap128): H, W img.shape[:2] step tile - overlap all_boxes [] for y in range(0, H, step): for x in range(0, W, step): x2, y2 min(x tile, W), min(y tile, H) x1, y1 max(x2 - tile, 0), max(y2 - tile, 0) patch img[y1:y2, x1:x2] results model(patch, verboseFalse) for r in results: for box in r.boxes: bx1, by1, bx2, by2 box.xyxy[0].tolist() all_boxes.append([bx1 x1, by1 y1, bx2 x1, by2 y1, box.conf.item(), box.cls.item()]) # 用 cv2.dnn.NMSBoxes 做全局去重 boxes_xywh [[b[0], b[1], b[2]-b[0], b[3]-b[1]] for b in all_boxes] scores [b[4] for b in all_boxes] idx cv2.dnn.NMSBoxes(boxes_xywh, scores, 0.25, 0.5) return [all_boxes[i] for i in idx]这段代码把大图按tile640、overlap128切块每块单独推理后把框坐标映射回原图最后用 NMS 去掉重叠框。overlap不能太小否则跨块的轨道会被切断也不能太大否则重复计算拖慢速度。我一般设成 tile 的 20%。从那以后我每次拿到新的 COCO 数据集都强制先跑一遍类别统计和路径校验再抽三张图画框确认最后才开训。这套流程帮我省掉了至少五次训到一半发现标注错位的返工。希望帮到你。本文还有配套的精品资源点击获取
返回列表