恒美微站
首页
关于我们
建站服务
主题模板
案例展示
资讯中心
联系我们
遥感语义分割数据集从预处理到训练的完整实战指南
首页
资讯中心
/
遥感语义分割数据集从预处理到训练的完整实战指南
遥感语义分割数据集从预处理到训练的完整实战指南
发布时间:2026/10/9 3:58:08
简介面向遥感影像语义分割的高分辨率数据集包含7个类别覆盖背景、土地、建筑、农田、植被与水体等常见地物可用于建筑提取、土地覆盖分类等细粒度分割任务。资源包共375个文件以186张jpg原图与187个png掩膜标签为主体另附类别说明txt和辅助Python脚本整体体积约47MB。数据已明确划分为训练集与验证集训练集149张图片及对应掩膜验证集37张图片及对应掩膜可直接投入FCN、U-Net等主流分割网络训练图像分辨率统一为1024×1024保留丰富空间细节有利于地物边界精细分割。配套类别文本和脚本可简化标签映射、数据预处理与格式转换完整标签映射见classes.txt。数据集来自真实遥感场景样本覆盖山地、建筑、农田等多种地物有助于评估模型泛化能力。已有166人浏览学习适合遥感与计算机视觉方向的研究者作为标准数据集开展算法验证与工程实践。1. 一套7类遥感分割数据集为什么“解压就能训”是错觉拿到一份“图像分割数据集遥感背景下的建筑、山地、植被等语义分割7类”大多数人的第一反应是解压、看一眼文件夹、直接开训。实际上这套数据决定你模型上限的不是那几行训练代码而是标签文件怎么编码、类别怎么映射、影像和标签的坐标是否对齐。错过了这几点后面每个环节都会还回来。这篇笔记会带你把这类数据集从头拆到尾先搞清7类标签体系和文件组织再走完标注、切片、增强的预处理流水线最后用一个能直接跑的 PyTorch Dataset 把训练拉通并把最常见的翻车点一次说清。适合正在做遥感影像语义分割、需要为语义分割算法准备训练集的工程师。2. 先读懂数据内部结构7类标签体系与文件组织2.1 七类地物是怎么定的以及为什么要关心类别顺序遥感语义分割里的“7类”通常是建筑、山地、植被再搭配道路、水体、农田、裸地之类的组合具体类别由项目需求决定。标签文件mask里每个像素的数值就是类别编号。常见做法有两种流派一种是0留作背景1~7对应七类地物另一种是从1到7直接用七类0留作 ignore_index——这个细节直接决定你训练代码里ignore_index参数写几。我拿到一份新数据第一件事是先算标签的像素分布确认最大索引是不是真的连着7而不是中间跳空。跳空本身不致命但会在计算类别权重时把不存在的类也放进 Softmax导致训练时 logits 有7个标签里却只有3类表现上就是 loss 下降正常、mIoU 就是上不去。更隐蔽的是“空类问题”。比如这张遥感图里没有山地标签最大值小于7但验证集里又有山地。交叉熵对这种缺类的样本并不会报错却会让模型在背景和山地之间来回摇摆。我处理的办法是训练时把所有缺类的切片过滤掉或者确保每个 batch 里至少有一张含全主要类别而不是只靠随机采样。还有一个常被忽略的点类别内部的像素占比往往极不平衡。建筑屋顶、道路都是细碎小块植被和山地经常占画面的六成以上。这种不平衡是遥感数据集的天性后续损失函数和采样策略都会围绕它做文章。2.2 标签文件常见的三种编码灰度PNG、RGB伪彩色、GeoJSON矢量最省心的标签文件是单通道8位 PNG每个像素存类别索引。训练时PIL打开转long张量直接用。次常见的是RGB伪彩色掩膜人看着舒服但如果你直接把三通道当输入喂进损失函数训练代码会静默把它当成三分类问题去算类别数直接从7变成几百。先转索引再训练是铁律。颜色表通常在一个class_dict.csv里记录每个类别对应的 RGB 值。GeoJSON 矢量是 GIS 标注的产物边界是经纬度坐标。这种格式不能直接进网络必须先栅格化成像素掩膜。常见做法是交给rasterio.features.rasterize用影像的 GeoTransform 把矢量面画到和影像一样大小的整型栅格上。这里有一个容易踩的坑矢量是投影坐标米影像也是投影坐标但两者 CRS 不一致时栅格化结果会整体偏移画出来的面和你肉眼看影像里的建筑对不上。给一个我最常用的读取逻辑。假定标签是8位灰度PNG颜色表单独存import numpy as np from PIL import Image def read_label(path, color2indexNone): lbl np.array(Image.open(path)) if lbl.ndim 3: # RGB 伪彩色掩膜必须先用色表映射成单通道索引 mapping np.full((256, 256, 256), 255, dtypenp.uint8) for idx, (r, g, b) in color2index.items(): mapping[r, g, b] idx lbl mapping[lbl[:, :, 0], lbl[:, :, 1], lbl[:, :, 2]] return np.asarray(lbl, dtypenp.uint8)这里面np.full的默认值 255 就是ignore_index意思是不认识的颜色直接忽略而不是归到背景类。只把这一步做对就能避免很多“loss 收敛得好好的可视化一看全是噪点”的局面。文件组织上常见的数据集会给你images/、labels/两个目录加一个train.txt和val.txt分别列出训练、验证的文件名。如果缺val.txt我一般会自己按 8:2 随机分一次并固定随机种子保证每次实验的验证集一致否则模型对比会失真。2.3 CRS与GeoTransform数据集里最容易被忽视的元数据普通图像分割数据集没有“坐标系”这个概念遥感数据集有。影像如果是 GeoTIFF它的头部带着 GeoTransform记录左上角坐标和像素分辨率。标签文件如果是 PNG则什么地理信息都没有。所以训练、切片之前必须先确认影像和标签的坐标参考系统CRS是否一致。我最常用的检查命令gdalinfo train_images/area_01.tif gdalinfo train_labels/area_01.tif重点看Coordinate System is和Origin/Pixel Size两行。如果影像来自 Sentinel 或高分卫星标签用经纬度画而影像本身是 UTM 投影两者在像素上天然错位——除非你在标注时就知道了分辨率换算否则一定要先重投影。常见做法是用gdal.Warp把矢量或栅格统一到影像的投影再用同一套 GeoTransform 栅格化。像素对齐这件事做完之后训练代码里根本不会走地理坐标走的是行列号但当你需要把预测结果拼回大图、写成 GeoTIFF 再放进 GIS 做变化检测时CRS 是唯一能把结果“放回地球”的依据。如果你的数据集里同时给了images/和labels/目录名一致、文件名一致还要警惕一件事文件名相同不等于内容对齐。有的数据集来自公开标注成果原始影像被旋转或翻转过标签却没有同步变换。记住几何变换必须同时作用于影像和标签否则错位积累到像素级。我一般的校验方法是随机抽三张图把标签透明叠加在影像上存成 PNG肉眼确认建筑轮廓贴合再进训练。3. 从原始遥感影像到训练样本标注、切片与预处理流水线3.1 标注工具怎么选QGIS、Labelme与半自动预标注数据集的另一半是标注。面对遥感影像我见过三套主力方案。第一套是 QGIS 的画图工具直接在 WGS84 或 UTM 投影下画多边形导出 GeoJSON 或 Shapefile。优点是有地理参考便于后续和影像叠加校验缺点是每个地物都要手描标注量大。第二套是 Labelme适合在影像上画完导出 json再转成掩膜。它不关心投影适合做纯像素级分割但转掩膜时需要先把 json 里的多边形坐标按影像分辨率换算成像素坐标不然会出现整体缩小或偏移。第三套是半自动预标注用一个较强的分割模型DeepLabV3、SegFormer 之类先跑一遍生成伪标签再用标注工具人工修正边界。标注成本能降一半以上但前提是边缘情况得人工筛选一遍伪标签里的噪声如果混进训练集往往表现为类别边缘“糊”在一起。不管用哪种工具我有几个经验。第一把“边界咬合到像素级”写进标注规范避免出现跨越屋顶边缘的斜线否则模型学出来全是锯齿。第二山区阴影里的建筑、被树冠遮挡的屋顶要在规范里明确落到“建筑”还是“植被”否则不同标注员会给你两个版本的答案训练集内部就打架了。第三每一类至少做一次交叉质检两个人标同一块区域比对像素级 IoU低于 0.85 就返工。标注规范的细节最后会百分之百反映在模型的边界质量上。3.2 滑窗切片脚本把大影像变成GPU吃得下的补丁遥感影像动不动就是 1万×1万像素直接塞进 GPU 是自杀。最稳的做法是滑窗切片切成 512×512 或 768×768 的补丁并且把影像和标签同步裁剪。我这里给一份可以直接改的小脚本import numpy as np from PIL import Image from pathlib import Path def sliding_window_crop(img_path, lbl_path, out_dir, size512, stride384): img np.array(Image.open(img_path)) # (H, W, C) lbl np.array(Image.open(lbl_path)) # (H, W) H, W lbl.shape[:2] out_dir Path(out_dir) out_dir.mkdir(parentsTrue, exist_okTrue) n 0 for y in range(0, H - size 1, stride): for x in range(0, W - size 1, stride): img_patch img[y:ysize, x:xsize] lbl_patch lbl[y:ysize, x:xsize] # 跳过纯背景或只有一类的切片避免喂给网络大量无效样本 if np.unique(lbl_patch).size 2: continue # 记录 x_y 坐标之后拼回原图时用它定位 Image.fromarray(img_patch).save(out_dir / fimg_{x}_{y}.png) Image.fromarray(lbl_patch.astype(np.uint8)).save(out_dir / flbl_{x}_{y}.png) n 1 return n这段代码里有三个值得一提的选择。size512是速度和上下文视野的折中太小模型看不到建筑的整体结构太大占用显存stride384意味着每两个切片之间重叠 128 像素重叠是为了避免建筑被切成两半后完全丢失——这个点在第5章还会展开。np.unique(lbl_patch).size 2的过滤规则很关键如果一张切片里只有一个类别网络学不到任何边界信息还白白拉低这一个类别的样本质量。另外右下角不足size的部分会被丢弃如果目标区域正好落在边缘可以给坐标循环加几个边界抖动把余量也采进来。3.3 数据增强与归一化遥感影像不是普通照片普通分割任务常用的水平翻转、随机裁剪在遥感里一样有效但我额外加两项垂直翻转和随机旋转90度的倍数。遥感影像没有“上下”概念卫星拍出来的图你翻转180度依然是一张合法图模型也因此能学到旋转不变性。增强时最重要的是保持影像和标签同步变换。用albumentations比手写省心得多RandomCrop、HorizontalFlip这些变换同时接受 image 和 mask 两个参数天然保证对齐。归一化是另一个经常被忽略的环节。很多人直接把 ImageNet 的 mean/std 拿来用在自然图像上好使在遥感上经常翻车。不同传感器、不同季节、不同大气条件影像的 RGB 数值分布差异非常大。我一般会先统计训练集自身的通道均值方差import numpy as np from PIL import Image from glob import glob files sorted(glob(images/*.png)) means, stds [], [] for f in files[:200]: # 抽200张足够稳住统计量 arr np.array(Image.open(f)).astype(np.float32) / 255.0 means.append(arr.mean(axis(0, 1))) stds.append(arr.std(axis(0, 1))) mean np.mean(means, axis0) std np.mean(stds, axis0) print(channel_mean:, mean, channel_std:, std)注意两点一是样本量不用全量抽200张统计出来的均值和方差已经足够稳定二是如果你的原始影像是16位 GeoTIFF先把它缩放到0~1别把 0~65535 的数值直接喂给网络否则 BN 层前的梯度会被拉得七零八落。还有一种更省事但是我不推荐的方式是对每张图做实例归一化它会让影像失去绝对亮度信息在阴影严重的遥感场景里反而不利。数据增强的力度是个很玄学的事我通常先只用翻转和裁剪把基线跑出来再逐步加色彩抖动和随机缩放每一次只改一个变量。4. 训练一套遥感语义分割模型从Dataset类到关键参数4.1 写一个适配遥感分割数据集的Dataset类数据组织完毕下一步是把文件夹变成 PyTorch 能吃的 Dataset。这里最容易出问题的是“标签通道数”和“数据类型”。标签必须是单通道的long张量很多新手在这里把掩膜当成 RGB 图导致交叉熵在3通道上算得莫名奇妙。给一个可以直接跑的版本import torch from torch.utils.data import Dataset from PIL import Image import numpy as np class RemoteSegDataset(Dataset): def __init__(self, img_dir, lbl_dir, file_list, meanNone, stdNone, transformNone): self.pairs [(img_dir / f, lbl_dir / f) for f in file_list] self.transform transform self.mean mean if mean is not None else [0.485, 0.456, 0.406] self.std std if std is not None else [0.229, 0.224, 0.225] def __len__(self): return len(self.pairs) def __getitem__(self, idx): img_path, lbl_path self.pairs[idx] img np.array(Image.open(img_path)).astype(np.float32) / 255.0 lbl np.array(Image.open(lbl_path)) if self.transform is not None: aug self.transform(imageimg, masklbl) img aug[image] lbl aug[mask] img (img - np.array(self.mean)) / np.array(self.std) img torch.from_numpy(img).permute(2, 0, 1).float() lbl torch.from_numpy(lbl).long() return img, lbl逻辑说明影像读进来先除 255 变成 0~1再做通道归一化最后转成(C, H, W)标签直接读成(H, W)的uint8转long之前绝不混入channel维度。transform如果是albumentations的Compose它会同时返回image和mask天然完成同步增强。如果你的标签是开头说的 RGB 伪彩色记得在进入Dataset之前先转成索引否则这里lbl.shape直接变三通道CrossEntropyLoss会立刻报错或者静默算错。提示RGB 掩膜转索引尽量在预处理阶段做一次并缓存成单通道 PNG不要在__getitem__里每次转换尤其当数据集超过几千张时重复转换会明显拖慢训练。4.2 类别不平衡用像素占比反推加权损失遥感分割里“植被一大片、道路一条线、房子一小块”是常态。直接拿 CrossEntropy 去训模型很快学会“全都预测成植被”因为植被的像素占比可能超过50%光靠这个预测 mIoU 都能有0.4以上但人一看就知道什么都没分出来。对抗不平衡最直接的一招是给损失函数加类别权重。权重按像素占比反比计算def compute_class_weights(lbl_dir, file_list, num_classes8): counts np.zeros(num_classes, dtypenp.int64) for f in file_list: lbl np.array(Image.open(lbl_dir / f)) for c in range(num_classes): counts[c] int((lbl c).sum()) weights 1.0 / (counts 1e-6) weights weights / weights.sum() * num_classes # 归一化让均值接近1 return torch.tensor(weights, dtypetorch.float)这里num_classes建议写成 8把0背景也算进去ignore_index255单独处理。权重归一化这一步很重要如果直接把未归一化的反比当权重某些类权重可能比别的类大几十倍模型会在早期震荡到发疯。比值过大时我还会做一步截断把最大权重限在20以内。除了权重损失Focal Loss 也是一条出路它的 gamma 参数默认2.0通常够用gamma 太大对简单样本压制太过反而让前期训练缓慢。我的实际经验是先跑一个带权重的 CE 做基线看 per-class IoU确认到底是哪些类拉了后腿如果建筑一直起不来再考虑 Focal Loss 或者对大图切片做“含建筑优先采样”。优先采样就是在切片循环里额外判断只保留至少包含若干建筑像素的切片。它比损失函数更立竿见影但会导致整体样本分布失真最后评估时一定要用随机采样验证集。4.3 模型选型与训练参数怎么定FCN、SegFormer与多大分辨率模型选型取决于数据规模。如果你手里的切片只有几千张我的建议是从 U-Net 或 FCN-8sResNet50 骨干起步网络小、收敛快、能快速验证数据质量。切片数量超过两三万张上 SegFormer-B2 或 DeepLabV3 才有意义否则大模型会把你所有的调参时间都耗在过拟合上。遥感语义分割场景下FCN 这类早期语义分割算法依然有它的位置——推理快、易部署适合先拿来做数据冒烟测试。各方案的关键训练参数我整理成一张表参数推荐值说明输入尺寸512×512平衡上下文与显存768 需要更大显存batch_size8~16用 AdamW 时线性缩放学习率初始学习率AdamW 3e-4 / SGD 0.02SGD 配 poly 策略指数0.9训练轮数60~100遥感任务收敛比 ImageNet 慢损失函数CE class weights小目标改善后再试试 Focal评估指标per-class IoU mIoU只看准确率会被背景主导关于迁移学习遥感影像和 ImageNet 图像视觉分布差别不小但骨干网络前几层学到的边缘、纹理特征仍然能用。常见做法是加载 ImageNet 预训练权重把最后分割头换成自己的 8 类或 7 类加背景先冻结骨干训练 5 个 epoch再解冻全量微调。学习率用 poly 衰减lr base_lr * (1 - iter / total_iters) ** 0.9比 cosine 更快稳定。训完以后别急着下结论单独跑一遍验证集的 per-class IoU看小类数字有没有真正起来。5. 遥感分割数据集的5个高频翻车点现象、根因与解决这条路我基本趟过一遍挑五个最常见、最隐蔽的写出来每一条都按“现象 → 原因 → 解决”三步讲清楚。5.1 影像和标签像素错位训练一切正常mIoU就是上不去现象loss 正常下降训练集 mIoU 也能有0.7验证集突然掉到0.3可视化预测结果发现建筑轮廓整体偏移半个房子。 原因影像和标签来自不同数据源或标注时 GeoJSON 的 CRS 与影像 CRS 不一致栅格化时没有统一参考。偏移量在切片后被每个切片“原样继承”模型从头到尾都在学错位的配对。 解决训练前跑一遍gdalinfo确认 CRS 一致不一致就用gdal.Warp重投影到影像的 CRS再栅格化。如果标签是 PNG 掩膜也可以拿影像做基准手动检测偏移量后做整幅平移但只适合近似刚体偏移复杂地形下不推荐。5.2 滑窗把建筑切成两半边界预测永远糊现象模型预测的建筑边缘参差不齐尤其在切片边界处出现断头路、半栋楼。 原因滑窗切片的窗口位置没有考虑建筑边缘。一栋楼如果横跨两个切片每个切片里它都只是“半个建筑”模型被迫在缺失上下文的情况下学习边界自然糊。 解决用带重叠的滑窗比如size512, stride384让 128 像素的上下文保留周围地物同时在训练时对切片做小幅随机偏移让建筑边缘在每次迭代里出现在不同位置。评估阶段用全图推理或重叠拼接投票消除边界效应。5.3 RGB伪彩色掩膜没转索引类别数瞬间爆炸现象dataloader 能跑但 loss 从一开始就异常高训练过程中类别输出从8变成几百。 原因标签是三通道伪彩色像(255, 0, 0)这样的三元组被当成3通道特征输进了交叉熵Softmax 输出维度还是8但目标张量变成了(3, H, W)模型相当于在学一个几百类的标签映射。 解决读取掩膜后先判断ndim三通道就先按色表映射成单通道索引。不认识的像素填255交给损失函数的ignore_index255。这层检查我建议直接写进Dataset.__getitem__既是风格也是保命。5.4 植被占比太大模型变成“植被预测机”现象整体准确率90%以上但查看 per-class IoU植被0.85、建筑0.15、道路0.2mIoU惨不忍睹。 原因训练集里植被、山地的像素占比远远压过建筑和道路交叉熵被大类的梯度淹没小类几乎没有有效学习信号。 解决第一步给CrossEntropyLoss加按像素占比反比的类别权重权重上限截断在20第二步做“小类优先采样”切片时如果某张图建筑像素过少就直接丢弃保证训练集里每类最低占比第三步实在不行上 Focal Loss并同步调高小类权重。按这个顺序试九成场景能救回来。5.5 整图读入内存直接爆掉训练还没开始就 OOM现象dataloader 第一次迭代就卡死或者一张 1024×1024 的图就把8GB显存吃穿。 原因输入原始大影像没有切片整张 1万×1万 的 GeoTIFF 变成 tensor或者切片尺寸设得太大而 batch_size 没减。 解决在大图进模型前先做完滑窗切片缓存成 512×512 小图训练时按文件名索引读取。另外把pin_memoryTrue配合适当num_workers多线程读图能显著缓解 IO 瓶颈num_workers先从 4 试卡死就往下调太猛了反而引起系统调度抖动。这五条是我反复踩过的最痛位置尤其是第3条属于看完一眼就能避开但没避开会浪费一整周的血泪经验。6. 进阶用逐类IoU和可视化回看验收数据集6.1 逐类IoU怎么算以及为什么它能定位你的数据短板mIoU 是个平均值会被大类拉高。逐类IoU才是体检表。给一个简单的实现def compute_per_class_iou(pred, gt, num_classes8): ious [] for c in range(num_classes): p (pred c) g (gt c) inter (p g).sum() union (p | g).sum() ious.append(float(inter) / float(union 1e-6)) return ious脚本跑完一眼就能看出是哪一类在拖后腿。如果某一类的 IoU 长期低于阈值而其他类正常问题往往不在模型在那一类的标注质量或样本量。这个指标也是论文里最常用、也最容易骗过自己的数字所以你更应该坚持自己算一遍原始结果而不是只看一个平均分。6.2 训练期间把预测结果叠回原图是唯一可靠的质控手段我常年保持一个习惯每训练几个 epoch随机抽20张验证切片把预测 mask 半透明地叠在影像上存成 PNG排进 TensorBoard 或直接打开文件夹看。重点不是看整体效果而是看建筑边界是否贴边、道路有没有断、植被和山地的边界是否尖锐。有一次我盯了半小时发现所有切片里水体总被误判成山地回去查标签才发现水体那一条的标签文件后缀名写错了压根没参与训练。这个习惯救过我很多次。现在只要数据集里有任何怀疑我就先把影像和标签两张图透明叠加输出到一张图上第一眼就能看出错位、类别颠倒、标注污染。这套流程在你从公开数据切到自制数据时尤其有用别人给你的 7 类数据可以直接用自己的数据则必须这样验一遍。数据质量永远是一等公民模型只是把它放大出来给你看。希望这个习惯能帮到你。本文还有配套的精品资源点击获取