资讯动态

UNet遥感图像语义分割:毕设级全流程指南与避坑实战

发布时间:2026/10/14 11:38:44 来源:尧图企业网站定制
简介一套基于UNet的遥感图像语义分割毕业设计项目源码与论文齐整面向计算机视觉方向的学生与开发者可用于毕业设计、课程设计、期末大作业等场景。项目源码均本地编译通过且可运行评审分达九十八分难度适中内容经助教老师审定具备较强的参考与复用价值。资源包共六十八个文件大小约四十六点九四兆字节主要包含Python脚本数据处理、模型构建、训练预测、Jupyter笔记本演示创建数据集、训练、预测、论文相关的Tex与PDF文档以及PNG、SVG图表等辅助材料便于按模块开展学习。目前已有三百六十九人浏览学习读者可从完整源码、可运行示例、论文底稿与清晰目录结构中系统掌握UNet在遥感图像语义分割中的落地流程亦可直接改造用于其他分割任务。1. 基于UNet的遥感图像语义分割毕设选题为什么总是它每年临近毕业季总有一批人被同一个题目卡住基于UNet的遥感图像语义分割。这个题看起来到处都是教程真正动手做的时候才发现公开代码跑不通、数据格式对不上、loss不收敛、预测图全是噪点最后靠熬夜和玄学调参勉强交差。如果你也在做这个方向或者正准备选这个题这篇文章就是按你从头到尾会踩的坑来写的。遥感图像语义分割说白了就是让模型给每个像素打标签哪些是建筑、哪些是道路、哪些是水体、哪些是植被。UNet之所以成为这个任务的默认起点不是因为它最新而是因为它结构简单、对小样本数据友好论文和答辩都能讲清楚。它不依赖预训练权重也能从零训练出可用的分割结果这对没有GPU集群的本科生来说就是最大的优势。接下来我会从网络结构、数据准备、训练代码到答辩论文一条线讲完代码能直接复现参数给出经验值坑也提前标好。2. 从任务到模型遥感影像分割为什么认准UNet2.1 语义分割任务的核心矛盾与遥感影像的独特性语义分割不是简单的分类它要同时回答是什么和在哪里两个问题。一个224x224的遥感图像块如果是图像分类任务模型只需要输出一个类别标签但分割任务要求输出一个224x224的像素级类别矩阵每个像素都要有归属。这意味着模型必须在压缩特征提取语义信息的同时保留空间位置精度这两个目标天然冲突——网络越深感受野越大语义越强但下采样导致的空间信息丢失也越严重。遥感影像进一步放大了这个矛盾。以常见的0.3米分辨率卫星影像为例一栋房子的屋顶可能只占几十个像素一条乡村道路的宽度可能只有三五个像素。小目标多、边界模糊、地物类别之间光照差异大这是遥感分割区别于自然图像分割的三个典型特征。还有一个常被忽视的问题是影像幅面巨大通常几千乘几千像素不可能整图送入模型必须切块处理这让上下文信息的利用变得更加困难。与自然图像分割相比遥感影像还有一个显著差异地物分布极不均衡。水域可能占整幅图的40%而独立房屋可能只占0.5%。如果直接用交叉熵损失训练模型会倾向于把所有像素预测为占比最大的类别因为这样loss最小。这也是很多初学者第一次跑遥感分割时发现模型啥也不学只输出全图背景的根本原因。理解这个特殊性才能理解后面为什么要做数据增强、为什么要用加权损失。2.2 UNet的编码器-解码器结构与跳跃连接的真正作用UNet的命名来自它的U型结构左边是编码器右边是解码器中间靠跳跃连接拼接。编码器部分连续做卷积和下采样每经过一次下采样特征图尺寸减半、通道数翻倍这是为了逐步扩大感受野让高层特征能看到更大的范围。到最底层时特征图已经缩小到原始尺寸的1/16或1/32通道数可能达到512甚至1024这时候特征图承载的主要是这里是什么类别的语义信息。解码器做的事情刚好反过来连续做上采样和卷积把特征图逐步恢复到原始分辨率。上采样有两种常见做法转置卷积和双线性插值。转置卷积可学习但有棋盘效应双线性插值稳定但上采样后特征不够锐利。UNet的经典实现用的是转置卷积配合跳跃连接来缓解细节丢失但这不代表转置卷积一定优于插值后面训练代码里我会给出另一种选择。跳跃连接是UNet区别于FCN的核心设计。它把编码器第i层输出直接拼接到解码器对应层让解码器在恢复空间细节时能同时看到浅层的边缘纹理信息和深层的语义类别信息。对遥感影像来说这条跳跃连接几乎决定了小目标的分割质量——屋顶边缘、道路边界这些精细结构的信息主要存在浅层特征里如果没有跳跃连接解码器只能靠上采样插值来猜测边界位置结果就是分割图边缘像锯齿一样不平整。需要提醒的是UNet的层数选择不是越多越好。常用的深度是4次下采样对应特征图尺寸从原始分辨率降到1/16。如果你用ResNet34或ResNet50做编码器主干下采样次数可能不一样跳跃连接的对齐方式也要相应调整。用5次下采样时1/32特征图对于小目标来说可能已经丢失了太多细节在遥感场景里反而不如4次下采样效果好。2.3 损失函数选择交叉熵、Dice Loss与Focal Loss损失函数是训练分割模型最容易忽略却影响最大的一个环节。交叉熵对每个像素独立计算损失然后取平均计算简单、梯度稳定但它在类别不平衡时完全失效——如果背景占95%模型全预测背景就已经有5%的错误率再往其他类别上优化反而可能拉升总loss于是模型陷入局部最优不动了。Dice Loss的思路是直接优化分割结果与真实标注的重叠度公式是2倍交集除以并集。它对类别不平衡不敏感即使某类只占1%也能参与训练。但Dice Loss的梯度在预测概率接近0或1时容易变得非常陡峭训练前期模型还处于乱猜阶段时loss可能震荡得很厉害。实践中我见到最多的情况是单独用Dice Loss训练前几十个epoch loss像心电图然后骤降属于需要耐心等的那种。Focal Loss是在交叉熵基础上给难分样本加权的方案通过调整聚焦参数gamma让模型更关注那些预测概率低的像素。对于遥感图像中大量存在的难例——阴影遮挡的房屋、雨后泥泞的道路——focal loss通常比普通交叉熵更稳。实际项目里最常见的做法不是三选一而是把交叉熵和Dice Loss按1:1加权组合兼顾稳定性和不平衡鲁棒性。这个组合在公开的遥感分割数据集上是验证过最不挑超参的方案。3. 数据准备与预处理跑通UNet之前真正花时间的环节3.1 环境搭建版本选择背后的兼容性考虑在写任何模型代码之前先把环境固定下来。遥感分割项目最常用的框架搭配是Python 3.8或3.10PyTorch 1.12到2.x配合CUDA 11.3或12.1。如果你的电脑是NVIDIA显卡最好安装GPU版PyTorch训练速度差一个数量级CPU跑UNet不是不能跑但训练一个epoch可能要等十几分钟调参体验非常痛苦。我的建议是直接用conda创建一个独立环境python版本选3.10避免某些依赖包在3.11以上版本还有兼容问题。安装命令如下conda create -n unet_seg python3.10 -y conda activate unet_seg pip install torch torchvision --index-url https://download.pytorch.org/whl/cu118 pip install numpy opencv-python pillow matplotlib tqdm tensorboard pip install scikit-learn pandastorch和torchvision的版本必须匹配否则import会直接报错。如果你已经装了CPU版想换GPU版需要先pip uninstall torch torchvision再重装不要直接覆盖。数据集如果是TIFF格式的遥感影像还需要装gdal或rasterio来读取这两个库的安装依赖系统底层库Windows下建议直接用pip install rasterio它会自动拉预编译的wheel包。3.2 遥感数据集的获取与预处理管线公开的遥感分割数据集里最常见的是建筑、道路、水体这几类地物因为标注相对明确。以某公开的航空影像建筑分割数据集为例图像是RGB三通道对应标注是单通道的类别掩膜。做毕设时选择公开数据集是最稳妥的自己标注遥感图像的像素级标签工作量巨大一块512x512的影像标注一栋建筑的轮廓就要十几分钟。拿到数据后的第一件事是检查图像和标注是否对齐。我见过数据集里个别样本的图像是错的但标注对得上或者图像旋转了标注没跟着转的现象。写个简单的脚本把所有样本过一遍把图像和标注叠在一起保存成对比图半透明叠加看一眼就能发现问题。import cv2 import numpy as np from pathlib import Path img_dir Path(data/images) mask_dir Path(data/masks) save_dir Path(data/overview) save_dir.mkdir(exist_okTrue) for img_path in img_dir.glob(*.png): name img_path.stem mask_path mask_dir / f{name}.png if not mask_path.exists(): print(f{name}: mask missing) continue img cv2.imread(str(img_path)) mask cv2.imread(str(mask_path), cv2.IMREAD_GRAYSCALE) if img.shape[:2] ! mask.shape[:2]: print(f{name}: size mismatch {img.shape} vs {mask.shape}) continue colored cv2.applyColorMap(mask, cv2.COLORMAP_JET) overlay cv2.addWeighted(img, 0.6, colored, 0.4, 0) cv2.imwrite(str(save_dir / f{name}.jpg), overlay)这段代码做三件事检查标注是否存在、检查尺寸是否一致、生成半透明叠加预览图。如果标注缺文件或尺寸对不上后面训练时DataLoader会在随机裁剪时报错提前发现能省很多时间。叠加图建议全部人工扫一遍重点看有没有标注错位和类别标错的情况。预处理管线里还有一个关键参数是裁剪尺寸。遥感影像通常很大直接resize到小尺寸会丢失小目标不resize又放不进显存。常规做法是随机裁剪固定大小的图像块常见选择是256x256或512x512。512x512对显存要求高8GB显存跑UNet加batch size 4已经比较吃力256x256则可能让大目标被切碎上下文不足。没有绝对正确答案我的经验是先用256试试确认loss能收敛再考虑加大。3.3 数据增强遥感分割必做的五类操作数据增强不是锦上添花而是让小样本遥感分割跑起来的必要步骤。遥感图像有明确的上帝视角特性物体朝向没有统一的上下所以几何增强要比自然图像更激进。我总结必备五类随机水平翻转、随机垂直翻转、随机旋转90度/180度/270度、随机亮度对比度扰动、随机缩放裁剪。但有几个操作要特别谨慎随机旋转任意角度不是90度的倍数会引入插值伪影边界会变得模糊随机ranslate平移幅度过大可能把目标挪出图像块颜色抖动范围太大会让分割模型把颜色当成唯一线索真实场景泛化反而变差。把这些增强封装成一个类挂在训练集的Dataset里。import albumentations as A from albumentations.pytorch import ToTensorV2 train_transform A.Compose([ A.RandomCrop(256, 256), A.HorizontalFlip(p0.5), A.VerticalFlip(p0.5), A.RandomRotate90(p0.5), A.RandomBrightnessContrast(brightness_limit0.2, contrast_limit0.2, p0.3), A.Normalize(mean(0.485, 0.456, 0.406), std(0.229, 0.224, 0.225)), ToTensorV2(), ]) val_transform A.Compose([ A.CenterCrop(256, 256), A.Normalize(mean(0.485, 0.456, 0.406), std(0.229, 0.224, 0.225)), ToTensorV2(), ])这里用的是albumentations库它对分割任务的特殊之处在于所有增强操作会同步应用到图像和标注掩膜不需要你手动对齐坐标变换。Normalize的参数用的是ImageNet的均值和标准差这是很多遥感分割项目的默认选择。要注意的是如果你的训练集整体色调和自然图像差别很大——例如只有红外波段或只有灰度图——就不要硬套ImageNet统计值直接算自己数据集的mean和std会更好。4. 从零实现UNet遥感分割训练主流程与必调参数4.1 模型定义从UNet到带有BatchNorm的稳定版本网上能搜到的UNet实现版本很多但很多是直接搬运原始论文代码缺少BatchNorm和合适的初始化训练起来非常不稳定。我给出的版本是在经典结构上做了三处必要改动每个卷积块后加BatchNorm、激活函数从ReLU换成LeakyReLU、上采样用双线性插值替代转置卷积。import torch import torch.nn as nn class DoubleConv(nn.Module): def __init__(self, in_ch, out_ch): super().__init__() self.conv nn.Sequential( nn.Conv2d(in_ch, out_ch, 3, padding1, biasFalse), nn.BatchNorm2d(out_ch), nn.LeakyReLU(0.1, inplaceTrue), nn.Conv2d(out_ch, out_ch, 3, padding1, biasFalse), nn.BatchNorm2d(out_ch), nn.LeakyReLU(0.1, inplaceTrue), ) def forward(self, x): return self.conv(x) class UNet(nn.Module): def __init__(self, in_channels3, num_classes2): super().__init__() self.enc1 DoubleConv(in_channels, 64) self.enc2 DoubleConv(64, 128) self.enc3 DoubleConv(128, 256) self.enc4 DoubleConv(256, 512) self.pool nn.MaxPool2d(2) self.bottleneck DoubleConv(512, 1024) self.up4 nn.Upsample(scale_factor2, modebilinear, align_cornersFalse) self.dec4 DoubleConv(1024, 512) self.up3 nn.Upsample(scale_factor2, modebilinear, align_cornersFalse) self.dec3 DoubleConv(512, 256) self.up2 nn.Upsample(scale_factor2, modebilinear, align_cornersFalse) self.dec2 DoubleConv(256, 128) self.up1 nn.Upsample(scale_factor2, modebilinear, align_cornersFalse) self.dec1 DoubleConv(128, 64) self.out nn.Conv2d(64, num_classes, 1) def forward(self, x): e1 self.enc1(x) e2 self.enc2(self.pool(e1)) e3 self.enc3(self.pool(e2)) e4 self.enc4(self.pool(e3)) b self.bottleneck(self.pool(e4)) d4 self.dec4(torch.cat([self.up4(b), e4], dim1)) d3 self.dec3(torch.cat([self.up3(d4), e3], dim1)) d2 self.dec2(torch.cat([self.up2(d3), e2], dim1)) d1 self.dec1(torch.cat([self.up1(d2), e1], dim1)) return self.out(d1)模型的核心结构是4层编码、4层解码。编码器每层输出通道数翻倍解码器每层先把特征图放大再和对应编码层按channel维度拼接所以解码器输入通道是上采样输出通道加跳跃连接通道之和。比如dec4的输入是bottleneck上采样后的1024通道加e4的512通道共1536通道第一个卷积改成1536正好输出512通道。batch size和图像尺寸决定显存需求。这个模型参数量约3100万输入512x512的图batch size设为4时大约需要9到10GB显存。如果你的卡只有6GB把输入尺寸降到256并把batch size设为2基本能跑再不够就换成输入尺寸256、batch size 1加梯度累积。注意BatchNorm在batch size为1时统计量不稳定表现会明显变差至少用2。4.2 数据加载器与训练主循环评估指标别只用准确率训练代码最需要写清楚的是评估部分。很多同学只知道用准确率Accuracy评模型但分割任务里如果背景占95%准确率90%以上可能模型什么都没学会。语义分割的标准评估指标是mIoUmean Intersection over Union计算每个类别的预测结果与标注的交并比再取平均。这个指标对不平衡类别更敏感也是大多数遥感分割论文和竞赛用的指标。import torch from torch.utils.data import Dataset, DataLoader import numpy as np class SegDataset(Dataset): def __init__(self, img_dir, mask_dir, transformNone): self.img_paths sorted(list(img_dir.glob(*.png))) self.mask_paths sorted(list(mask_dir.glob(*.png))) assert len(self.img_paths) len(self.mask_paths) self.transform transform def __len__(self): return len(self.img_paths) def __getitem__(self, idx): img cv2.imread(str(self.img_paths[idx])).astype(np.float32) / 255.0 mask cv2.imread(str(self.mask_paths[idx]), cv2.IMREAD_GRAYSCALE) mask[mask 0] 1 # 二值分割背景0前景1 if self.transform: augmented self.transform(imageimg, maskmask) img augmented[image] mask augmented[mask].long() return img, mask def compute_miou(pred, target, num_classes2): pred torch.argmax(pred, dim1) ious [] for cls in range(num_classes): p (pred cls) t (target cls) intersection (p t).sum().float() union (p | t).sum().float() iou (intersection 1e-6) / (union 1e-6) ious.append(iou.item()) return np.mean(ious)Dataset的__getitem__里做了一件事把标注掩膜中所有非零像素统一改成1。这是因为数据集里有的标注背景标0、前景标255有的前景标1不做二值化统一的话模型输出两个类别但标注有三个值mIoU会乱套。如果你的数据集有多个类别就把这句去掉改成mask torch.from_numpy(mask).long()同时把model的num_classes改成实际类别数。训练循环中除了常规的前向和反向传播还需要注意两个细节保存最佳模型用mIoU而不是loss因为loss下降不代表分割效果好每个epoch结束后在验证集上评估验证时不要开梯度计算和数据增强图片要center crop而不是random crop保证结果可比。model UNet(in_channels3, num_classes2).cuda() criterion nn.CrossEntropyLoss() optimizer torch.optim.Adam(model.parameters(), lr1e-4, weight_decay1e-5) scheduler torch.optim.lr_scheduler.CosineAnnealingLR(optimizer, T_max50) best_iou 0.0 for epoch in range(50): model.train() train_loss 0.0 for images, masks in train_loader: images, masks images.cuda(), masks.cuda() outputs model(images) loss criterion(outputs, masks) optimizer.zero_grad() loss.backward() optimizer.step() train_loss loss.item() model.eval() val_ious [] with torch.no_grad(): for images, masks in val_loader: images, masks images.cuda(), masks.cuda() outputs model(images) val_ious.append(compute_miou(outputs, masks)) mean_iou np.mean(val_ious) if mean_iou best_iou: best_iou mean_iou torch.save(model.state_dict(), best_unet.pth) print(fEpoch {epoch}: loss{train_loss/len(train_loader):.4f}, mIoU{mean_iou:.4f})优化器选择Adam是遥感分割训练里最省心的方案初始学习率1e-4基本不需要调整比SGD对学习率的敏感度低很多。CosineAnnealingLR让学习率从1e-4沿余弦曲线降到接近0与手动每20轮衰减一半相比能更好收敛。训练50轮在小数据集上已经能看出明显效果如果数据集超过5000张图可以增加到80到100轮。每轮epoch后打印loss和mIoU如果mIoU连续10轮不升反降就加载best_unet.pth继续调不要跑完所有epoch才去看结果。4.3 训练中的关键参数经验值第一个要盯的是损失函数的收敛曲线。交叉熵loss在前几个epoch从较高的数值快速下降是正常现象如果loss不下降或下降极慢先检查学习率是否过大导致震荡再看数据增强是否太激进让模型学不到稳定特征。学习率从3e-4到1e-3对Adam来说是常见范围我从1e-4起步跑20轮后如果loss停在某个值不动再降学习率到5e-5续跑。第二个容易被忽略的是图像归一化位置。我见过有人把归一化写在DataLoader外面读图时直接除以255但到了GPU上又做了ImageNet归一化等于归一化了两次。归一化只需要做一次要么在transform里做要么在Dataset里做不要两边都做。第三个是类别权重的设置方法。如果选择用交叉熵而不做加权可以在损失函数里传一个weight参数权重按类别像素占比的倒数来设。计算方式很简单统计训练集每类像素总数用总像素数除以每类像素数就得到权重向量。例如背景占80%、前景占20%权重就是100/801.25和100/205前景权重远大于背景模型会更卖力地学习前景区域。5. 遥感分割避坑指南5个高频翻车现场与排查方法5.1 模型预测输出全黑或全白现象训练过程loss在下降但预测图全部是背景或者全部是前景完全没有任何结构信息。这个翻车现场在初学者项目里出现频率最高。原因根本原因是类别极端不平衡背景像素占比过高模型发现全预测背景就能把loss压到很低于是钻了这个空子。另一个常见诱因是标注掩膜里的类别值和模型输出约定不一致例如模型用softmax输出2类标注里却混着255这样的像素值导致计算交叉熵时把255当成一个额外类别。解决先在验证集上单独算每个类别的IoU确认模型是不是只学了一个类别。然后用加权交叉熵或Dice Loss替换普通交叉熵按像素占比倒数设置class weight。最后检查标注像素值打印np.unique(mask)把非0非1的值全部映射掉。做完这三步再重训通常问题能解决。5.2 训练loss下降但验证mIoU不涨现象训练集的loss从2.0降到0.1可验证集mIoU一直卡在0.4上下怎么调都不涨。这种过拟合现象在遥感分割项目里很典型。原因没有正常泛化的原因有两个方向一是数据划分有问题验证集和训练集来自同一张大幅影像的相邻切片训练时模型已经见过验证集中区域的特征二是数据增强不够模型把颜色分布当成分类依据到验证集上遇到底层颜色不同的同类目标就崩溃。还有一类隐蔽原因是验证集太小几十张图的mIoU波动很大看不出真实水平。解决基于地理区域的划分方式是按图切块划分而不是按随机样本划分类似大数据集的切割方式。同一幅来源影像的切块都放到同一集合里避免空间信息泄漏。数据增强上增加随机亮度扰动让模型不再依赖具体颜色。验证集至少保留200张图否则mIoU的标准差太大看不出优化效果。5.3 预测结果出现明显的棋盘格子或斑块现象预测图在物体边缘呈现出整齐的方块状纹理像马赛克或者分割结果里出现不应该存在的小斑块像是噪声。这类翻车属于模型结构问题而非数据问题。原因使用转置卷积上采样时卷积核如果不能被步长整除会导致重叠区域权重不均匀产生棋盘效应这是转置卷积的固有特性。小斑块则通常是CRF后处理或softmax温度设置不当导致的也可能是模型最后一层卷积输出通道数不对被view操作强行reshape产生的错乱。解决最直接的办法是把我前面模型代码里的nn.Upsample(modebilinear, align_cornersFalse)换成双线性上采样再用一个3x3卷积做语义融合这样能消除棋盘现象。如果一定要用转置卷积可以先把特征图四倍上采样到一个大尺寸再做卷积而不是直接两倍两倍地堆叠。小斑块问题可以用简单的形态学后处理对预测概率图做一次中值滤波或开闭运算能消掉大多数孤立噪点。5.4 显存溢出OOMbatch size调成1还是崩现象8GB显存batch size设为4直接报CUDA out of memory改成2也崩改成1勉强能跑但训练速度慢到怀疑人生。原因这个问题大概率不是batch size本身的问题而是输入图像尺寸太大了。常见误区是直接用原始遥感切片做训练比如2048x2048的图完全不裁剪那batch size为1也需要大量显存更何况UNet中间特征图的channel数很大显存占用在编码器最深处达到峰值。另一个容易忽略的地方是PyTorch的默认benchmark模式没有开启导致卷积选择的算法不稳定且浪费显存。解决先把输入尺寸固定为256x256或512x512用random crop切块再进模型。然后开启torch.backends.cudnn.benchmark True这行代码能让cuDNN自动选择最优卷积算法显存占用和速度都有改善。如果还是不够可以用梯度累积模拟更大的batch size每批次算完梯度不立即更新累积几步再更新显存开销不变但等效batch size变大训练更稳。5.5 毕设论文写作时发现代码结果和论文结论对不上现象跑通了代码也出了分割图但论文里的实验数据怎么都复现不出来补实验的时候发现模型参数没保存或者某个类别的IoU和初稿里写的不一样。这个坑很多人到答辩前一周才意识到。原因根源在于训练和实验记录不规范。跑实验时不记录随机种子、不固定数据划分方式、不记录每次实验的配置参数导致两次训练结果有差异。有些情况下是验证时用了不同的预处理管线归一化统计量或裁剪方式不一致模型性能自然不同。这些都是训练代码里模棱两可的参数设置导致的。解决从第一天训练就养成分步保存的习惯固定随机种子保存完整训练配置到配置文件中。我自己的标准做法是把模型参数、优化器参数、数据划分方式、随机种子、最终mIoU一并记录在项目目录下每条实验记录一个配置文件。答辩复现实验时直接加载配置文件重跑绝对不会出现上次的结果和这次不一致的问题。6. 把项目从60分做到90分消融实验与特殊场景验证一个UNet跑通训练流程并输出预测图对毕设来说只完成了基础部分。有区分度的项目通常围绕三个方向做增量编码器换主干、损失函数改进、后处理优化。从这三个方向入手每个方向做一个改动并记录mIoU变化论文的消融实验章节就有着落了。第一个值得做的是把编码器从原始的4层卷积换成带有预训练权重的ResNet34或ResNet50。UNet的编码器是从零训练的没有ImageNet预训练特征收敛慢且最终精度偏低。ResNet50作为UNet编码器时解码器通道需要配合调整跳跃连接从ResNet的不同stage引出。如果显卡显存够建议做一组对比原始UNet vs ResNet34-UNet vs ResNet50-UNet往往mIoU能提升5到10个百分点。这个实验是遥感分割论文里最常见的可视化材料也能体现你对模型结构的理解深度。第二个方向是边界优化。语义分割的一大难点是小目标的边界精度建筑物边缘多一个像素或少一个像素在遥感评估里IoU差异很大。常见做法是在解码器的最后一层旁边接一个边界预测分支用边缘检测的辅助损失约束共享编码器让网络在学习分类的同时学习哪里是边界。这样主分割logits和边界logits共享低层特征训练收敛后主观视觉上边界会锐利很多对这个方向的改进可以体现在论文的消融实验表格里。第三个方向是推理时的滑窗预测。大幅遥感影像在预测时需要切成小块每个小块独立经过模型再拼接。直接拼接会在小块边界产生明显的拼接痕迹正确做法是滑窗时设置重叠区域对重叠部分的预测概率做加权平均越靠近窗口中心的像素权重越高。这个技巧属于做了看不见但工整的工作论文里可以放一张拼接对比图说明重叠滑窗对消除边界伪影的有效性。在代码上实现时思路是在重叠区域用高斯权重合并每个像素的最终概率 该像素被各窗口预测的概率乘以窗口权重再归一化。此外还有一个细节容易被忽略预测的置信度校准。模型的输出logits经过softmax后是概率分布但遥感分割项目里的置信度和真实精度往往不对等。如果模型对某个像素输出0.7的建筑概率这个像素实际属于建筑的可能性未必是70%。不要直接用0.5作为二分类阈值可以在验证集上遍历阈值选mIoU最高的那个作为最终推理阈值。不同类别的类间混淆模式不同每个类别单独找最优阈值往往还能再提升1到2个点的mIoU。最后我始终建议把训练过程的每个中间环节都沉淀为可视化图表loss曲线、mIoU曲线、连续多个epoch的预测对比图这些既是调试手段也是论文中证明方法有效性的重要素材。我自己做这个方向时最深刻的教训是数据准备阶段多花一周整理训练阶段能少花一个月调参。所有翻车现场追根溯源几乎都发生在数据格式和划分方式上而不是模型结构上。希望这篇文章能帮你把这个方向做扎实无论是代码实现还是论文写作都能少走弯路。希望帮到你。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