简介:面向需要训练多类别分割模型的开发者,这份铁路图像全景分割数据集提供约3500张图像与对应掩码,共2000个文件,包括1499个png标注、500个jpg原图及1个可视化脚本,压缩包约572MB。数据按训练集与测试集划分,训练集约3000张,测试集约500张,每类均含images和masks目录,便于直接用于U-Net、DeepLab等网络训练与评估。附带脚本可随机抽取图片,将原始图、GT图与叠加蒙版效果保存到本地,快速核验标注质量。已有312人学习下载,适合入门至进阶的计算机视觉学习者、科研人员及铁路巡检相关项目开发者。数据集源自CSDN作者qq_44886601,并关联医学图像分割网络专栏,可作为多类别分割任务从数据准备到模型验证的完整实践素材。
1. 铁路图像全景分割数据集:约3500张图,多类别分割能解决什么
轨道交通视觉检测项目大多会卡在同一个坑:钢轨、扣件、接触网这类目标又细又长又碎,用检测框框出来的不是目标,而是大片混合背景。铁路图像全景分割数据集的价值,在于用像素级多类别分割把轨道、道砟、扣件、接触网逐层解出来,这个标注了约3500张图像的图像分割数据集,正好是训练这种模型的入口。
它适合两类人:一类是做铁路巡检算法落地的工程师,需要快速验证全景分割方案;另一类是研究语义分割迁移学习的开发,想要一份类别结构更特殊的真实场景数据。三千多张图的量级不大,但足以把小模型从零训起来,或者做域内微调。更合理的定位是先靠它把训练pipeline、数据校验和评估闭环跑通,再决定是否扩大标注。
2. 铁路全景分割任务拆解:为什么多类别分割比检测框更适合轨道
2.1 全景分割和语义分割的边界:钢轨、扣件到底怎么划分
先想清楚一个问题:这个标题里的全景分割,和常见的语义分割有什么区别。语义分割要做的事,是给图像里的每一个像素打上一个类别标签。拿铁路场景举例,训练完的模型会把画面分成钢轨、枕木、道砟、接触线、支柱、植被、天空等区域,每个区域只有类别,没有个体编号。这在做路面区域判断、覆盖范围分析时够用。全景分割则往前走了一步:它同时处理stuff和thing。stuff通常指无固定形状的结构,比如道砟、天空、植被、土地;thing是可数物体,比如单个扣件、绝缘子、异物、行人。如果任务只做多类别分割,模型对所有像素做分类就够了;如果做全景分割,可数物体还必须单独分出实例。
铁路场景对这两者的需求是同时存在的。钢轨、道砟是stuff,取它们的语义掩码就够;但扣件、绝缘子是thing,不区分实例就没法统计数量,没法判断哪个扣件缺失,后续维护动作也没法落到具体坐标上。只看类别标签文件里的train_id设计,就能看出这个数据集是不是按照全景分割的思路组织的:纯语义分割数据集通常只有一串平铺的类别ID,全景分割数据集则会在类别设计里预留实例语义,并在标注工具里区分“东西”和“背景”。
| 目标 | 语义分割标注 | 全景分割标注 |
|---|---|---|
| 钢轨 | 一个“钢轨”类别覆盖所有轨道 | 语义类别即可,不区分单根 |
| 扣件 | 所有扣件统一成一类 | 每个扣件单独实例 |
| 接触网线 | 一条线类别 | 长线类语义 |
| 道砟 | 一个区域类别 | 一个区域类别 |
| 异物/行人 | 语义类别 | 实例,可计数和定位 |
看到训练配置里既有语义类别又有实例类别时,不要惊讶于同一个数据集的标签结构是混合的,这正是全景分割的通用组织方式。Cityscapes、BDD100K这类自动驾驶数据集基本沿用同一套思路。铁路图像与自动驾驶数据最大的差别不在算法,而在场景结构:铁路摄影机位置相对固定,画面里细长结构极多,背景长期不变,前景稀疏,类不平衡和边缘质量往往比算法本身的差距更致命。把算法从街景搬到铁路上,最先要适应的不是backbone,而是数据分布。
2.2 标签组织与类别统计:先搞懂3500张数据是怎么放好的
无论数据集原始格式是COCO JSON还是PNG索引图,第一步都是把标签读成像素级的类别索引数组。常见目录结构是images/和annotations/并列,图像通常是JPG,标签是PNG。PNG标签一般用单通道索引图,像素值就是类别ID,而不是RGB彩色填充图。这里最容易踩的坑是高高兴兴把mask当成彩色图读进来,结果np.unique一数,类别数量变成几百个。
from PIL import Image import numpy as np from collections import Counter # 固定用PIL读,避免OpenCV的通道序问题 img = Image.open("images/rail_001.jpg") mask = Image.open("annotations/rail_001.png") mask_arr = np.asarray(mask) assert mask_arr.shape == (img.height, img.width), ( f"mask尺寸{mask_arr.shape}和图像尺寸{(img.height, img.width)}不一致" ) vals, counts = np.unique(mask_arr, return_counts=True) for v, c in zip(vals, counts): print(f"类别ID {v}: {c} 像素,占比 {c / mask_arr.size:.4f}")逻辑说明:第9行的维度断言一定要放在训练开始前,因为铁路数据常见问题是原始图片被旋转校正后标签没有同步更新。只有先保证两者尺寸一致,后面的类别统计才有意义。np.unique统计标签像素分布,能快速发现哪些类别接近缺失;全图的类别占比可以帮助后续做类别采样或者类别权重,不用等到训练完再回头找原因。
参数说明:PIL对16位PNG也能读,np.asarray得到的dtype可能是uint8或uint16,统计时不关心。但要注意,如果mask是索引彩图(P模式),np.asarray得到的是索引值而不是RGB,这正是需要的类别ID;如果得到的是三通道RGB,多半是导出端转错了,先让数据方重新导出,不要自己在代码里硬转。
类别表的常见约定如下,不同数据集可能不同,以数据集提供方实际说明为准:
| 类别名 | train_id | 是否实例 | 常见标签痛点 |
|---|---|---|---|
| 未标注/背景 | 0 | 否 | 容易和远处道砟混在一起 |
| 钢轨 | 1 | 否 | 轨头反光导致断裂 |
| 枕木 | 2 | 否 | 被道砟遮挡后不连续 |
| 道砟 | 3 | 否 | 与阴影边界模糊 |
| 扣件 | 4 | 是 | 小目标,容易漏标 |
| 接触线/承力索 | 5 | 否 | 1-3像素线宽,标注难度极高 |
| 支柱/绝缘子 | 6 | 是 | 与背景颜色接近 |
| 异物/人员 | 7 | 是 | 样本稀疏,类不平衡严重 |
这里先不要关注具体模型,先把标签读通。图像分割数据集的价值有一半埋在标签里,类别结构设计合理、mask边缘干净,后面训练能省下大量纠错时间。反过来,标签没验证就投入训练,大概率会碰到loss很正常、可视化和现场效果却对不上的情况,后面避坑章节会展开讲。
3. 把3500张铁路图像变成可训练样本:清洗、重映射和按线路划分
3.1 数据清洗:先剔除坏样本,再统计类别缺失
拿到数据先清洗,而不是直接写train.py,这是投入产出比最高的一步。很多公开数据集的原始质量比想象中差,会出现标签缺失、尺寸不一致、mask全零等问题,这些样本混进训练集会直接污染mIoU曲线。
import os, glob from PIL import Image import numpy as np img_files = sorted(glob.glob("images/*.jpg")) bad = [] for img_path in img_files: mask_path = img_path.replace("images/", "annotations/").replace(".jpg", ".png") if not os.path.exists(mask_path): bad.append((img_path, "missing_mask")) continue im = Image.open(img_path).convert("RGB") ms = Image.open(mask_path) if im.size != ms.size: bad.append((img_path, f"size_mismatch {im.size} vs {ms.size}")) continue m = np.asarray(ms) if m.ndim != 2: bad.append((img_path, f"mask_channels {m.ndim}")) continue if np.unique(m).size <= 1: bad.append((img_path, "single_class_mask")) print(f"共检查 {len(img_files)} 张,异常 {len(bad)} 张") for item in bad[:20]: print(item)逻辑说明:这个脚本检查四类问题:标签缺失、尺寸不一致、mask通道数异常、单类mask。尺寸不一致在行车记录仪焦距切换或多相机混采的数据里很常见,一定要在训练前拦下。单类mask通常是标注员漏标,这种样本直接删掉或退回重标,比保留着让模型学噪声更划算。
参数说明:脚本默认目录结构是images与annotations平级,文件命名一一对应。如果拿到的是单个COCO JSON文件,不必强行转成这个目录结构,直接解析JSON字段就行。Windows环境注意路径分隔符,别把路径替换写死成斜杠,用os.path.join处理,防止盘符问题翻车。
清洗完再做一遍类别频率汇总,把所有图中每个类别出现的图像次数和累计像素数都统计出来。这一步决定后续要不要做类别采样,也决定类别权重怎么设置。记录一下数据目录总共有多少帧、多少张正常样本,后面划分时心里有数。
3.2 类别重映射与按场景分组划分
标签清理后经常需要重映射:把语义相近的细类合并成一个训练类,去掉样本太少、无法参与训练的类。例如把承力索和接触线合并成“接触线组”,把杂草并入植被;把255这类“不确定”像素映射到背景或忽略区。重映射时最忌讳直接对mask的每个像素做mod运算,因为原始ID和目标ID之间没有单调关系。
# remap 字典:key 是数据集的原始标签值,value 是训练标签值 remap = {0: 0, 1: 1, 2: 2, 3: 3, 4: 4, 5: 5, 6: 5, 7: 6, 255: 0} def remap_mask(mask: np.ndarray, remap: dict) -> np.ndarray: out = np.zeros_like(mask, dtype=np.uint8) for src, dst in remap.items(): out[mask == src] = dst return out # 批量处理后写入新目录,保持PNG格式 for p in mask_files: m = np.asarray(Image.open(p)) m = remap_mask(m, remap) Image.fromarray(m, mode="L").save(p.replace("annotations/", "remap_annotations/"))逻辑说明:重映射不是简单复制类别号。用零数组做底,再按原始标签逐个填充目标值,比np.where嵌套写法更不容易误改。写入时用mode="L"强制成8位灰度PNG,大多数训练框架默认只认0-255的单通道索引图,存成RGB反而会让loader扑空。
参数说明:类别0如果是背景且不参与loss,训练时统一设置ignore_index=0;但如果背景占比真的很大,把0直接设成ignore会导致模型对未知区域无所适从。我一般保留背景类参与训练,让模型至少学会“这是背景”,再通过类别权重调节主目标,而不是把背景整个忽略掉。255这类特殊值必须在训练前映射掉,千万不要原样喂给模型。
数据划分更要重视。铁路数据几乎都来自连续视频抽帧,相邻帧高度相似。如果直接random split,同一个钢轨缺陷会同时出现在训练集和验证集里,验证指标虚高,上线就翻车。我一般按录制批次或里程段做分组划分,保证同一段线路的视频帧只出现在一个集合里。
from sklearn.model_selection import GroupShuffleSplit # group id 从文件名解析,例如 "line_01_0042.jpg" 取 "line_01" groups = [p.split("/")[-1].split("_")[0] for p in img_files] gss = GroupShuffleSplit(n_splits=1, test_size=0.2, random_state=42) train_idx, val_idx = next(gss.split(img_files, groups=groups)) train_files = [img_files[i] for i in train_idx] val_files = [img_files[i] for i in val_idx] print(f"训练集 {len(train_files)} 张,验证集 {len(val_files)} 张")逻辑说明:GroupShuffleSplit的单位是组而不是样本,只要group id定义正确,同一线路的所有帧就不会被拆到两个集合。很多公开数据集把不同月份、不同路段作为拆分线索,也是这个原因。注意group id的粒度要足够粗,如果按“线路+批次”分,能避免同一时段光线条件相似的照片跨集合。
参数说明:test_size=0.2只适合验证流程。如果后面要做模型选型和超参搜索,最好切出0.15左右比例的test集,保证最终评估结果客观。数据只有3500张的情况下,训练、验证、测试按80/10/10切分即可;验证集太小会导致mIoU置信区间宽到无法观察模型提升。
3.3 标注质量校验:连通域和边缘检查
训练前后各做一次标注质量校验,能省掉大量无效迭代。铁路场景里最明显的特征是线状目标多,钢轨、接触线、承力索一旦被标成断断续续的虚线,全卷积模型学到的就是“断线审美”。连通域检查对这种问题有奇效。
from scipy import ndimage mask = np.asarray(Image.open("remap_annotations/rail_001.png")) # 对钢轨(train_id=1)做连通域分析 labels, n_comp = ndimage.label(mask == 1) sizes = ndimage.sum_labels(mask == 1, labels, range(1, n_comp + 1)) print("钢轨连通域数量:", n_comp) print("最大连通域占比:", sizes.max() / (mask == 1).sum()) if n_comp > 50: print("疑似断裂过多,需要回到标注侧检查")逻辑说明:钢轨是连续结构,理想情况下连通域数量接近1;如果拆出上百个小块,说明标签或预测有严重的断裂。用最大连通域占比量化,小于0.9就要人工介入。这个检查对细线类尤其重要,但对扣件这类本身分散的小目标没有意义,做校验时要按类别分开看。
参数说明:ndimage.label默认使用二维八邻域。对1-2像素宽的接触线,如果原始mask本身是断开的单像素序列,八邻域已经是最宽容的检查方式,仍然报出大量碎片,问题就出在标注源头或转换脚本,而不是模型后处理。
边缘检查看的是mask边缘是否贴住物体边界。常见做法是把mask叠加到原图上做人工抽查,每类抽30张左右就能暴露问题。不要相信“数据集是别人标好的就不用看”这种想法,多花一小时检查,能省下后面几轮训练调试时间。
4. 用YOLOv8-seg和MMSegmentation训练:配置、命令和参数选择
4.1 选型:3500张图更适合先用小模型把流程跑通
数据准备好以后,选训练框架。如果是要在一个铁路项目里快速验证全景分割这条路线,我建议先用YOLOv8-seg做一个基准,因为它配置可读性好、模型出图快、mask IoU直接打印。确认方向可行后,再切换MMSegmentation这类更偏研究的工具去抠细节。3500张数据量不大,一上来就用超大模型很容易过拟合,先用nano或small级别的backbone把训练数据流跑通,比追求极致精度更重要。
| 方案 | 上手成本 | 训练速度 | 分割类型 | 适用阶段 |
|---|---|---|---|---|
| YOLOv8-seg | 低 | 快 | 实例分割,stuff按语义类别处理 | 基准线/快速验证 |
| MMSegmentation | 中 | 中 | 语义分割与部分全景配置 | 算法对比/精细调参 |
| Mask2Former等 | 高 | 慢 | 原生全景分割 | 性能上限冲刺 |
这是一个比较实际的技术选型逻辑:先用最适合自己现有基础设施的方案跑通一遍,拿到可解释的指标,再决定要不要投入更多训练资源。不要为了“全景分割”这个名词,一上来就上最重的模型,数据量撑不住,调试周期也会拉长。
4.2 YOLOv8-seg训练:dataset.yaml和关键参数
用YOLOv8-seg时,数据组织要扁平化。下面是一个可用的配置文件样例,路径和类别名要按实际数据集替换。
# rail-seg.yaml path: /data/rail train: images/train val: images/val names: 0: background 1: rail 2: sleeper 3: ballast 4: fastener 5: catenary 6: pillar 7: foreign_object逻辑说明:YOLOv8-seg的数据接口需要这种扁平组织。对于语义类数据集,像素掩码一般需要先转成多边形标注格式再喂给训练器;转换脚本建议在数据打包阶段完成,不要等到训练时才发现格式不匹配。names的顺序必须和训练时的类别索引严格对应,索引错位会让可视化看起来像“语义错乱”,实际上只是配置写错了。
训练命令按下面这种方式跑:
yolo train data=rail-seg.yaml model=yolov8n-seg.pt epochs=120 imgsz=1280 batch=8 close_mosaic=10参数说明:imgsz=1280很关键。轨道和接触网是细长结构,分辨率太低会把扣件缩成几个像素,钢轨边缘直接糊掉。close_mosaic=10表示最后10个epoch关闭mosaic增强,因为mosaic会把钢轨截断成四个图块,模型学到断线风格,关闭后能明显改善边缘连续性。batch=8在1280分辨率下对20G左右显存比较稳,显存不够就降到4,不要硬撑。
第一次跑用nano模型,整个训练只花几小时,完全跑通后再切s或m。不要一上来直接上最大模型,3500张数据不足以支撑超大模型,容易过拟合。
4.3 MMSegmentation配置:多类别分割的常用写法
MMSegmentation更适合需要精细对比实验的阶段。它的CustomDataset接口要求img_dir和ann_dir目录组织,类别列表和调色板写在config里,标签文件为单通道PNG即可。
# configs/rail/deeplabv3plus_r50_rail.py data = dict( samples_per_gpu=8, workers_per_gpu=4, train=dict( type="CustomDataset", data_root="./data/rail", img_dir="images/train", ann_dir="remap_annotations/train", classes=( "background", "rail", "sleeper", "ballast", "fastener", "catenary", "pillar", "foreign_object" ), palette=[ (0, 0, 0), (255, 0, 0), (0, 255, 0), (0, 0, 255), (255, 255, 0), (0, 255, 255), (255, 0, 255), (128, 128, 128) ], ), )逻辑说明:MMSeg的CustomDataset从ann_dir读入单通道mask,classes数量和顺序直接决定训练类别;palette不是给loss用的,是给可视化工具和TensorBoard上色用的,类别数量必须与classes一致,否则画图时会少色或越界。
参数说明:samples_per_gpu=8即单卡batch size,backbone用ResNet-50时1024分辨率下一般够用。如果输入分辨率缩到512,batch可以升到16。重点是把pipeline里的crop_size从常见默认值(512,512)调成(768,768)或更大,否则线状目标会被随机裁剪切成段,模型看不到完整钢轨结构。
训练命令走标准入口:
CONFIG=configs/rail/deeplabv3plus_r50_rail.py python tools/train.py $CONFIG --work-dir work_dirs/rail先单卡训练确认能稳定收敛,再考虑多卡。单卡log里的loss曲线如果出现阶梯式跳跃,优先检查数据加载和learning rate,不要急着改网络结构。
4.4 loss与类别权重:ignore_index 和 sqrt 分类权重
铁路数据的类不平衡非常明显,背后原因是道砟和背景占大面积,扣件和异物只占少量像素。交叉熵在这种分布下很容易被高频类带偏。
# loss 配置片段 loss_decode=dict( type="CrossEntropyLoss", use_sigmoid=False, ignore_index=255, # 有不确定区才需要 ignore;否则保留背景类 loss_weight=1.0, ) class_weight=None # 按像素频率设置后再打开import numpy as np pixel_cnt = np.array([counts_per_class]) # 长度=类别数,来自训练集统计 freq = pixel_cnt / pixel_cnt.sum() class_weight = 1.0 / np.sqrt(freq) # sqrt 倒数,比直接倒数更温和 class_weight = class_weight / class_weight.sum() * len(freq) print(class_weight)逻辑说明:对像素频率取sqrt倒数后归一化,不会让小样本类别的权重过大导致训练震荡。如果扣件只有几万像素、背景有几百万像素,这个权重直接决定扣件能不能被真正学出来。纯语义loss里设置class_weight最直接;YOLOv8-seg的实例分割分支不直接支持按类别加权,此时改成对少样本类别的图像做重复采样,效果更可控。
参数说明:ignore_index只在“不确定区”存在时使用。如果数据里背景标签是可靠的,就不要忽略背景,否则模型会失去“判断背景”的能力。class_weight的数值不需要太精细,先把数量级调对,再观察各类IoU的变化。类别频率统计一定要来自训练集,不能用验证集算权重,否则会泄漏验证分布信息。
5. 铁路图像分割数据集避坑:标签错位、类不平衡与验证集泄漏
5.1 标签错位:模型看起来在工作,但mask永远偏几像素
现象:训练loss正常下降,验证mIoU不低,把预测mask叠加到原图后,钢轨边缘整体偏移,或者mask比原图小一圈。
原因:常见有三类。一是用OpenCV读灰度mask后按BGR保存,通道顺序和值被篡改;二是在resize时用了线性插值,线性插值会在类别边界产生新数值,把mask搞花;三是旋转校正后标签没有同步旋转,EXIF自动旋转和数据集抽帧工具各做了一次变换,导致图片和mask差了几个像素。
解决:统一用PIL读图像和mask,任何尺寸变换都用NEAREST最近邻插值,不要在数据增强时对mask做线性插值。训练pipeline里加一行assert,检查mask尺寸和图像尺寸一致,这一步能把大部分错位问题拦在训练前。这是我做数据准备时碰到的第一个大坑,越早写进预处理,后面越少返工。
5.2 类别不平衡:mIoU很高,但扣件类完全没学出来
现象:整体mIoU是0.73,扣件类IoU是0.002,接触线也接近0。只看整体mIoU完全发现不了问题。
原因:像素频率差两个数量级,交叉熵softmax天然偏向高频类。铁路场景是“大片背景/道砟 + 少量细线/小目标”的结构,这种不平衡被体系化放大了。当背景和道砟占98%像素时,模型只要把所有像素都预测成背景就能拿到很低的loss,但扣件等于完全被忽略。
解决:先做像素类别直方图,确认各类占比。loss侧用sqrt类加权交叉熵,数据侧对少样本类别的图像做过采样,把扣件出现多的图重复放进训练集。然后每轮验证都要输出per-class IoU,不要只看mIoU。3500张数据里少样本类别的IoU波动很大,建议在验证集上多积累几个epoch再看趋势,不要因为一个epoch的跳动就改超参。
5.3 验证集泄漏:随机划分让mIoU虚高五个点
现象:验证mIoU有0.85,现场测试只有0.4,泛化能力差到没法用。
原因:铁路图像来自连续视频抽帧,相邻两帧几乎一模一样。如果直接random split,同一段钢轨缺陷会同时出现训练集和验证集,验证指标虚高。这不是模型强,是数据划分作弊了。
解决:按里程段或拍摄批次分组划分,用GroupShuffleSplit这类工具,保证同一段线路不会跨集合。这是一个高频翻车点,很多人在网上分享“yolov8训练自己的数据集”经验时都会强调这一点,因为它造成的假象最难察觉,指标又最漂亮。数据划分时宁可让验证集少几张,也要保证独立性。
5.4 钢轨和接触线标签边缘开裂
现象:接触线的GT是一条细线,但mask上出现一排小黑洞,钢轨掩码也断成虚线。
原因:标注工具导出PNG时用了有损压缩,或标注时只画了线路径但没有填充,或数据在RLE转多边形再转回mask的过程中产生了锯齿和孔洞。对1-3像素宽的接触线来说,一个孔洞就足以让连通域检查报警。
解决:对钢轨、接触线这类连续结构,在预处理阶段用3x3大小的形态学闭运算修复细小孔洞,注意不要对扣件这类小目标做闭运算,会把独立的扣件粘连到一起。更好的是在数据源头保留逐像素PNG,不要在PNG和COCO格式之间来回转换,每转一次都是对细线结构的破坏。
5.5 无标签负样本不能平白无故混进去
现象:训练集里混了一些没有标签或标签不完整的图,训练后模型遇到新场景全判成背景。
原因:未标注区域在训练时会被当成背景计算loss,模型被“无标签即背景”这个规则带偏,背景类特征空间被各种新场景撑大。铁路现场照片千奇百怪,阴影、杂草、桥梁、隧道壁都被塞进背景,模型自然失去区分能力。
解决:只训练有完整标签的图像。没有标签的图如果一定要利用,成熟的做法是做伪标签后单独进入半监督环节,并且设置可信度阈值。在系统没有跑通半监督之前,最稳妥的策略就是删掉这些图。图像分割数据集的价值建立在标签可信度上,不要因为舍不得那几十张图,赔上整个模型的收敛质量。
6. 训练完先别急着上线:用叠加可视化和逐类IoU表验证数据集质量
6.1 叠加可视化脚本
训练结束后,第一件事不是看总mIoU,而是把预测mask和GT同时叠加到原图上,随机抽20张看一遍。只看数字会漏掉很多结构性问题,图片不会说谎。
def overlay_mask(img, mask, color=(255, 0, 0), alpha=0.5): """把mask叠加到BGR图像上,适合快速抽查""" vis = img.copy() mask_bool = mask > 0 vis[..., 0][mask_bool] = vis[..., 0][mask_bool] * (1 - alpha) + color[2] * alpha vis[..., 1][mask_bool] = vis[..., 1][mask_bool] * (1 - alpha) + color[1] * alpha vis[..., 2][mask_bool] = vis[..., 2][mask_bool] * (1 - alpha) + color[0] * alpha return vis逻辑说明:mask叠加到原图后,能直接看出边缘偏不偏、细线断不断、扣件有没有漏。建议按类别分别做叠加图,比如只看钢轨一个类,把漏检和误检分色显示。这个脚本比单纯打印IoU更利于定位数据问题。
6.2 看最难样本而不是最好样本
每次训练结束,把每个类别IoU最低的几张图打印出来,看那些最难的样本。这些图才是决定模型能不能上线的关键。几乎每次都能从最难样本里找到问题根源:要么标签错位,要么某类样本太少,要么验证集里混杂了同一线路的重复帧。
说一个我自己跑这个方向的血泪经验:第一次用铁路全景分割数据时,我花了很多时间调模型backbone和训练策略,最后发现是标签里有几十张mask错位,模型自己学会了绕过这些边缘区域。后来我把叠加可视化和逐类IoU作为任何训练流程的固定收尾动作,再也不在这种坑上浪费时间了。学会先验证数据,再迭代模型,能省下五六轮无效实验。希望帮到你。
本文还有配套的精品资源,点击获取