遥感图像分割数据集与7类语义分割全流程:从标签文件到SegFormer训练实践
发布时间:2026/10/1 13:11:40来源:尧图网络
简介针对遥感背景下建筑、山地、植被等典型地物的语义分割需求压缩包内提供了一套七类像素级标注数据。图像分辨率统一为1024×1024原图为jpg格式掩膜标签为png格式标注类别涉及背景、土地、建筑、农田、植被、水体等具体标签说明可在随包的txt类别文件中查看。数据集已划分为训练集与验证集其中训练集149张图片及对应掩膜验证集37张图片及对应掩膜结构清晰免去自行标注与整理的环节可直接用于语义分割模型的训练和效果对比。压缩包共375个文件除图片和标签外还包含类别定义txt与辅助脚本整体约47.03MB下载解压即可使用。目前已有166人学习适合需要快速获取遥感分割数据、开展算法实验或项目演示的研究者与开发者便于快速上手。1. 遥感图像分割数据集为什么标签文件才是 7 类分割项目的分水岭遥感图像分割数据集这个方向最容易被低估的是标签文件的价值——模型结构可以复现训练参数可以公开但一份类别边界干净、覆盖建筑、山地、植被等 7 类的标注文件才是决定分割模型上线精度的真正分水岭。下面围绕一套遥感背景下的 7 类语义分割数据集从类别体系设计、标签文件格式选型到基于 SegFormer 的训练落地再到标注质检与踩坑记录按一条完整可复现的路径展开。适合正在做遥感图像语义分割、需要自制数据集或打算用现成数据训练分割模型的研究生、算法工程师和 GIS 开发。2. 7 类语义分割的类别体系与标签文件格式定标准是训练前最重要的事2.1 七个类别怎么定建筑、山地、植被之外的类别取舍原则遥感影像里地物种类远不止 7 类真正做数据集时不能凭感觉堆类别而是按“互斥、完备、可标注”三个原则来筛。最常见的做法是把 7 类定成背景、建筑、山地、植被、水体、道路、农田。其中建筑、山地、植被是标题里点名的三个显性类剩下四类的取舍才是数据集质量的关键。先看建筑和山地、植被之间的边界怎么切。建筑在遥感影像里指有屋顶和有墙体的构筑物包括住宅、厂房、大型公共建筑但集装箱、活动板房、温室大棚经常和建筑长一个样必须在标注雷同里写明“温室大棚归农田工地临时板房归背景”。山地的定义要避开植被覆盖的语义交叉我一般把“自然地形抬升且植被覆盖度低于 40%”划归山地植被覆盖度高于 40% 的坡面归植被。裸岩、碎石坡、稀疏灌木都算山地。这套规则不写清楚两个标注员会在同一片山坡上给出完全不同的标签模型训出来边界一定乱。再看剩下四个类的取舍。水体和道路在遥感数据集中几乎必选因为它们是建筑和植被之外最容易识别、也最有应用价值的对象。水体包括河流、湖泊、水库和养殖塘静止水面按水体处理道路指硬化路面含街道和公路但建筑墙脚紧贴的硬化地面要跟建筑归并不能一个像素的路面就单独切开。农田和背景是两个“兜底”类农田覆盖水稻田、旱地、果园、大棚有明显耕作边界背景放广场、裸地、云影、无法归类的杂地。这 7 类之间的边界规则建议直接写进一份label_rule.md并配三张典型示例图。类别冲突重灾区是建筑与道路夹角、山地与植被过渡带、水体与阴影交界处这三处占标注返工量的八成。把规则前置到标注阶段省下来的时间远超训练时调模型的成本。对最终做语义分割数据集的人来说类别定义不是论文里的一句话而是每一条可执行的像素级判定标准。2.2 标签文件格式选型PNG 掩码、GeoJSON 矢量与 RLE 编码的对比语义分割数据集的标签文件在存储上有三种主流形态单波段 PNG 掩码、GeoJSON 矢量、RLE 游程编码。同一个 7 类数据集标签文件格式直接决定训练代码怎么写、二次修正怎么改、以及出问题后能不能“后悔”。格式物理形态训练友好度二次编辑典型问题PNG 掩码每张 mask 一张 PNG宽高与影像一致像素值为 0~6 整数直接进 DataLoader最快只能用像素画笔逐格改文件数量大不带地理坐标改一笔要重出GeoJSON 矢量一个 JSON 或 SHP存多边形顶点与类别属性训练前需要栅格化一次QGIS 里随时改多边形栅格化参数不对会错位RLE 编码每张图一串压缩字符串需要解码还原成二维 mask极难手工改调试时眼睛完全看不出形状在遥感背景的数据集里我一般用“矢量存底、PNG 出训练”的双轨策略标注阶段全都存 GeoJSON标注员在 QGIS 里勾多边形属性表里写类别编号发布训练集时用脚本把 GeoJSON 栅格化成与影像严格对齐的 PNG 掩码。GeoJSON 相当于整个数据集的后悔药——训练发现某类边界画错了回 QGIS 改几个多边形重新栅格化就行不用推倒重来。配套还需要一份class_dict.json把类别索引、类别名和 RGB 颜色对应起来。下面这段代码生成的标准 class_dict 可以作为数据集发布时的公共参考# build_class_dict.py import json class_names [ background, building, mountain, vegetation, water, road, farmland ] # 颜色尽量选色相差大、且避免纯白纯黑的 colors [ (0, 0, 0), # background (255, 0, 0), # building (0, 180, 0), # mountain (0, 255, 0), # vegetation (0, 0, 255), # water (255, 255, 0), # road (128, 64, 0), # farmland ] class_dict { str(i): {name: name, color: color} for i, (name, color) in enumerate(zip(class_names, colors)) } with open(class_dict.json, w, encodingutf-8) as f: json.dump(class_dict, f, ensure_asciiFalse, indent2) print(class_dict.json generated,, len(class_names), classes)这段代码的作用是把类别定义固化成机器可读的 JSON后续数据增强、可视化、评估脚本都从这一份文件读取类别名和颜色避免在多个脚本里各写一份硬编码。注意三个参数颜色表不能用 (255,255,255) 纯白和 (0,0,0) 纯黑作前景因为遥感影像里的高光屋顶和阴影区频繁出现这两种像素值质检时人眼容易看混类别名建议统一小写英文中文名放进一个附加字段避免编码问题索引从 0 开始连续编号背景固定为 0这样和 PyTorch 的 CrossEntropyLoss 默认映射一致。PNG 掩码本身建议存单波段、像素值直接等于类别索引0~6不要为了“看得见”去存 RGB 彩色图。原因很简单训练代码里 mask 读进来直接是LongTensor省去一次 RGB 到索引的映射而且 RGB 彩色图有 255 种组合转回类别索引时一旦出现压缩伪色标签就坏了。3. 用 SegFormer 训练遥感 7 类分割数据划分、训练代码与指标解读3.1 遥感影像裁剪为训练瓦片目录结构、滑窗与重叠参数遥感影像动辄上万像素宽直接整图训练显存放不下常规做法是滑窗裁成 512×512 或 256×256 的瓦片。裁剪本身不是无脑切overlap重叠参数必须设置否则跨越瓦片边界的建筑物会被拦腰截断模型训练时永远看不到完整轮廓推理时边界处会出现锯齿。推荐的目录结构如下dataset/ ├── images/ │ ├── train/ # 训练影像瓦片GeoTIFF │ └── val/ # 验证影像瓦片GeoTIFF ├── masks/ │ ├── train/ # 训练标签单波段PNG像素值0~6 │ └── val/ # 验证标签 ├── class_dict.json └── splits/ ├── train.txt └── val.txttrain.txt和val.txt里每行一个文件名不带扩展名DataLoader 按这个清单读取影像和 mask。目录与清单分离的好处是换划分方案时不用移动文件。用 GDAL 写裁剪脚本是遥感数据处理的基本操作代码如下# tiff2tiles.py from osgeo import gdal import os def tile_image(src_path, dst_img_dir, dst_mask_dir, tile_size512, overlap64): ds gdal.Open(src_path) width, height ds.RasterXSize, ds.RasterYSize band_count ds.RasterCount step tile_size - overlap # 滑窗步长 瓦片尺寸 - 重叠 count 0 for y in range(0, height - tile_size 1, step): for x in range(0, width - tile_size 1, step): count 1 tile_name f{os.path.basename(src_path)[:-4]}_{y}_{x} driver gdal.GetDriverByName(GTiff) dst_ds driver.Create( os.path.join(dst_img_dir, f{tile_name}.tif), tile_size, tile_size, band_count, gdal.GDT_Byte, ) # 保留地理变换与投影后续做标签对齐要用 dst_ds.SetGeoTransform(ds.GetGeoTransform()) dst_ds.SetProjection(ds.GetProjection()) for band in range(1, band_count 1): data ds.GetRasterBand(band).ReadAsArray(x, y, tile_size, tile_size) dst_ds.GetRasterBand(band).WriteArray(data) dst_ds None print(f{count} tiles generated from {src_path}) tile_image(raw/area1.tif, dataset/images/train, dataset/masks/train)裁剪脚本的逻辑是先用gdal.Open读原图宽高再按step tile_size - overlap从左到右、从上到下移动窗口。SetGeoTransform和SetProjection这两行不能省它们把地理坐标写到瓦片上后面做掩码栅格化和标签对齐时全靠这组信息。overlap64 在实际项目中是一个比较稳的默认值512 瓦片配 64 重叠约 14% 的像素会重复出现在相邻瓦片里这给模型提供了跨边界的上下文。如果原影像波段数多于 3比如含近红外裁剪脚本会自动把所有波段都写进瓦片。但训练时通常只取 RGB 三个波段多波段场景我一般先做波段选择再裁剪避免把数据量白白放大。3.2 训练脚本与关键参数以 SegFormer b0 为例SegFormer 是遥感分割里目前用得最多的模型之一在中低分辨率的多类别场景下对边界保持比 U-Net 好且预训练权重成熟拿来训练 7 类数据集很省事。训练脚本分两部分Dataset 类和训练循环。Dataset 类负责把瓦片和 mask 读进来、做数据增强、转 Tensor# dataset.py import os import numpy as np from PIL import Image import torch from torch.utils.data import Dataset import albumentations as A class RemoteSegDataset(Dataset): def __init__(self, img_dir, mask_dir, split_file, img_size512, trainTrue): self.img_dir img_dir self.mask_dir mask_dir self.img_size img_size self.train train with open(split_file, r, encodingutf-8) as f: self.samples [line.strip() for line in f if line.strip()] # 训练增强随机裁剪翻转亮度对比增强方向不变性 self.train_transform A.Compose([ A.RandomResizedCrop(img_size, img_size, scale(0.5, 1.0), p1.0), A.HorizontalFlip(p0.5), A.VerticalFlip(p0.5), A.RandomBrightnessContrast(brightness_limit0.2, contrast_limit0.2, p0.3), ]) self.val_transform A.Compose([ A.CenterCrop(img_size, img_size), ]) def __len__(self): return len(self.samples) def __getitem__(self, idx): name self.samples[idx] img np.array(Image.open(os.path.join(self.img_dir, name .tif)).convert(RGB)) mask np.array(Image.open(os.path.join(self.mask_dir, name .png)), dtypenp.int64) mask np.clip(mask, 0, 6) # 防止标签里偶然出现255等异常值 if self.train: augmented self.train_transform(imageimg, maskmask) else: augmented self.val_transform(imageimg, maskmask) img_t torch.from_numpy(augmented[image].transpose(2, 0, 1)).float() / 255.0 mask_t torch.from_numpy(augmented[mask]).long() return img_t, mask_t这个 Dataset 里值得注意的参数有三个RandomResizedCrop的 scale 范围设成 (0.5, 1.0)这意味着每张图会随机裁取原图 50% 到 100% 的区域并缩放到 512×512等于白送一层尺度增强对山区、农田这种尺度变化大的地物非常有效np.clip(mask, 0, 6)是为了拦截标签里可能出现的脏像素值防止训练时出现类别索引越界convert(RGB)把多波段 TIF 前三个波段取出来忽略近红外。训练循环用 Hugging Face Transformers 的 SegFormer 接口# train_segformer_remote.py import torch from torch import nn from torch.utils.data import DataLoader from transformers import SegformerForSemanticSegmentation from dataset import RemoteSegDataset NUM_CLASSES 7 LR 6e-5 EPOCHS 60 BATCH_SIZE 8 DEVICE cuda if torch.cuda.is_available() else cpu model SegformerForSemanticSegmentation.from_pretrained( nvidia/mit-b0, num_labelsNUM_CLASSES, ignore_mismatched_sizesTrue, ) model.to(DEVICE) train_ds RemoteSegDataset( dataset/images/train, dataset/masks/train, splits/train.txt, trainTrue, ) val_ds RemoteSegDataset( dataset/images/val, dataset/masks/val, splits/val.txt, trainFalse, ) optimizer torch.optim.AdamW(model.parameters(), lrLR, weight_decay0.01) scheduler torch.optim.lr_scheduler.CosineAnnealingLR(optimizer, T_maxEPOCHS) criterion nn.CrossEntropyLoss(ignore_index255) for epoch in range(EPOCHS): model.train() total_loss 0.0 for imgs, masks in DataLoader(train_ds, batch_sizeBATCH_SIZE, shuffleTrue): imgs, masks imgs.to(DEVICE), masks.to(DEVICE) outputs model(pixel_valuesimgs) loss criterion(outputs.logits, masks) optimizer.zero_grad() loss.backward() optimizer.step() total_loss loss.item() scheduler.step() avg_loss total_loss / len(train_ds) print(fepoch {epoch1}/{EPOCHS} loss {avg_loss:.4f}) torch.save(model.state_dict(), fcheckpoints/segformer_b0_ep{epoch1}.pth)训练脚本里四个关键配置直接影响收敛质量。ignore_mismatched_sizesTrue必须打开否则预训练分类头是 1000 类、换了 7 类后会报尺寸不匹配。ignore_index255配合第 5 章要讲的“云影区域标成 255”策略让模型完全忽略这些区域的梯度。学习率 6e-5 对 SegFormer b0 是一个实测稳妥的起点大于 1e-4 通常会在前 5 轮出现 loss 震荡。CosineAnnealingLR在遥感分割里几乎必用它能让学习率在后半段平滑下降避免 loss 在收敛点附近来回弹。3.3 mIoU、Kappa 与逐类 IoU遥感分割指标怎么读训练完只看 loss 没意义遥感分割的验收指标是逐类 IoU 和 mIoUKappa 系数作为辅助参考。评估代码按混淆矩阵计算这样既能得到总体指标又能定位到具体哪个类别拖后腿# evaluate_remote_seg.py import numpy as np import torch from torch.utils.data import DataLoader from tqdm import tqdm def compute_metrics(hist): eps 1e-6 iou_per_class np.diag(hist) / ( hist.sum(axis1) hist.sum(axis0) - np.diag(hist) eps ) miou np.nanmean(iou_per_class) total hist.sum() p0 np.diag(hist).sum() / total pe (hist.sum(axis0) * hist.sum(axis1)).sum() / (total * total) kappa (p0 - pe) / (1 - pe eps) return miou, iou_per_class, kappa def evaluate(model, val_loader, num_classes7): model.eval() hist np.zeros((num_classes, num_classes), dtypenp.int64) with torch.no_grad(): for imgs, masks in tqdm(val_loader): imgs imgs.to(DEVICE) logits model(pixel_valuesimgs).logits preds logits.argmax(dim1).cpu().numpy() masks masks.numpy() for p, m in zip(preds, masks): valid m ! 255 p p[valid] m m[valid] hist np.bincount( m * num_classes p, minlengthnum_classes * num_classes, ).reshape(num_classes, num_classes) miou, ious, kappa compute_metrics(hist) class_names [background, building, mountain, vegetation, water, road, farmland] for i, name in enumerate(class_names): print(f{name:12s} IoU {ious[i]:.4f}) print(fmIoU {miou:.4f}, Kappa {kappa:.4f})代码核心是混淆矩阵的累加方式m * num_classes p把“真实类别—预测类别”组合编码成一维索引bincount统计后 reshape 成 7×7 矩阵这一招比双重循环快一个量级。valid m ! 255是配合 ignore_index 策略把云影区域的像素从评估里剔除否则这些纯噪声像素会同时压低真阳率和假阳率让 mIoU 失真。读指标时有个常见误区mIoU 总体高不代表模型可用。比如山地和植被占了大头这两个类 IoU 高能把 mIoU 拉到 0.85但建筑 IoU 可能只有 0.35。所以在遥感项目里一定要打印逐类 IoU哪一类低于 0.5 就回到 5.2 的去处理类别不平衡。Kappa 对类别不平衡更敏感如果 Kappa 比 mIoU 低 0.1 以上说明多数类虚高、少数类崩盘这时候加权损失要马上跟上。4. 从原始遥感影像到 7 类标签文件数据制作的全流程4.1 影像预处理分辨率统一、波段合成与投影对齐数据集的影像来源五花八门有公开遥感影像、无人机正射影像、还有从地图服务里抓的静态图。预处理不做齐后面每走一步都是坑。第一件事是统一分辨率。0.5 米分辨率的无人机影像和 10 米分辨率的卫星影像混在一起同是“建筑”前者边缘轮廓清晰锐利后者糊成一片。模型会学到“清晰的是建筑模糊的是山地”这种完全错误的隐含关联。常见的处理方式是把所有影像重采样到同一个 GSD地面采样距离比如统一到 1 米或 2 米语义分割对分辨率没有检测那么敏感但 5 倍以上的分辨率差一定出问题。第二件事是波段合成。多数开源模型吃 RGB 三通道遥感多光谱影像通常选 R、G、B 三个波段直接合成 RGB如果后续要解决水体与阴影的混淆可以把近红外波段作为额外通道让模型在 4 通道输入下学习“水体在近红外强吸收”的特性。但这要求模型第一层接受 4 通道输入SegFormer 预训练权重是 3 通道改通道等于放弃预训练常见的做法仍是训练 RGB 三通道把 NIR 留给专门训练的模型。第三件事是投影与坐标对齐。不同来源的影像如果不在同一投影坐标系下做标签栅格化时点位会整体偏移。我的习惯是全部统一到目标区域的 UTM 分区投影用 GDAL 的gdal.Warp批量重投影到同一坐标系和分辨率然后把投影信息写进瓦片。这一步不做第 5 章要讲的标签错位问题会以最隐蔽的方式出现。4.2 标注工具选型与像素级质检流程7 类语义分割数据集的标注工作量取决于影像面积。几十张小图用 Labelme 逐张勾多边形可以胜任上千张瓦片就必须用支持多人协作的标注工具否则时间成本直接失控。常见做法是 QGIS 手工勾绘配合 CVAT 或 Supervisely 做半自动标注先用分割模型预测一版粗结果标注员在粗结果上修正边界效率比从零画多边形高 3 倍以上。标注规则里最容易出问题的是类别判定歧义。下表是一个可以直接抄走的标注判定表类别判定要点最容易混淆的邻类建筑有屋顶的构筑物含厂房屋顶道路、温室大棚山地自然抬升、植被覆盖度低于 40%裸地、植被植被乔木、灌木、草地等自然绿色覆盖农田、山地水体河流、湖泊、水库、养殖塘阴影、低反射屋顶道路硬化路面含街道、公路建筑墙脚、广场农田耕地、大棚作物区、果园植被、裸地背景广场、裸地、云影、无法归类的区域山地、道路质检流程必须独立于标注流程。我一般抽 10% 的瓦片做人工复查把预测 mask 以半透明色块叠加在原图上放大到 200% 检查边界。边界容差允许 1~2 像素如果同一批数据平均边界误差超过 3 像素整批退回重标。另一个容易被忽略的步骤是“交叉标定”让第二个标注员独立标注 5% 的影像算一次两人标注结果间的平均 IoU低于 0.9 说明标注规则本身有歧义先修改规则再继续批量标注。4.3 类别不平衡与数据增强哪些手段真的有效遥感 7 类分割里山地和植被常常占了总像素的 60% 以上建筑、道路加一起可能不到 10%。类别不平衡不解决模型会倾向于把所有像素预测成植被和山地mIoU 虚高但建筑类完全不可用。数据增强方面对遥感分割真正有效的是随机翻转、90° 旋转和随机裁剪缩放。遥感影像没有“上下”概念水平垂直翻转和旋转 90° 不损失语义这是比其他视觉任务更强的增强自由度。亮度对比度和高斯噪声只建议加一个加多了会让模型在真实推理时的色调偏移上失控。CutMix 或 Mosaic 这类混合增强在遥感多类别分割里效果不稳定容易把被剪切地物的边角打乱常见做法是用类别加权损失代替这类激进增强。类别不平衡的根治手段有两个。第一个是少数类过采样训练时对包含建筑、道路像素的瓦片提高采样概率比如统计每个训练瓦片的类别直方图含有小类的瓦片重复进 batch。第二个是类别加权损失把 CrossEntropy 的权重设成1 / sqrt(freq)频率越低权重越高简单有效。这两个手段要在训练脚本里同时用单靠一个撑不住建筑这类极少数类别。5. 遥感语义分割常见问题排查7 类数据里的 5 个翻车现场5.1 训练 loss 不降标签文件读进来为什么全是背景现象训练几个 epochloss 一直在高位徘徊不下降验证时所有预测结果都是背景类。原因最常见的是标签读取环节出了黑匣子错误。PNG 掩码如果是 16 位单波段 TIF用 PIL 的Image.open读进来默认当成 8 位处理高 8 位全丢像素值全部变成 0或者 GeoJSON 栅格化时窗口没对齐生成的 mask 整片是背景。解决在 Dataset 的__getitem__里加一条校验断言统计 mask 的类别直方图如果逐类像素数与预期不符直接报错。我习惯在每个 epoch 结束时打印一次训练集的类别分布表能立刻发现标签文件是否被错误读取。5.2 建筑 IoU 不到 0.5类别不平衡把梯度全吃掉了现象mIoU 有 0.8但逐类 IoU 打印出来建筑只有 0.3道路 0.4山地植被都超过 0.9。原因建筑和道路在整幅影像里占比太小交叉熵损失把绝大多数梯度贡献给了占有像素最多的植被和山地模型学不到小类的区分特征。解决先按 4.3 做类别加权损失和少数类过采样再来看指标。如果加了权重后建筑仍然上不去就要检查标注本身——建筑屋顶颜色和道路材质在可见光下本来接近如果标注边界又粗糙模型学到的特征会被噪声淹没。调试时单独取一批建筑密集的瓦片做可视化看预测边界是偏大还是偏碎。5.3 验证集很好、新影像全错同图泄漏与时相差异现象训练和验证集上的 mIoU 都很高但把模型部署到另一块区域、另一时相的影像上精度断崖式下跌。原因遥感影像存在强烈的空间自相关训练和验证如果来自同一景影像模型其实在记忆“这片区域的纹理”而不是在学“建筑长什么样”。另一种原因是时相差异——训练集全是夏季影像推理时给的是冬季影像植被、农田的外观差异会骗过模型。解决划分 train/val 时按“景”切而不是按像素切一景影像要么全在训练、要么全在验证保证验证集与训练集没有任何空间重叠。数据集要尽量覆盖 3 个以上不同时相、不同区域的影像让模型学到跨场景的泛化能力而不是记住某一块地的纹理。5.4 预测边界整体偏移投影不一致导致标签错位现象模型整体形状预测得不错但所有目标物的轮廓像被推了一格建筑边缘整体偏移十几个像素。这个偏移在不同瓦片上方向一致而且大小固定。原因影像瓦片和 mask 栅格化的投影基准不一致。比如影像在 Web Mercator 投影下载GeoJSON 用 UTM 坐标栅格化栅格化后 mask 与影像的像素网格没有对齐整个标签图发生了平移。解决在栅格化代码里严格使用与影像相同的地理变换参数。核对方法是取三到五个地面控制点——道路交叉口、建筑物角点——在影像和 mask 上对比像素坐标误差超过 2 个像素就要回查投影和重采样参数。这个问题在训练代码里几乎无法发现因为指标看起来只是整体偏低不会提示错位。5.5 阴影把水体带偏深色像素的类别歧义怎么解现象预测图在山区背光面出现大片水体实际上那是山体阴影反之部分深色水体被预测成植被或阴影。原因RGB 影像里阴影和水体在灰度上高度相似模型学到的是“深色像素水体”的简单规则。类别定义里没有“阴影”这个类所有深色像素被迫塞进水体和背景里模型只能在这两个类之间做没有意义的区分。解决两种路径。第一种是在标注阶段把山体阴影单独标记为忽略区mask 像素值设为 255训练时ignore_index255让模型不再被迫学阴影的归属。第二种是引入近红外波段水体在近红外波段吸收强烈而阴影区反射较强这个物理特征能在颜色之外把两者分开但需要模型支持多通道输入。对大多数 RGB 训练场景第一种方案是性价比最高的。6. 把 7 类遥感分割模型做到可交付可视化、矢量化与选型边界6.1 分割结果叠加原图用典型难例替代指标作为验收标准指标只是数字真正交付前要看图。我的习惯是每次训练完固定跑一组“硬样本”——建筑密集区、山地阴影区、水体边界、窄道路各选十张把预测 mask 叠加在原图上存档训练前后对比。下面是叠加可视化代码# visualize_overlay.py import cv2 import numpy as np def overlay_mask(img, pred, alpha0.5): color_map np.array([ [0, 0, 0], [255, 0, 0], [0, 180, 0], [0, 255, 0], [0, 0, 255], [255, 255, 0], [128, 64, 0], ], dtypenp.uint8) colored color_map[pred] return cv2.addWeighted(img, 1 - alpha, colored, alpha, 0)看可视化时重点看三处建筑物墙角是否被抹圆、窄道路是否有断点、山地植被过渡带是否出现椒盐噪声。这三类问题 mIoU 指标反映不出来但 GIS 交付时一眼就能看到。生态遥感指数计算这类下游任务也依赖分割结果里植被、水体的边界是完整连通的而不是破碎的。6.2 mask 转矢量连通域过滤与多边形抽稀参数GIS 交付通常需要把掩码转成矢量面。直接调用cv2.findContours会产出大量碎多边形必须做两个后处理连通域面积过滤和多边形抽稀。# mask2vector.py import cv2 import numpy as np def mask_to_polygons(label_mask, class_id, min_area50, epsilon_factor0.0005): binary (label_mask class_id).astype(np.uint8) contours, _ cv2.findContours(binary, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) polygons [] for cnt in contours: area cv2.contourArea(cnt) if area min_area: continue # 过滤碎块min_area按分辨率折算 epsilon epsilon_factor * cv2.arcLength(cnt, True) approx cv2.approxPolyDP(cnt, epsilon, True) polygons.append(approx.squeeze().tolist()) return polygonsmin_area50在 0.5 米分辨率下大约是 12.5 平方米对应一个小杂物间低于这个面积的连通域基本是预测噪声。epsilon_factor0.0005控制抽稀强度值越大轮廓越平滑但细小凸起丢失越多0.0005 是保留建筑直角特征的安全值。这两个参数必须按实际分辨率和交付精度要求重新折算不能照搬。6.3 语义分割与实例分割的选型边界热搜里经常看到 yolo26 中实例分割与语义分割的区别这种讨论在遥感项目里这两条路线要早做决定。本文这套 7 类方案是语义分割每个像素一个类别标签适合土地利用分类、面积统计、生态遥感指数计算这类“算面积、看分布”的任务。如果需求是“把每栋建筑单独框出来、数清楚有多少栋”语义分割给不了独立目标编号必须走实例分割路线比如 Mask R-CNN 或 YOLOv8-seg 这类模型。遥感语义分割数据集和实例分割数据集在标签形态上就是两类东西——一个是像素级掩码一个是逐目标的独立掩码和框做数据集之前先想清楚下游要什么能省掉一整轮返工。最后说一个这几年反复踩过的教训数据集里最容易出问题的不是模型而是标签文件与影像之间那层看不见的错位。每次跑完训练第一件事是把预测结果叠加到原图上用眼睛扫三分钟比任何指标都管用。这个检查习惯帮我挡掉过很多次边界偏移、类别混淆和过拟合假象。希望帮到你。本文还有配套的精品资源点击获取
网站建设高端定制企业官网