尧图网络 高端网站定制 · 原创设计
免费咨询热线
400-888-6620
免费获取方案
PyTorch高分遥感语义分割:GF2影像地物分类的U-Net实践与工程避坑
简介面向遥感影像智能解译的PyTorch语义分割项目实践资源基于高分遥感数据完成地物分类适合算法工程师、科研人员及深度学习初学者参考。压缩包共858个文件、约548MB含35个Python脚本覆盖模型训练、膨胀预测、后处理及半监督伪标签方法并加入TensorBoardX可视化输出819张PNG图像保存了遥感样本、预测结果、融合对比和标注叠加图便于直观评估分割效果另有CSV清单与README说明。已有3040人学习这套资料的价值在于把理论实现与工程化细节结合既有可直接运行的源码和说明也有丰富的预测对比图能帮助读者快速复现高分遥感地物分类流程并深入理解半监督策略与后处理调优。读者可对照样例数据观察模型逐步输出学习如何利用膨胀预测改善目标边界、以后处理消除细小噪声并通过伪标签拓展无标注数据的使用场景从而掌握从数据准备到结果可视化的完整工程链路。1. PyTorch实现高分遥感语义分割一份能直接落地的地物分类工程用PyTorch做高分遥感语义分割第一个坑往往不在模型而在数据组织方式。高分二号这类卫星影像单景动辄上万像素整图喂进网络先爆显存切成小图又会在边缘丢上下文预测结果像打了补丁。这份项目实践把GF2多光谱影像切成约380像素的样本块用U-Net做地物分类训练推理时用膨胀预测消除边缘伪影再配合伪标签半监督和TensorBoardX可视化恰好把这条链路完整走通了。如果你正在做遥感语义分割课程设计、人工智能大作业或者刚入门遥感加深度学习想找一个骨架工程这份源码包值得拆开看看。这里有个反直觉的点项目里真正值钱的不是U-Net本身而是样本怎么切、预测怎么拼、类别不平衡怎么处理。同样的网络结构这些细节直接决定mIoU是0.6还是0.82。下面从数据准备、模型搭建、训练配置到推理后处理和半监督扩展逐层拆解这份项目并给出可复现的代码和踩坑记录。2. 数据准备GF2影像切块、标签对齐与类别分布2.1 先看懂GF2文件命名与数据组织拿到项目第一件事不是跑训练而是把文件命名读明白。项目根目录下有GF2_PMS2_E116.0_N39.1_20170302_L1A0002214760-MSS2.csv、classes_sample.jpg以及一批GF2_PMS2_E116.0_N39.1_20170302_L1A0002214760-MSS2_NNN.png格式的影像块。GF2指高分二号卫星PMS2是卫星搭载的两台多光谱相机中的第二台E116.0_N39.1是影像中心经纬度在东经116度、北纬39.1度附近20170302是成像日期L1A表示数据经过了辐射校正MSS2对应多光谱传感器包含蓝、绿、红、近红外四个波段。文件名最后的三位数字是切块编号说明原始大图已经被切成了若干块用于训练和推理。CSV文件通常记录的是每个样本的名称、标签路径或类别统计信息。建议打开CSV先做一次探索性分析统计每类的像素占比import pandas as pd from PIL import Image import numpy as np df pd.read_csv(GF2_PMS2_E116.0_N39.1_20170302_L1A0002214760-MSS2.csv) print(df.head()) print(df.columns.tolist()) label_paths df[label].tolist() class_pixels {} for lp in label_paths[:20]: mask np.array(Image.open(lp)) for cls in np.unique(mask): class_pixels[cls] class_pixels.get(cls, 0) (mask cls).sum() print(class_pixels)这段代码帮你快速掌握两个关键信息CSV字段结构以及每个地物类别在样本中的像素占比。拿到类别比例后才能决定要不要做类别权重或重采样。如果某一类占比低于5%训练时大概率会被多数类淹没后面损失函数部分会专门处理。classes_sample.jpg是类别样例图建议打印出来对照着看。地物分类的标签一般用整数索引比如0代表背景、1代表水体、2代表植被、3代表建筑、4代表道路、5代表裸地具体含义以项目里的类别表或README说明为准。遥感影像的标签容易有错标和漏标先看样例图能帮你判断标签质量质量差的话再好的模型也白搭。2.2 构建Dataset图像、标签与transform同步写语义分割训练第一个要避免的翻车是图像和标签没有用同一个transform。随机翻转时只翻了图像标签没翻模型会在错位的监督信号里反复震荡。正确做法是把所有几何增强同时作用到img和mask上import torch from torch.utils.data import Dataset class GF2SegDataset(Dataset): def __init__(self, df, img_dir, mask_dir, transformNone): self.df df self.img_dir img_dir self.mask_dir mask_dir self.transform transform def __len__(self): return len(self.df) def __getitem__(self, idx): row self.df.iloc[idx] img Image.open(f{self.img_dir}/{row[image]}) mask Image.open(f{self.mask_dir}/{row[label]}) if self.transform: img, mask self.transform(img, mask) return img, mask实际项目里transform建议用albumentations库它原生支持img和mask同步变换比手写torchvision的Compose省心很多。归一化操作只需要对图像做mask保持整数标签即可。有一个细节容易忽略PyTorch的CrossEntropyLoss要求标签是long类型且取值在0到num_classes-1之间。很多人在读取mask后忘了转dtype训练到一半报RuntimeError原因就是float类型被当成了回归目标。在Dataset返回前强制加上mask torch.as_tensor(np.array(mask), dtypetorch.long)能省掉后面一半的排查时间。2.3 滑窗切块重叠切块是膨胀预测的基础高分影像整图推理不现实滑窗切块是最常见的做法膨胀预测就是重叠切块在推理阶段的延伸。切块参数有两个patch_size决定单次输入尺寸stride决定块与块之间的步长。stride等于patch_size时不重叠速度快但预测图块间会出现拼接缝stride小于patch_size时块与块有重叠每个像素被多个块预测过取平均后边缘过渡自然。import numpy as np def sliding_window(img, patch_size256, stride128): h, w img.shape[:2] patches [] coords [] for y in range(0, h - patch_size 1, stride): for x in range(0, w - patch_size 1, stride): patches.append(img[y:ypatch_size, x:xpatch_size]) coords.append((y, x)) # 处理右边缘和下边缘的残余区域 if (h - patch_size) % stride ! 0: for x in range(0, w - patch_size 1, stride): y h - patch_size patches.append(img[y:ypatch_size, x:xpatch_size]) coords.append((y, x)) if (w - patch_size) % stride ! 0: for y in range(0, h - patch_size 1, stride): x w - patch_size patches.append(img[y:ypatch_size, x:xpatch_size]) coords.append((y, x)) if (h - patch_size) % stride ! 0 and (w - patch_size) % stride ! 0: patches.append(img[h-patch_size:h, w-patch_size:w]) coords.append((h-patch_size, w-patch_size)) return patches, coords这段代码的关键是边缘处理。slide遍历从0开始如果原图尺寸不是patch_size的整数倍右侧和下侧会残留一条窄带。我的习惯是先把原图pad到patch_size的整数倍padding模式选reflect遥感影像的零填充会在边界引入一圈黑边影响归一化后的统计量。切块尺寸建议和训练时保持一致比如训练用256推理也切256不要训练一个尺寸推理另一个尺寸否则分布偏移直接反映在mIoU上。2.4 数据增强的取舍别把光谱特征增强没了遥感语义分割的增强策略和自然图像不完全一样。几何增强相对安全水平翻转、垂直翻转、随机旋转90度的整数倍都不会破坏地物语义可以放心加。随机旋转任意角度要慎重因为遥感图像虽然有旋转不变性但切块后任意角度旋转会引入插值噪声边缘出现混叠。颜色增强要区分场景。RGB合成图上做亮度、对比度、饱和度抖动对提升泛化有一定帮助但对多光谱波段做随机颜色抖动会破坏地物光谱特征。近红外波段对植被和水体的响应非常敏感把近红外通道的像素值随机调亮调暗模型学到的可能不是地物本身而是亮度伪影。我常用的遥感增强组合是随机水平翻转加随机垂直翻转加随机旋转90度的倍数加微量的高斯噪声。最后一项强度控制在sigma0.01以内目的是让模型对传感器噪声更鲁棒增强过度反而会掩盖地物边缘。3. 模型搭建U-Net结构与PyTorch实现细节3.1 选型对比为什么是U-Net而不是DeepLabV3地物分类可选的语义分割模型很多DeepLabV3在Cityscapes这类自然图像上表现强势但放在GF2遥感小样本场景里U-Net往往更实用。首先是参数量基础版U-Net约30M参数DeepLabV3用ResNet101做backbone直接到60M以上样本量只有几百张时大模型更容易过拟合。其次是细节保留U-Net的跳连接把编码器浅层的空间细节直接拼到解码器建筑边缘、道路边界这类精细结构的恢复效果比ASPP空洞卷积更直接。DeepLabV3的优势是多尺度感受野但遥感影像的地物尺度跨度极大一栋房子可能占20个像素也可能占200个像素仅靠ASPP的固定膨胀率覆盖不全。U-Net通过逐层下采样天然构建了多尺度特征金字塔配合跳连接做特征融合对尺度差异的容忍度更高。最后是改造灵活度U-Net加注意力模块、换backbone、做深度监督都很容易作为课程设计和项目实践调试成本低很多。3.2 核心实现双卷积块与跳连接这里给出一个标准U-Net的PyTorch实现通道配置采用经典的64到1024结构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.ReLU(inplaceTrue), nn.Conv2d(out_ch, out_ch, 3, padding1, biasFalse), nn.BatchNorm2d(out_ch), nn.ReLU(inplaceTrue) ) def forward(self, x): return self.conv(x) class UNet(nn.Module): def __init__(self, in_channels3, num_classes6, base_channels64): super().__init__() self.enc1 DoubleConv(in_channels, base_channels) self.enc2 DoubleConv(base_channels, base_channels * 2) self.enc3 DoubleConv(base_channels * 2, base_channels * 4) self.enc4 DoubleConv(base_channels * 4, base_channels * 8) self.pool nn.MaxPool2d(2) self.bottleneck DoubleConv(base_channels * 8, base_channels * 16) self.up4 nn.ConvTranspose2d(base_channels * 16, base_channels * 8, 2, stride2) self.dec4 DoubleConv(base_channels * 16, base_channels * 8) self.up3 nn.ConvTranspose2d(base_channels * 8, base_channels * 4, 2, stride2) self.dec3 DoubleConv(base_channels * 8, base_channels * 4) self.up2 nn.ConvTranspose2d(base_channels * 4, base_channels * 2, 2, stride2) self.dec2 DoubleConv(base_channels * 4, base_channels * 2) self.up1 nn.ConvTranspose2d(base_channels * 2, base_channels, 2, stride2) self.dec1 DoubleConv(base_channels * 2, base_channels) self.out nn.Conv2d(base_channels, 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)关键参数有三个。第一个是in_channelsGF2如果只用RGB三波段就设3如果用满四个多光谱波段就设4具体由你的数据决定。第二个是num_classes对应地物类别数量根据CSV类别表来常见是5到8类。第三个是base_channels默认64如果显卡显存只有4G以下把base_channels改成32模型参数量直接降到原来的四分之一mIoU下降通常不超过1到2个点对小样本数据集影响很小。conv层里biasFalse是因为后面接BatchNormBN自带偏置项卷积再加bias属于冗余。解码器上采样用的是ConvTranspose2dkernel_size2stride2输出尺寸正好翻倍。3.3 尺寸对齐问题奇数尺寸引发的血泪史U-Net对输入尺寸有要求每层MaxPool2d(2)会让特征图尺寸减半如果输入是奇数尺寸到深层后会出现上采样尺寸和下采样特征尺寸不一致的情况。比如输入257像素经过四次pool变成16像素257除以2向下取整三次再上采样回256和编码器分支的16像素差1torch.cat时直接报size mismatch。规避办法是在Dataset初始化时把图像统一resize到偶数尺寸比如256或384。不要resize到380这种看起来是偶数但不一定是2的4次方倍数的尺寸。最稳妥的做法是让输入尺寸能被16整除因为U-Net编码器有4次下采样256、384、512都是安全选择。项目里的影像块编号正好是380380不能被16整除如果原版工程直接用了380尺寸切块代码里应该隐式做了padding或resize自己复现时注意这一点否则会卡在维度不匹配上。4. 训练配置损失函数、评估指标与TensorBoardX可视化4.1 损失函数CrossEntropy加Dice的组合地物分类中直接用CrossEntropyLoss会遇到类别不平衡问题。GF2影像里背景和裸地可能占了60%以上建筑、水体这类关键地物占比很低模型会倾向于把一切都预测成多数类。常见做法是给CE Loss加类别权重权重取类别频率的倒数再用Dice Loss辅助class CEDiceLoss(nn.Module): def __init__(self, num_classes, weightsNone, ignore_index255): super().__init__() self.ce nn.CrossEntropyLoss(weightweights, ignore_indexignore_index) self.num_classes num_classes self.ignore_index ignore_index def forward(self, pred, target): ce_loss self.ce(pred, target) pred_softmax torch.softmax(pred, dim1) dice_loss 0.0 valid_classes 0 for cls in range(self.num_classes): p pred_softmax[:, cls, ...] t (target cls).float() if (t.sum() 0): continue intersection (p * t).sum() dice_loss 1 - (2 * intersection 1e-6) / (p.sum() t.sum() 1e-6) valid_classes 1 return ce_loss dice_loss / max(valid_classes, 1)类别权重weights怎么算统计训练集所有mask中每类的像素总数用total_pixels除以(num_classes乘以class_pixels)得到初始权重再做归一化。注意ignore_index要设置成255或-1用于跳过伪标签中不可靠的像素这到半监督部分会用到。Dice Loss的改进点在于它对类别不平衡不敏感即使是占比极小的类别只要有交集梯度就不会消失。但纯Dice Loss在训练初期容易不稳定因为预测概率和真实标签完全没有交集时梯度为零所以用CE加Dice的加权和让CE负责引导方向Dice负责细化边界。4.2 评估指标用mIoU和每类IoU盯问题语义分割的论文指标基本都用mIoU不用像素准确率。像素准确率在类别不平衡时严重失真背景占80%的情况下模型全预测背景都有80%准确率但建筑、道路一个没分出来这样的模型没有实际价值。mIoU对每个类计算交并比再取平均小类别的表现直接反映到分数上def compute_miou(pred, mask, num_classes): iou_list [] for cls in range(num_classes): p (pred cls) t (mask cls) intersection (p t).sum().item() union (p | t).sum().item() if union 0: iou_list.append(float(nan)) else: iou_list.append(intersection / union) return np.nanmean(iou_list) def compute_per_class_iou(pred, mask, num_classes): return [((pred c) (mask c)).sum().item() / ((pred c) | (mask c)).sum().item() if ((pred c) | (mask c)).sum().item() 0 else None for c in range(num_classes)]每类IoU比平均mIoU更有诊断价值。如果水体IoU特别低先去看是不是样本太少如果道路IoU低大概率是标签噪声大道路边缘标注困难导致预测和标签在边缘处对不齐从而拉低IoU。验证时建议每个epoch计算一次mIoU并记录到TensorBoard同时把验证集上表现最差的三张图保存下来人工检查这比单纯看曲线更能定位问题。4.3 训练循环与优化器配置训练循环里值得注意的有三个地方优化器、学习率调度、梯度裁剪。遥感小样本训练用AdamW比Adam稳weight_decay设1e-4初始学习率从1e-4起步用余弦退火或者ReduceLROnPlateau。梯度裁剪是很多人忽略的点语义分割的loss通常比较大尤其是Dice Loss在早训练阶段可能出现梯度爆炸clip_grad_norm_可以有效防止import torch from torch.cuda.amp import GradScaler, autocast model UNet(in_channels3, num_classes6).cuda() optimizer torch.optim.AdamW(model.parameters(), lr1e-4, weight_decay1e-4) scheduler torch.optim.lr_scheduler.CosineAnnealingLR(optimizer, T_max50) scaler GradScaler() criterion CEDiceLoss(num_classes6) for epoch in range(epochs): model.train() for img, mask in train_loader: img, mask img.cuda(), mask.long().cuda() optimizer.zero_grad() with autocast(): pred model(img) loss criterion(pred, mask) scaler.scale(loss).backward() scaler.unscale_(optimizer) torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm5.0) scaler.step(optimizer) scaler.update() scheduler.step()混合精度训练在GF2数据上很值得开推理速度提升明显但务必用GradScaler配合autocast否则容易溢出。BatchNorm在训练和推理时行为不同model.eval()之后BN统计量切换为running_mean和running_var如果val阶段忘了切model.eval()预测结果会很不稳定。这个小坑在遥感多类分割上尤其明显因为输入影像的统计特性差异大。4.4 TensorBoardX可视化不只看loss曲线项目摘要里强调加入了TensorBoardX可视化输出。调用方式如下from tensorboardX import SummaryWriter writer SummaryWriter(log_dirruns/gf2_seg) writer.add_scalar(loss/train, loss.item(), global_step) writer.add_scalar(iou/val, val_miou, epoch) writer.add_image(input, img_denorm, epoch) writer.add_image(gt, mask_colorize, epoch) writer.add_image(pred, pred_colorize, epoch) writer.add_scalar(lr, optimizer.param_groups[0][lr], epoch)可视化mask有个常见翻车直接把0到5的整数标签传给add_image显示一整片黑。因为TensorBoard对float类型默认按0到1范围显示0到5的数值全部超出范围变成白色或黑色。解决办法是手动把mask映射到0到255的RGB伪彩色或乘以int(255 / num_classes)再转uint8。可视化对象推荐处理方式输入影像反归一化后转HWC uint8标签mask乘255/num_classes转uint8或转伪彩色预测mask同标签mask用同一色彩映射表原图预测叠加alpha混合blend.png就用这种方式生成建议每5个epoch记录一次验证集的可视化结果不要每个epoch都写否则TensorBoard文件体积膨胀加载变慢。项目里提到的blend.png和img_gt_pre.png正是这种可视化的产物前者把原图和预测混合显示后者把图像、标签、预测三图并排用来做课程设计报告插图非常合适。5. 推理与后处理膨胀预测、多尺度融合与结果拼接5.1 膨胀预测重叠推理消除拼接缝膨胀预测是遥感语义分割推理阶段最常用的技巧项目摘要里也明确提到了。思想很简单推理时切块的步长小于patch尺寸让相邻块有重叠区域每个像素被多个块先后预测最后把多组概率取平均消除接缝效应。预测时patch边缘通常比中心差因为卷积感受野跨出了patch边界缺少上下文重叠区域取平均能有效缓解边缘质量差的问题。def predict_expand(model, img_tensor, patch_size256, stride128, num_classes6): model.eval() n, c, h, w img_tensor.shape prob_sum torch.zeros((n, num_classes, h, w), deviceimg_tensor.device) count torch.zeros((n, num_classes, h, w), deviceimg_tensor.device) for y in range(0, h - patch_size 1, stride): for x in range(0, w - patch_size 1, stride): patch img_tensor[:, :, y:ypatch_size, x:xpatch_size] with torch.no_grad(): prob torch.softmax(model(patch), dim1) prob_sum[:, :, y:ypatch_size, x:xpatch_size] prob count[:, :, y:ypatch_size, x:xpatch_size] 1 prob_avg prob_sum / count.clamp(min1) pred torch.argmax(prob_avg, dim1) return predstride的取值直接影响推理质量和速度。stride越小重叠区域越大每个像素被预测的次数越多但推理耗时成倍上涨。常见做法是patch_size256时stride取128或64前者每像素被预测4次后者16次。我的经验是64次的边际收益已不明显128是性价比最高的选择。如果边缘仍有细线状接缝可以把stride降到64再对比一次但不要小于64再小就是纯堆算力了。5.2 多尺度推理一张图预测三遍膨胀预测解决了空间连续性多尺度推理解决的是地物尺度差异。同一景GF2影像里大型水体占几千像素小路只占几十像素固定感受野很难同时兼顾。常见做法是把输入缩放到0.75、1.0、1.25倍三个尺度分别预测再把概率图上采样回原尺寸取平均def predict_multiscale(model, img_tensor, scales(0.75, 1.0, 1.25), patch_size256): model.eval() _, _, h, w img_tensor.shape final_prob torch.zeros((1, num_classes, h, w), deviceimg_tensor.device) for scale in scales: new_h, new_w int(h * scale), int(w * scale) scaled_img torch.nn.functional.interpolate( img_tensor, size(new_h, new_w), modebilinear, align_cornersFalse ) prob predict_prob_expand(model, scaled_img, patch_sizepatch_size) final_prob torch.nn.functional.interpolate( prob, size(h, w), modebilinear, align_cornersFalse ) return torch.argmax(final_prob / len(scales), dim1)这段代码里predict_prob_expand是上面膨胀预测函数返回概率图的变体。注意多尺度推理会把推理时间乘以3所以在验证阶段先单尺度验证确定模型本身收敛了再上多尺度否则调参效率太低。多尺度通常能提升1到3个点mIoU不算特别夸张但对于比赛或课程设计冲刺这1到3个点可能决定了分数档次。5.3 结果后处理去噪、连通域过滤与可视化输出模型输出的hard mask直接看会有椒盐噪声个别类别上还有孤立的小碎块。常用后处理链路是中值滤波、形态学开运算、连通域面积过滤import cv2 import numpy as np def postprocess_mask(mask, kernel_size5, min_area100): mask mask.astype(np.uint8) mask cv2.medianBlur(mask, kernel_size) for cls in np.unique(mask): cls_mask (mask cls).astype(np.uint8) cls_mask cv2.morphologyEx(cls_mask, cv2.MORPH_OPEN, np.ones((3, 3), np.uint8)) num_labels, labels, stats, _ cv2.connectedComponentsWithStats( cls_mask, connectivity8) for i in range(1, num_labels): if stats[i, cv2.CC_STAT_AREA] min_area: labels[labels i] 0 mask np.where(labels 0, cls, mask) return mask中值滤波kernel_size取5是折中值取3去噪能力弱取7会让道路和细小水体变模糊。连通域过滤min_area要根据影像分辨率调整0.8米分辨率下100个像素对应约64平方米实际取多少视地物而定。开运算能去掉小碎块但窄长地物比如道路和河流容易被开运算切断我的习惯是先开运算再连通域填充最后检查道路类是否出现断裂如果断裂就把kernel从3x3降到1x1或者只做中值滤波和面积过滤跳过开运算。项目目录里提到的blend.png和img_gt_pre.png是结果可视化的两种格式。img_gt_pre是三张图横向拼接原始影像、真实标签、预测结果。blend.png是把原图和预测结果做半透明叠加便于观察预测地物和实际地物的贴合程度。生成叠加图的简单做法是blend cv2.addWeighted(img, 0.6, pred_color, 0.4, 0)透明度比例建议原图0.6、预测0.4预测透明度太高会遮住地物细节太低又看不清分类边界。汇报和写报告时这两种图都是必备素材。6. 避坑指南与半监督扩展伪标签的实践边界6.1 五个高频踩坑记录现象训练loss正常下降验证mIoU却一直停在0.4左右上不去。 原因图像和标签没有对齐。读取样本时用了不同的文件排序规则切块坐标在图像和标签上不统一模型一直在学错位的数据对。 解决训练前写一个可视化脚本把同一个batch的img和mask拼接保存肉眼检查确认地物轮廓和标签完全重合。切块时用同一套坐标生成图像块和标签块不要分开读再按顺序配对。现象推理结果整体出现偏移建筑和道路的边缘向同一个方向移动了几个像素。 原因推理时输入影像做了归一化但输出预测的尺寸和原始影像不一致。要么是滑窗推理时边缘处理丢失了几行像素要么是最后resize时用了不同的插值方式导致坐标映射偏差。 解决推理全程在原始影像的尺寸下进行记录原始height和width所有坐标变换在这个基准上做。需要resize时统一用bilinear且align_cornersFalse不要混用nearest和bilinear。现象batch_size设成了8GPU利用率却只有30%训练速度极慢。 原因CPU数据预处理成了瓶颈每个epoch实时做随机裁切和增强GPU在等数据。 解决把预处理结果先缓存成npy文件或使用lmdb格式训练时只做翻转和旋转这类轻量增强DataLoader的num_workers调到4到8pin_memory设为True。如果显存允许一次性加载多个batch到显存里做离线增强。现象TensorBoard里显示的mask全黑视觉上完全看不出分类结果。 原因mask是0到5的整数标签直接传给add_image被当成了归一化的float图显示数值都在0到1之外显示为黑色或白色。 解决显示前把mask乘以255再除以num_classes转成uint8或使用matplotlib的colormap映射成RGB伪彩色图再写入TensorBoard。现象加入伪标签数据后模型mIoU反而明显下降比只用真实标签还差。 原因伪标签的置信度过低错误标注成了训练信号。模型早期的预测本身就有误差把低置信度预测当成硬标签回灌相当于把噪声放大。 解决置信度阈值至少要0.9起步每轮只选取通过阈值的像素参与loss计算其他像素用ignore_index跳过。伪标签在总loss中的权重设为0.3到0.5不喧宾夺主。每5个epoch才更新一次伪标签避免训练过程震荡。6.2 半监督方法伪标签的具体落地步骤伪标签是半监督语义分割最朴素也最实用的方法核心流程是三步用现有模型对无标注影像预测取高置信度像素作为伪标签把这些伪标签作为额外训练数据继续训练。项目摘要里的半监督方法对应的就是这条路线。def generate_pseudo_labels(model, unlabeled_img, threshold0.9): model.eval() with torch.no_grad(): logits model(unlabeled_img) prob, pred torch.max(torch.softmax(logits, dim1), dim1) pseudo_label pred.clone() pseudo_label[prob threshold] 255 return pseudo_label伪标签生成后255标记的像素在训练时通过ignore_index跳过。这里有两个实践要点。第一阈值不能固定不变训练初期模型还不准用0.95的高阈值筛出极少像素训练中后期模型变强把阈值降到0.9甚至0.85让更多像素参与进来。第二无标注影像要经过和训练数据相同的预处理和切块流程不能用不同尺寸或不同波段组合否则伪标签的分布偏移会传导给下游模型。伪标签在GF2地物分类上的收益通常在2到5个点mIoU之间前提是无标注影像和训练影像来自同一区域或相近的成像条件。如果无标注影像是完全不同的地理环境比如用北京地区的模型给上海影像打伪标签地物光谱分布差异大伪标签噪声就会盖过收益。做项目实践时先用同区域影像验证流程再考虑迁移。6.3 轻量升级注意力模块与验证习惯在伪标签基础上把U-Net的encoder替换成带空卷的ResNet或EfficientNet骨干是常见的升级路线但它需要加载预训练权重对数据集规模和训练时间都有更高要求。更轻量级的选择是在decoder的跳连接处加一个注意力门控让模型抑制无关区域的特征突出地物边缘。注意力门控的实现不复杂在原有DoubleConv之前加一个全局平均池化加全连接的通道注意力即可class ChannelGate(nn.Module): def __init__(self, channels, reduction16): super().__init__() self.fc nn.Sequential( nn.AdaptiveAvgPool2d(1), nn.Conv2d(channels, channels // reduction, 1), nn.ReLU(inplaceTrue), nn.Conv2d(channels // reduction, channels, 1), nn.Sigmoid() ) def forward(self, x): return x * self.fc(x)把这个ChannelGate插入到DoubleConv的输出后面计算量增加不到5%但能小幅提升边缘清晰度。这类带轻量注意力的U-Net配合数据增强和伪标签在GF2数据上通常能把mIoU从0.72推到0.78左右比盲目堆叠更大的网络效果更稳。另外一个值得养成的验证习惯是每次改完一个环节固定其他变量做对比实验。比如测试膨胀预测时保持模型权重和后处理完全不变只切换重叠推理和普通推理单独记录mIoU变化。这样得到的结果才是可解释的而不是把所有改进一次性堆上去最后根本不知道是哪个环节起作用。从那以后我每次做遥感语义分割无论是不是课程设计都会强制自己走一遍完整流程先可视化检查数据对齐再统计类别权重训练中盯mIoU而不是loss推理用膨胀预测最后用伪标签扩样本。即使项目不需要半监督我也会在推理阶段保留重叠切块因为边缘连续性的改善是肉眼可见的。希望帮到你。本文还有配套的精品资源点击获取
RELATED

