遥感语义分割实战:7类数据集与SegFormer训练全流程解析
简介面向遥感影像语义分割任务的开源数据集适用于建筑提取、山地植被分类、农田与水体识别等场景。图像统一为1024×1024高分辨率支持细粒度分割模型的训练与验证既适合计算机视觉初学者练习分割流程也可用于科研和工程评估。数据集按训练集和验证集组织训练集含149张原始jpg图像及149张对应png掩膜验证集含37张图像及37张掩膜样本经过整理可直接开展监督学习。标签覆盖背景、土地、建筑、农田、植被、水体等7个类别并附类别txt说明文件便于确认类别索引和后续自定义改动。包内共375个文件除jpg原图与png掩膜外还提供Python脚本用于数据处理或格式转换整体压缩包约47MB轻量易下载。已有166人学习配套博客与YOLOv5分割实战参考可帮助快速上手。适合遥感、计算机视觉领域的学生、研究人员及开发者作为标准数据资源使用。1. 遥感语义分割的7类数据集它到底解决了什么、凭什么值得做做遥感图像分割的人十有八九都卡在同一个地方算法模型不缺缺的是能直接喂进模型的带标签数据。目标检测还能用框把建筑圈出来可一旦要算建筑占地面积、评估山地植被覆盖率、做生态遥感指数分析框就完全不够用了必须像素级地判断每一块地是什么。这个图像分割数据集把遥感影像里的地物归成建筑、山地、植被等7个类标签文件跟着影像一起把原始影像和像素级mask的对应关系全部打通拿过来就能训练语义分割模型。它适合三类人做遥感地物识别的算法工程师、做土地利用分类的GIS从业者以及想从目标检测切到语义分割但找不到干净数据集的入门者。不需要自己从零标注几千张影像这是做这个方向最值钱的部分。2. 标签文件为什么长这样从原始影像到像素级mask的完整链路2.1 单通道PNG里藏了什么调色板映射与类索引的关系遥感分割数据集的标签文件绝大多数是PNG格式但这里的PNG和普通照片的PNG完全不是一回事。普通PNG是RGB三通道而标签PNG是单通道索引图像每个像素存的是一个整数这个整数就是类别索引。显示的时候通过调色板palette把索引映射成颜色所以你在看图软件里看到的是彩色mask但实际数据里每个像素只有一个值。这带来一个最常见的误解很多人以为标签图是RGB三通道读进来变成一个[H, W, 3]的数组再去做one-hot编码结果训练时loss怎么都不收敛。正确做法是读成[H, W]的单通道值域在0到6之间7类背景可能是0然后在训练代码里做one-hot转成[7, H, W]的概率图。用Python验证一张标签图是不是单通道代码长这样import numpy as np from PIL import Image mask_path labels/001.png mask np.array(Image.open(mask_path)) print(shape:, mask.shape) print(dtype:, mask.dtype) print(unique values:, np.unique(mask))如果shape是(H, W)说明索引模式正确如果shape是(H, W, 3)且三个通道的值都一样说明标签被读成了RGB模式但内容仍然有效需要转回索引模式。如果shape是(H, W, 3)且三个通道值不一样基本可以判定标注工具在导出时直接把RGB写进了每个像素这类标签需要做颜色到索引的映射才能用而不是直接拿来训练。2.2 从矢量边界到栅格mask把shp转成和影像对齐的标签文件遥感领域的原始标注通常是矢量格式常见的有shapefile里的面要素每一块地物是一个多边形。模型训练要的是栅格mask所以关键的中间步骤不是画图而是矢栅转换。常见做法是先用GDAL把遥感影像读进来拿到地理变换参数然后创建一个和影像像素网格完全对齐的空白栅格再把矢量多边形通过GDAL的RasterizeLayer烧录进去。from osgeo import gdal, ogr raster_path images/tile_001.tif shp_path labels/tile_001.shp out_mask labels/tile_001.png # 读取影像尺寸与地理变换保证mask和影像像素严格对齐 ds gdal.Open(raster_path) geo_transform ds.GetGeoTransform() width ds.RasterXSize height ds.RasterYSize # 创建单通道内存栅格值为0代表背景 target_ds gdal.GetDriverByName(MEM).Create(, width, height, 1, gdal.GDT_Byte) target_ds.SetGeoTransform(geo_transform) target_ds.SetProjection(ds.GetProjection()) # 设置类别字段与烧录值 shp_ds ogr.Open(shp_path) layer shp_ds.GetLayer() field class_id # 矢量属性表中的类别字段 options [ATTRIBUTE field] gdal.RasterizeLayer(target_ds, [1], layer, optionsoptions)这段代码的核心是最后两步先保证栅格和影像的尺寸、坐标一致再用矢量属性表里的class_id字段作为烧录值。很多人忽略SetProjection这一步结果mask和影像在GIS软件里叠不上训练时虽然能跑但可视化时对不上位置。烧录完成后如果发现mask和影像有偏移优先检查这一步的坐标系是否一致。2.3 类别平衡遥感分割最容易忽视的第一道坎7类地物在遥感影像里的面积天然不平衡。建筑和植被通常占比很大山地可能面积也大但道路、水体这种窄条和细碎地物占的比例很小。如果直接拿原始分布训练模型会倾向于把所有不确定像素都预测成大类别小类别的IoU可能只有个位数。拿到数据集后第一件事不是急着跑模型而是统计类别分布。import numpy as np from PIL import Image from collections import Counter total_counts Counter() for i in range(1, len(valid_images) 1): mask np.array(Image.open(flabels/{i:03d}.png)) total_counts.update(mask.flatten()) total_pixels sum(total_counts.values()) for cls_id in range(7): count total_counts.get(cls_id, 0) print(fclass {cls_id}: {count / total_pixels:.4f})统计结果出来后如果最大类别占比超过60%就得在loss里加类别权重或者用加权采样让每张图的类别分布更均衡。我一般直接用CrossEntropyLoss的weight参数权重取总像素数除以该类别像素数的对数这样既放大小类别梯度又不会让权重差异过大导致训练震荡。别跳过这一步很多人在山地和植被上翻车根子就在类别分布没看。3. 基于SegFormer训练这个7类数据集从目录组织到命令跑通3.1 数据目录怎么组织参考VOC风格的三个子目录语义分割数据集的目录结构看起来是小事但直接影响后续所有脚本。我最常用的是VOC风格JPEGImages放原始影像SegmentationClass放标签PNGImageSets/Segmentation放txt索引文件。txt里每行一个文件名不带扩展名训练脚本按这个列表读图而不是直接扫描整个文件夹好处是划分训练集和验证集只需要改txt不用移动任何文件。dataset/ ├── JPEGImages/ │ ├── tile_001.jpg │ ├── tile_002.jpg │ └── ... ├── SegmentationClass/ │ ├── tile_001.png │ ├── tile_002.png │ └── ... ├── ImageSets/ │ └── Segmentation/ │ ├── train.txt │ ├── val.txt │ └── trainval.txt └── class_names.txtclass_names.txt按顺序写7个类别名每个类一行顺序必须和标签像素值严格一致。第0行对应背景第1行对应建筑依此类推。这是最容易埋坑的地方如果class_names.txt里的顺序和mask像素值对不上训练脚本不会报错但mIoU曲线可能莫名其妙地停在某个值上不去可视化时所有预测结果都偏移一个类。3.2 最小训练配置SegFormer-B0在这个数据集上的启动参数训练遥感分割数据集模型选择上SegFormer是既省心又稳定的选项不需要像传统FCN那样精心设计编码器MiT-B0足够处理这种7类任务,而且感受野够大对建筑这种不规则轮廓抓得比纯卷积结构好。用MMSegmentation跑配置文件里的核心参数有四个crop size、batch size、学习率和类别权重。# segformer_b0_remote_7class.py 配置文件核心段 dataset_type CustomDataset data_root data/remote_7class/ img_norm_cfg dict( mean[123.675, 116.28, 103.53], std[58.395, 57.12, 57.375], to_rgbTrue) train_pipeline [ dict(typeLoadImageFromFile), dict(typeLoadAnnotations), dict(typeRandomCrop, crop_size(512, 512)), dict(typeRandomFlip, prob0.5), dict(typeNormalize, **img_norm_cfg), dict(typeDefaultFormatBundle), dict(typeCollect, keys[img, gt_semantic_seg]), ] model dict( typeEncoderDecoder, backbonedict( typeMixVisionTransformer, in_channels3, embed_dims[32, 64, 160, 256], num_layers[2, 2, 2, 2]), decode_headdict( typeSegformerHead, in_channels[32, 64, 160, 256], num_classes7, loss_decodedict( typeCrossEntropyLoss, use_sigmoidFalse, class_weight[1.0, 1.2, 1.1, 2.0, 1.8, 1.5, 1.3])))crop size取512是经过考量的遥感影像原始尺寸通常几千乘几千直接整图输入显存放不下随机裁剪成512乘512既能覆盖大部分地物尺度又不会让建筑这种中等目标被切碎。batch size在单卡上取4到8显存不够时优先减crop size而不是减batch size因为小图多batch比大图少batch更容易稳定训练。class_weight按上一步类别统计的倒数再归一化四个关键类的权重区分度要大否则等于没加。3.3 断点续训和评估两条不能省的命令训练遥感数据集不像跑分类那样短平快一次性跑到收敛的可能性很低中断是常态。训练脚本必须支持断点续训否则每次从头来就是灾难。用MMSegmentation训练时启动命令和续训命令如下# 首次启动训练8卡分布式 bash tools/dist_train.sh configs/segformer/segformer_b0_remote_7class.py 8 # 中断后续训加--resume参数 bash tools/dist_train.sh configs/segformer/segformer_b0_remote_7class.py 8 --resume work_dirs/segformer_b0_remote_7class/latest.pth评估时注意评测脚本默认输出的是mIoU、mAcc、aAcc这三个汇总指标但7类遥感任务里汇总指标掩盖了很多细节必须把每个类各自的IoU单独打出来否则你根本不知道到底哪一类在拖后腿。用单卡评估的命令python tools/test.py configs/segformer/segformer_b0_remote_7class.py \ work_dirs/segformer_b0_remote_7class/best_mIoU_iter_80000.pth \ --eval mIoUeval参数会针对每个类分别输出IoU对照class_names.txt里的顺序逐行看。建筑、山地、植被这三类的IoU应该在75以上道路和水体这种窄条类目能过55就算合格。如果大类都上去了小类卡在30以下优先怀疑类别权重不够其次是crop size把这些细长目标切碎了。4. 评估指标怎么解读mIoU的涨跌背后是哪些真实遥感问题4.1 混淆矩阵比mIoU数字更诚实建筑和裸地为什么总是互相污染训练收敛后先别急着看mIoU总分拉一张混淆矩阵出来看哪两类在互相污染。遥感分割里最常见的错误配对是建筑和裸地建筑屋顶在卫星影像上和裸土颜色接近模型经常把建筑边缘预测成裸地或者反过来。第二个高频污染对是植被和山地山地阴影里的植被和低矮灌木在光谱上确实接近人眼都容易分错。验证集上mIoU高并不代表生产环境可靠。遥感影像的分布和自然图像不一样不同季节、不同光照、不同传感器的影像光谱差异巨大。这个数据集里的影像如果来自固定区域固定季节模型换到另一块区域的影像上mIoU可能直接掉20个点。所以评估时除了数据集的验证集我建议额外留出完全不同的区域影像做盲测不看训练区域的任何像素。4.2 每类IoU的计算与多类别mIoU的关系看懂评估输出每类IoU的计算公式是交集除以并集分子是预测为该类且真实也为该类的像素数分母是预测为该类或真实为该类的总像素数。对遥感分割来说这个公式对小类目标极其苛刻一条道路如果只有4个像素宽预测偏移1个像素IoU就掉到60以下。mIoU则是所有类别IoU的算术平均它的隐患在于大类和小类权重相等某个小类从30提到70对mIoU的拉动很大但视觉上可能看不出明显改善因为小类在画面里的面积占比本来就很低。看评估结果有个实用技巧先看7类的IoU分布再算一下中位数和均值的差。如果均值比中位数高5个点以上说明有大类表现很好拉高了整体小类其实还烂着。这种时候优化方向应该是小类的损失权重而不是继续堆模型容量。反向情况均值比中位数低说明有某类严重拖后腿要单独分析那一类的错误模式。4.3 可视化预测mask的检查流程不只看颜色要看边界评估指标全部合格不代表结果能用。遥感图像分割和手机拍照分割最大的区别是几何精度要求建筑边界偏几个像素算占地面积可能就差了几十平方米。所以可视化检查预测结果时不要只看整体颜色分布对不对要看边界处的贴合度。from PIL import Image import numpy as np img np.array(Image.open(images/tile_001.jpg)) pred np.array(Image.open(preds/tile_001.png)) label np.array(Image.open(labels/tile_001.png)) # 把预测和标签不一致的像素标记为白色 diff (pred ! label).astype(np.uint8) * 255 diff_img Image.fromarray(diff).convert(RGB) overlay Image.blend(Image.fromarray(img).convert(RGB), diff_img, alpha0.5) overlay.save(check_diff.png)输出图里白色区域就是预测和标签不一致的像素重点看这些不一致是聚集在类别边界附近还是大面积出现在类别内部。如果主要集中在边界属于正常误差如果类别内部出现整片不一致说明模型在该类上存在系统性偏差大概率是训练数据里该类样本的影像特征不够多样。白色像素占比超过15%这个模型基本还不能投入实际使用。5. 避坑指南遥感分割数据集上最常见的五个翻车现场5.1 标签图看起来是灰的调色板信息丢失还是索引错乱现象用看图软件打开label PNG显示出来是纯黑或者一片灰完全看不出类别颜色。原因某些标注工具导出的PNG只写了索引值没写调色板看图软件不认识索引就直接显示成灰度。解决这不代表数据坏了训练代码里直接按单通道读取即可不需要恢复调色板如果需要可视化用代码把索引映射成自定义颜色再保存。5.2 山地的IoU怎么调都上不去类别不平衡的锅还是标注噪声现象训练到8万迭代建筑、植被IoU已经过80山地IoU始终卡在50左右。原因山地在数据集里面积占比过大或过小都会出问题占比过大时模型倾向于把阴影、低矮植被全判成山地占比过小时模型根本没机会学到山地的纹理特征。另一个常被忽略的原因是山地的标注边界本身就很模糊标注员之间的一致性差这部分噪声是模型上限的天花板。解决先统计山地类别的像素占比如果在10%-30%区间检查训练集的影像里山地的光照角度是否多样性不足建议把山地单独抽出来看预测错误的可视化结果判断是漏检还是误检。5.3 大影像切块之后主体类别偏移crop size和overlap怎么权衡现象遥感影像原始尺寸是4000乘4000随机裁剪512乘512训练模型在验证集上mIoU不低但整图预测时建筑边缘出现大量锯齿且类别偏移。原因切块后模型缺少全局上下文不知道当前切块在整张图里的位置遇到大面积植被时局部特征和山地很像。解决推理阶段用滑动窗口预测窗口之间留overlap重叠区域取多次预测的投票结果。我一般用窗口512、步长256overlap是50%整图预测质量立刻上一个台阶。训练阶段不用刻意加overlap推理时加就够。5.4 验证集mIoU高、换块区域就崩训练和测试影像的分辨率不一致现象数据集里影像分辨率是1米每像素模型验证集mIoU到85换到另一块区域分辨率变成2米的影像mIoU掉到60。原因语义分割模型对分辨率极其敏感不同GSD地面采样距离下同一类地物的纹理特征完全不同模型在1米数据上学到的纹理模式在2米数据上根本不存在。解决训练时做多尺度增强把影像随机缩放到0.5倍、0.75倍、1.25倍再裁剪如果生产数据的分辨率已知应该把它们作为额外训练数据或至少做domain adaptation。不要指望模型自己在分辨率变化时保持鲁棒这是血泪经验。5.5 训练时数据读取慢到GPU空转高分辨率PNG解码太慢现象数据加载器成为瓶颈GPU利用率只有40%训练一步要等很久。原因遥感PNG动辄几千乘几千像素每张解码耗时几十毫秒普通DataLoader的prefetch不够用。解决先把所有影像离线裁成512乘512的小块并保存成JPEG或PNG训练时直接读小块或者用NVJPEG这类硬件解码库。C后端跑遥感数据已经不是可选而是必选纯Python读大图再在线裁剪速度根本跟不上。6. 快速验证数据集与标签文件的两个技巧加载检查和首轮训练试金石拿到一套新的图像分割数据集别急着配SegFormer、调学习率先做两个快速检查能帮你省掉至少一个星期的无效调试。第一个是加载检查。写一个几十行的脚本按train.txt的索引顺序读一张影像、读一张标签检查三件事影像和标签的文件名一一对应且一一对得上标签的unique值集合恰好是0到6随机抽5张图目视检查标签轮廓和影像里的地物边界是否大致贴合。这一步几乎能发现所有由于矢栅转换、坐标偏移、类别映射错乱导致的问题而且成本极低。# 快速验证脚本核心检查逻辑 for name in train_list: img np.array(Image.open(fJPEGImages/{name}.jpg)) mask np.array(Image.open(fSegmentationClass/{name}.png)) assert set(np.unique(mask)) set(range(7)), 类别超出范围 assert img.shape[:2] mask.shape, 影像与标签尺寸不一致第二个是首轮训练试金石。用极小配置跑20个迭代batch_size取2、crop_size取256、学习率取正常值的1/10只看loss能不能持续下降。这一步不追求精度只验证数据管线是通的。loss完全不动检查标签值是不是全为0loss下降但速度极慢检查数据增强里有没有把标签也做了归一化loss到0.5左右开始震荡大概率是类别权重设置得过于极端。试金石跑通后再回到完整配置正常训练基本不会再出现半夜醒来发现训练中断的惨剧。数据集的7类划分本身也值得花半天确认这7类是否符合你的业务需求。只有建筑、山地、植被的话水体在哪、道路在哪模型预测出来的类别输出少了几类后处理阶段照样得补标注。如果业务场景正好是这7类覆盖的范围这个数据集能让你绕过最痛苦的标注环节直接到模型迭代如果覆盖不全把它当作预训练来源再用这个模型做半自动标注给新类别打底稿也是一条很顺的路线。这是我自己踩过的教训当初草率地把一套数据集的类别强行映射到业务需求上结果在道路这一类上白白调了一个多月不如一开始就摸清类别边界再决定。希望帮到你。本文还有配套的精品资源点击获取
上一篇/下一篇内容由系统自动关联
返回资讯列表 →