相关推荐

基于深度学习的太阳能光伏板积灰识别:四分类图像任务实战

基于深度学习的太阳能光伏板积灰识别:四分类图像任务实战

简介:这份资源是面向计算机相关专业学生与深度学习入门者的太阳能光伏板积灰识别实战项目,可用于毕业设计、课程设计或期末大作业,重点解决光伏板表面灰尘的自动分类识别问题。项目采用自制灰尘数据集完成四分类任务,方法上覆盖普…

📅 2026/9/23 12:47:24
基于Chrome DevTools的前端埋点校验方案实践

基于Chrome DevTools的前端埋点校验方案实践

1. 项目背景与痛点解析前端埋点校验一直是困扰开发者的高频痛点问题。在大型Web项目中,数据采集的准确性直接影响业务决策和产品优化方向。传统埋点验证方式通常需要反复查看网络请求、手动比对参数、甚至依赖后端日志配合,整个过程耗时费力且容易遗漏关…

📅 2026/9/23 12:47:24
卫星轨道坐标系RTN、UNW、VVLH详解:定义、转换与工程避坑指南

卫星轨道坐标系RTN、UNW、VVLH详解:定义、转换与工程避坑指南

1. 三种坐标系到底在解决什么问题第一次接触卫星轨道力学的人,看到 RTN、UNW、VVLH 这三个缩写,大概率会愣一下:不都是描述卫星姿态和位置的坐标系吗,为什么搞出这么多套?我当初也是这么想的,直到有一次做编…

📅 2026/9/23 12:42:23
MORE NEWS

更多资讯

📰

MySQL实战笔记:从环境搭建到性能调优全流程

翻了翻自己手头的MySQL课堂笔记,发现从安装环境到跑通业务、从踩坑到调优,这条学习路径里几乎每一个关键节点都有值得记下来的细节。最近身边好几个朋友问的问题也正好集中在这条链路上:装哪个版本、初始密码到底在哪、为什么socket连接报错、…

📰

256位小内存故障覆盖率如何决定SoC测试成败:RAMFLT方法论解析

简介:这份PPT资料围绕内存的故障模型与测试算法设计展开,面向计算机体系结构、集成电路测试及嵌入式存储方向的学习者与工程人员,帮助理解内存工作模式、静态与动态故障分类,以及March系列等经典RAM测试算法的设计思路。压缩包内共…

📰

3种方案手写音乐合成器:告别Stack Trace报错

3种方案手写音乐合成器:告别Stack Trace报错 昨晚11点,你盯着屏幕上红色的 java.lang.OutOfMemoryError: Java heap space ,旁边是那个跑了半小时还没输出的 AudioProcessor…

📰

3天搞定b站号速查手册,拒绝只会看教程

3天搞定b站号速查手册,拒绝只会看教程 是不是觉得看了一堆教程还是不会写项目?别急,这是绝大多数开发者的通病。 你盯着屏幕,视频里的代码跑得飞起,自己一动手全是 Bug。 问题不在智商,在于你缺少一份能直接上手的 b站号 开发 速查手册…

📰

RTD2795T显示芯片硬件设计要点:HDMI/DP前端与GPIO规划实战解析

简介:这是面向硬件工程师的RTD2775QT/RTD2795T/QT硬件校验清单,集中列出HDMI、DVI与DisplayPort接口电路设计中的关键检查项,包括串阻选择、热插拔检测、DDC/AUX信号电平、TMDS交换规则、GPIO电压耐受、PCB叠层要求等,可有效规避常…

📰

使用 kyaml 实现 Kubernetes 配置校验函数:kustomize 的 validator-resource-requests 实战指南

使用 kyaml 实现 Kubernetes 配置校验函数:kustomize 的 validator-resource-requests 实战指南 【免费下载链接】kustomize Customization of kubernetes YAML configurations 项目地址: https://gitcode.com/gh_mirrors/ku/kustomize 导读 本文基于 kusto…

TODAY

今日更新

THIS WEEK

本周精选

THIS MONTH

本月热门

读完文章,想聊聊您的网站?

告诉我们您的行业与需求,资深顾问一对一梳理方案与报价,全程免费。

📞 💬