ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

基于PyTorch的高分遥感影像语义分割实践指南

基于PyTorch的高分遥感影像语义分割实践指南 简介这是一套基于PyTorch的高分遥感影像语义分割实践项目面向遥感、地物分类方向的开发者与研究生可帮助从数据预处理到模型训练、预测后处理全流程落地。压缩包内以Python源码为主配合大量预测结果图与样例图共858个文件整体约548MB其中819张PNG图像用于可视化对比与结果分析35个Python脚本覆盖训练、评估、预测等环节另含CSV标注文件与说明文档结构清晰便于按模块研读。项目不仅包含常规分割模型还引入膨胀预测、后处理策略并采用半监督伪标签方法扩充训练样本同时集成tensorboardX输出可视化指标方便调试与效果观察。目前已有3041人在线学习适合希望掌握遥感地物分类完整技术链路、并尝试半监督等进阶方案的读者可根据样例图快速验证方法效果并迁移到自有数据。1. 高分遥感语义分割这份 PyTorch 资源到底能帮你解决什么问题遥感影像的地物分类和普通图片分割不一样一张高分二号影像动不动就是上万像素宽地物尺度差异又大农田、建筑、水体、裸地在同一张图里可能各占一角。直接拿整张图塞进语义分割网络显存先不说边缘细节和类别不平衡就够你折腾几周。这份资源提供的是一个完整的 PyTorch 语义分割工程基于 GF2_PMS2 卫星影像能用来做耕地、建筑、道路、水体这类地物的像素级分类。它从数据裁剪、模型训练到膨胀预测、后处理再到期中阶段用伪标签做半监督扩充链路是闭环的拿来改一改就能换到自己的遥感数据上跑。适合正在做遥感地物分类课程设计、毕业设计或者想快速上手 PyTorch 分割工程、又不想从零搭训练管线的读者。2. 从原始影像到训练样本先搞懂数据管线和标注格式2.1 原始文件里都有什么GF2 影像和 CSV 标注的对应关系拿到资源后先看文件清单。项目里出现了 GF2_PMS2_E116.0_N39.1_20170302_L1A0002214760-MSS2.csv、classes_sample.jpg、blend.png、img_gt_pre.png 这类文件。GF2 是高分二号卫星PMS2 是它的多光谱传感器MSS2 后缀说明这是 2 号多光谱相机获取的数据覆盖 E116.0、N39.1 附近区域成像时间是 2017 年 3 月 2 日。CSV 文件是影像和标签的对应关系表通常每一行记录一个样本的影像路径、对应标签路径、类别编号等信息。我打开这种 CSV 的第一步永远是先确认列名和分隔符遥感项目的 CSV 偶尔会用空格或者分号做分隔盲目用 pandas 默认的逗号读会直接得到一列脏数据。在读取 CSV 之前建议先跑一段快速探查代码把路径字段的实际值打出来确认它指向的到底是裁剪前的原始影像还是已经切好的瓦片。常见做法是 CSV 里直接存瓦片文件名训练时再拼接根目录。这个项目里出现的 380.png、381.png、142.png 这类编号文件大概率就是从原图上裁剪下来的瓦片样本尺寸一般在 256 到 512 像素之间。裁剪的目的不是节省存储而是让每个训练样本的类别分布相对均衡同时把影像尺寸压缩到网络能接受的范围。import pandas as pd df pd.read_csv(GF2_PMS2_E116.0_N39.1_20170302_L1A0002214760-MSS2.csv) print(df.columns.tolist()) print(df.head(3)) # 常见列名img_path, gt_path, class_id, split # 如果列名不是这个打印出来看实际字段再改这段代码的作用是确认 CSV 的字段结构。很多初学者会跳过这一步直接写训练脚本结果发现 DataLoader 里读路径时报错才回头检查 CSV。参数说明head(3)只打印前 3 行避免终端刷屏columns.tolist()把列名转成 Python 列表方便后续按索引操作。我一般还会在探查阶段顺手打印df[class_id].value_counts()确认类别分布是否严重失衡。如果某一类只占全部标注像素的 1% 都不到那么后面训练时要么用加权损失要么就得在裁剪时做类别感知采样。2.2 样本裁剪策略滑动窗口切图时的重叠率选择高分影像直接整图训练不现实最常见的预处理方案就是滑动窗口裁剪。裁剪时有个关键参数是重叠率。如果完全不重叠那么地物跨边界时会被硬生生切断模型看到的地物形状总是不完整的。预测时也同样有这个问题边缘像素的预测质量比中心区域差。所以裁剪和预测都要设重叠率一般取 10% 到 20%。资源里提供的那几张 380、381、142 编号瓦片就是从大图上切下来的从文件名能看出是在同一个场景内连续滑窗得到的。裁剪和预测的窗口尺寸要匹配。如果你训练时用的是 256x256预测时也最好用同样的尺寸否则模型在推理时面对的感受野和训练时不一致精度会下降。我曾经图省事训练用 256预测用 512结果边缘区域出现规则条带后来才意识到是感受野变化导致的。裁剪时还有一个容易忽略的点瓦片的命名规则里最好包含它在原图上的坐标起点这样预测完拼接回大图时才不会错位。# 滑动窗口裁剪参数设计 window_size 256 stride 224 # 重疊 32 像素約 12.5% img_height, img_width 1024, 1024 # 假设原图尺寸 coordinates [] for y in range(0, img_height - window_size 1, stride): for x in range(0, img_width - window_size 1, stride): coordinates.append((x, y, x window_size, y window_size)) print(f共生成 {len(coordinates)} 个窗口最后一个窗口坐标为 {coordinates[-1]})这里stride 224造成的效果是相邻窗口有 32 像素重叠。重叠区域不是为了好看是为了预测拼接时让边缘像素被多次预测最后取平均值或投票来决定类别能明显抑制接缝处的错分。需要注意循环的停止条件是img_height - window_size 1如果最后一次滑动无法覆盖到图像右下角末尾会留下一条切不到的边。常见的处理方式有两种要么对边缘做镜像填充再裁剪要么记录坐标时把最后一块强制对齐到图像右下角。我更推荐后者因为镜像填充会让模型在边缘看到假的纹理训练时还好预测时一旦形成习惯就容易在真实场景里出错。2.3 归一化和数据增强别让卫星影像的物理量纲干扰网络GF2 的 MSS2 数据是 4 个波段蓝、绿、红、近红外不是常规的 RGB 三通道。如果 PyTorch 加载时代码里写死了 3 通道读入近红外时就会出问题。第一个要确认的是数据本身是 4 通道 TIF 还是已经被处理成 PNG 三通道。资源里的瓦片文件是 .png那大概率已经做过了波段合成或选择了 RGB 三个波段。如果后续你想换自己的 GF2 数据就必须按 4 通道来设计输入层否则近红外波段对植被的强响应特征就白白丢掉了。归一化参数也要单独算。遥感影像的 DN 值分布和自然图像差异很大直接用 ImageNet 的 mean 和 std 来归一化效果往往是灾难性的。正确的做法是从待训练的数据集中随机抽取几百个瓦片统计每个通道的均值和标准差然后写进训练配置里。# 统计通道均值和标准差 import numpy as np from PIL import Image import glob files sorted(glob.glob(train_imgs/*.png))[:200] pixels [] for f in files: img np.array(Image.open(f)).astype(np.float32) / 255.0 pixels.append(img.reshape(-1, img.shape[-1])) pixels np.concatenate(pixels, axis0) mean pixels.mean(axis0) std pixels.std(axis0) print(fmean {mean.tolist()}) print(fstd {std.tolist()})这个脚本的实质是把 200 张随机样本的像素值全部拼接后算全局统计量。reshape(-1, img.shape[-1])把每张图拉平成N x C的形状200 张图累积起来就是几十万行像素统计结果比随便估计要稳得多。注意astype(np.float32)必须先做否则 uint8 整数在除法时会被截断。有了这套统计量训练脚本里就用它替代固定的mean[0.485, 0.456, 0.406]。关于数据增强遥感影像不适合随手翻转加旋转的那种常规操作因为地物方向是有意义的道路和建筑在旋转后会和原样本产生错误对应关系。我一般只做水平翻转、垂直翻转和随机尺度缩放三个选项多了反而容易让模型混乱。3. 训练管线搭建模型选型、损失函数和 tensorboardX 可视化3.1 遥感场景下为什么首选 DeepLabV3 或者 UNet 系这份资源里的核心是语义分割模型PyTorch 实现。针对遥感地物分类模型选型的第一原则是边缘定位要准小目标不能漏。遥感地物里小房子、小块农田、窄道路都是难啃的骨头。UNet 系模型天然适合这种场景因为它有跨层连接浅层特征里的边缘细节可以直接传入解码器小目标的定位误差没那么大。DeepLabV3 则在多尺度上下文上占优空洞卷积扩大了感受野对大块耕地、水域这类匀质地物更友好。如果你最后是交课程设计或毕设我建议以 DeepLabV3 为基线因为调参空间大ablation study 也好写。而 UNet 更适合快速出基线效果显存占用低训练时间短。资源里没有明确写死只能用哪个模型但工程结构上一般会留一个model目录或者模型工厂方法你可以在配置里通过model_name参数切换。# 模型工厂根据配置创建分割模型 def build_model(model_name: str, num_classes: int, backbone: str resnet50): if model_name deeplabv3plus: from models.deeplabv3plus import DeepLabV3Plus model DeepLabV3Plus(num_classesnum_classes, backbonebackbone) elif model_name unet: from models.unet import UNet model UNet(in_channels3, num_classesnum_classes) else: raise ValueError(f未知模型类型: {model_name}) return model # 使用 model build_model(model_namedeeplabv3plus, num_classes6)这个工厂函数的价值在于把模型实例化和实验配置解耦。换模型或者换主干网络时不需要改训练脚本只改一行参数。backboneresnet50是 DeepLabV3 的默认主干如果显存不宽裕改成resnet18训练速度会快不少精度损失大约在 2 到 4 个 mIoU 点。遥感影像类别多时优先用resnet50类别少时resnet18够用。资源里如果有预训练权重注意确认它是 ImageNet 预训练还是从零训练。加载时如果分类头类别数不一致要把最后一层和分类头排除掉再加载。3.2 损失函数选择交叉熵的短板和加权方案地物分类的标签分布极度不均。大片的农田可能占了 70% 的像素道路和建筑加起来不到 5%。普通交叉熵在这种分布下会把模型带偏让它倾向于把所有像素都预测成农田。解决办法是给不同类别设置权重小类别的权重调高大类别调低。权重可以通过训练集标注统计得到最常用的公式是weight 1 / np.log(1.02 class_freq)这个公式在 DeepLab 系列的论文里也出现过比直接用1/class_freq温和不会让极小类别权重爆炸。Dice Loss 和 Focal Loss 也常被用来处理类别不平衡。Focal Loss 通过调制因子让模型把注意力集中在难分类的像素上对小目标有奇效但它的缺点是训练初期收敛慢需要配合较高的学习率。我的建议是主线用加权交叉熵辅助用 Dice Loss二者按 1:1 相加。原因是 Dice Loss 对类别不平衡天然免疫但它对网络输出的概率分布敏感梯度在极端情况下会不稳定跟交叉熵结合在一起能互补。# 加权交叉熵 Dice Loss 组合 import torch import torch.nn as nn import torch.nn.functional as F class CombinedLoss(nn.Module): def __init__(self, class_weights): super().__init__() self.class_weights torch.tensor(class_weights, dtypetorch.float32) def forward(self, logits, mask): ce F.cross_entropy(logits, mask, weightself.class_weights.to(logits.device)) # 计算 Dice Loss probs F.softmax(logits, dim1) one_hot F.one_hot(mask, num_classeslogits.shape[1]).permute(0, 3, 1, 2).float() intersection (probs * one_hot).sum(dim(2, 3)) union probs.sum(dim(2, 3)) one_hot.sum(dim(2, 3)) dice (2 * intersection 1e-5) / (union 1e-5) dice_loss 1 - dice.mean() return ce dice_loss # 使用 criterion CombinedLoss(class_weights[0.5, 1.5, 1.0, 2.0, 3.0, 0.8])CombinedLoss里ce是加权交叉熵dice_loss控制的是类别整体重叠度。公式里2 * intersection 1e-5是 Dice 系数的分子加上平滑项是为了防止除以零。permute(0, 3, 1, 2)这一步容易写错one_hot出来是N, H, W, C需要转成N, C, H, W才能和probs对齐。训练时如果发现 loss 不降十有八九是one_hot的维度顺序搞错了。3.3 tensorboardX 可视化不只盯 loss还要盯 mIoU 和预测样例摘要里明确提到了 tensorboardX 可视化输出。这个组件的价值在于训练过程中实时监控分割质量。只盯 loss 曲线是远远不够的在语义分割里 loss 从 0.5 降到 0.3看起来是在收敛但实际可能是背景类优化得很好建筑类反而变差了。所以除了 loss 曲线还要把验证集的 mIoU、每类 IoU、以及预测图放上去一起看。一个完整的 tensorboardX 可视化里至少要包含三张图片原始输入图、标注图、网络预测图。预测图通常要做 argmax 后转成伪彩色遥感地物用jet或者自定义颜色表比较清晰。训练早期你会发现预测图是一整片噪声不要慌张继续往下看 mIoU 的走向。from torch.utils.tensorboard import SummaryWriter writer SummaryWriter(runs/exp001) # 训练循环内部 if step % 50 0: writer.add_scalar(loss/train, total_loss.item(), step) writer.add_scalar(lr, optimizer.param_groups[0][lr], step) # 每个 epoch 结束后记录验证指标和预测图 for name, metric_val in val_metrics.items(): writer.add_scalar(fval/{name}, metric_val, epoch) # 预测图可视化 rgb_pred colorize(pred_mask) # pred_mask: HxW, 每像素是类别编号 writer.add_image(val/pred, rgb_pred, epoch, dataformatsHWC)代码里的colorize函数需要自己实现一般做法是建一个长度等于类别数的 RGB 颜色列表然后按像素类别索引去取色。dataformatsHWC必须显式声明否则 tensorboardX 会把数组当成CHW格式图像会旋转 90 度或者通道翻转。训练日志建议每 50 步写一次 loss每个 epoch 写一次验证指标效率高且不会撑爆磁盘。4. 膨胀预测滑动窗口推理的拼接逻辑和边界处理技巧训练结束进入预测阶段。整张高分影像推理时直接喂给网络GPU 显存吃不消所以需要滑窗预测。膨胀预测这个叫法实质是在推理时对图像做填充让窗口边缘也能拿到完整的上下文信息。最朴素的实现是直接用训练时的窗口尺寸滑动但这样拼接处容易出现一条条接缝。膨胀预测的思路是把输入图像先 padding 再切块让每个滑动窗口的中心区域才被保留边缘区域只为提供上下文。在 PyTorch 里最常用的实现方式是padding 滑动窗口裁剪 拼接。padding 有几种模式reflect模式适合遥感影像因为它不会在边界引入不存在的纹理值。zero模式会在图像周围生成一圈黑色模型在边界看到的是完全不同的上下文拼接处自然会出现黑色伪影。这一步是很多人翻车的地方训练时数据没 padding预测时一 padding 就露馅。# 膨胀预测实现 import torch import torch.nn.functional as F import numpy as np def sliding_predict(model, image, window_size256, stride224, pad32): image: HxWxC, numpy 返回: HxW 的类别索引 model.eval() # 对图像做 reflect padding pad_img np.pad(image, ((pad, pad), (pad, pad), (0, 0)), modereflect) H, W pad_img.shape[:2] pred_sum np.zeros((H, W, num_classes), dtypenp.float32) count np.zeros((H, W, 1), dtypenp.float32) for y in range(0, H - window_size 1, stride): for x in range(0, W - window_size 1, stride): crop pad_img[y:y window_size, x:x window_size] crop_tensor torch.from_numpy(crop.transpose(2, 0, 1)).unsqueeze(0).float() with torch.no_grad(): logits model(crop_tensor) probs F.softmax(logits, dim1).squeeze(0).cpu().numpy() # 保存每个窗口的预测概率 pred_sum[y:y window_size, x:x window_size] probs.transpose(1, 2, 0) count[y:y window_size, x:x window_size] 1 # 平均概率去除 padding 区域 pred_sum / np.maximum(count, 1) pred_result np.argmax(pred_sum, axis-1) return pred_result[pad:H - pad, pad:W - pad]这个函数的参数值得仔细讲。stride是滑窗移动步长越小重叠越多预测越平滑但耗时翻倍。224 对应 256 窗口重叠 32 像素是速度和精度的折中。如果影像地物边界密集比如城区可以降到stride192重叠提高到 64 像素。这里和训练裁剪的区别在于训练时每次只取中心区域预测时每次把整块预测结果加进累积器重复预测的中心像素被多次平均。count矩阵的分母是判断一个像素被预测了多少次。对于非重叠滑窗每个像素至少被预测一次但由于stride小于window_size中间区域可能被预测多次边缘可能只被覆盖一次。np.maximum(count, 1)防止出现除以零。最后把pad值的边界裁掉得到的就是和原图尺寸一致的预测结果。5. 半监督伪标签用未标注数据提升精度和避坑指南5.1 伪标签的基本流程先从已标注数据里长出第一代模型遥感数据的标注成本极高人工勾勒地块边界、楼房屋顶一个人一天能做几个平方公里已经算快了。摘要里的“半监督方法伪标签”就是用来解决数据不足问题的。核心思路很朴素先用已经标注的数据训练一个初步模型然后用这个模型去对未标注的影像做预测把置信度高的预测结果当成标注加入训练集再训一个新模型。这个循环可以迭代多轮。第一代伪标签的质量决定了上限。所以第一步要把有标注的数据尽量用好训练到验证集 mIoU 不再明显上涨为止。判断标准不是 loss 而是 mIoU因为伪标签后续会放大错误第一代模型的错误如果太明显加到训练集里就是负贡献。伪标签过滤的时候可以用 softmax 概率的置信度做筛选也可以看类别。我一般会额外设计一个“边界过滤”把靠近地物边界的低置信度像素自动忽略掉因为边界像素本身就是最难标注的模型预测错是常态。# 生成伪标签 def generate_pseudo_labels(model, unlabeled_loader, confidence_threshold0.9): model.eval() pseudo_imgs, pseudo_masks [], [] with torch.no_grad(): for imgs in unlabeled_loader: imgs imgs.cuda() logits model(imgs) probs F.softmax(logits, dim1) max_prob, pred_mask probs.max(dim1) # 置信度掩码 confident max_prob confidence_threshold # 保留置信度高的样本 for i in range(imgs.size(0)): if confident[i].sum() / confident[i].numel() 0.7: pseudo_imgs.append(imgs[i]) pseudo_masks.append(pred_mask[i]) return pseudo_imgs, pseudo_masksmax_prob是每个像素上的最大 softmax 概率confident是一个布尔掩码。这里的两个阈值可以解释一下confidence_threshold0.9控制像素级置信度0.7控制的是整张图的有效像素比例。如果一张图上 80% 的区域都是低置信度那这张图的伪标签基本是噪声留着它会污染训练集。第一轮迭代建议把阈值放到 0.95 和 0.8宁可少一点也不要引入太多噪声。想进一步提高伪标签质量可以叠加两个不同 epoch 的模型做预测只有二者结果一致的像素才保留为伪标签这比单模型置信度过滤更稳。5.2 伪标签训练中的常见问题和避坑记录伪标签看起来简单实际操作时坑很多。这里整理几个我实际踩过的按“现象 → 原因 → 解决”写。坑一迭代后模型 mIoU 不升反降现象第一轮伪标签训练结束后验证集 mIoU 下降了 3 到 5 个点。原因伪标签里混入了大量模型预测错误的像素相当于给训练集注入了错误的监督信号。解决把置信度阈值调高并且对每张伪标签图统计类别分布过滤掉某一个类别占比超过 90% 的极端样本。后续迭代中如果 mIoU 连续两轮不再上升就果断停止不要继续堆。坑二伪标签样本和已标注样本分布不一致现象新加的伪标签影像时相不同比如一个是春季一个是秋季训练出来的模型对旧验证集表现还行但实际应用场地上出现大片错分。原因不同时相的影像亮度、植被覆盖状态差别大模型学到了“季节”特征而不是地物语义。解决伪标签数据加入训练集时做色彩增强把亮度扰动和对比度扰动加大至少让模型不至于对特定亮度过度敏感。或者干脆每个批次里约束真实标签和伪标签按固定比例混合例如每批 8 张图里不超过 3 张是伪标签。坑三伪标签图存在明显的拼块痕迹现象新增的伪标签图像边缘处有规则的方框训练时 loss 在某个区间反复震荡。原因滑动窗口预测产生的接缝被直接存成了伪标签模型试图学习接缝处的错误边界。解决生成伪标签时不要保存硬标签而是保存概率图再做像素级平均后再求 argmax。或者在保存前用 3x3 中值滤波把孤立错分像素抹掉这是最快的修复方式。坑四伪标签训练导致过拟合已标注数据现象训练 loss 收敛很快但验证集和测试集表现都不好。原因伪标签样本数量庞大模型把大多数容量都用来记忆未标注数据的模式反而忽略了原始标注数据的多样特征。解决给伪标签样本设置一个权重系数比如 0.5等价于降低它们在损失函数中的贡献。或者限制每轮迭代中伪标签样本总量不超过原始标注样本的两倍。5.3 膨胀预测在伪标签中的应用别让边缘像素拖累下一轮生成伪标签时如果用滑动窗口预测同样要处理边缘拼接问题。如果边缘有接缝那么把这些伪标签图再拿去训练模型就会学到“遥感图像上有规则网格线”这是最崩溃的情况。所以在伪标签的生产线上需要先对影像做膨胀预测再把结果保存成 PNG 标注图。膨胀预测后的概率图如果直接保存为png其实是一种压缩因为它把多类概率转成了单通道整数标签。保存的时候一定要把类别编号映射关系写下来。我踩过这样一个坑训练时类别编号从 1 开始背景是 0生成伪标签时某个类别被误映射成 255模型训练时忽略了 255 的像素导致那个类别在整个数据里完全消失。后来我在保存伪标签的函数里加了一个断言检查像素值集合是否等于所有类别编号的集合一经发现问题立刻中断保存。# 保存伪标签时的校验 assert set(np.unique(pseudo_mask)) set(range(num_classes)), \ f伪标签中出现越界类别: {np.unique(pseudo_mask)} Image.fromarray(pseudo_mask.astype(np.uint8)).save(out_path)这段校验代码写起来只有两行但在大数据集上能拦住绝大多数低级错误。np.unique(pseudo_mask)会返回伪标签图中所有出现的像素值如果里面有255或者负数就说明前面某一步出了问题。保存为 uint8 格式也需要注意如果类别数超过 255uint8 就直接溢出但遥感地物分类一般也就 10 类以内这个约束基本不会有影响。6. 验证和进阶技巧mIoU、混淆矩阵与边界质量的三重检查模型训完后不能只看 mIoU 数字就收工。语义分割在遥感场景下的验收我一般会做三个层面的检查。第一个层面是指标层计算每类的 IoU然后看 mIoU这能定位是哪几个类别拖了后腿。第二个层面是像素混淆层绘制混淆矩阵观察到底谁和谁容易搞混。遥感里最常见的是裸土和道路互混、建筑物阴影和水体互混这两种混淆的修正方式完全不同。第三个层面是空间分布层把预测结果叠加到原图上目测错误是集中在边界还是块状区域。在代码实现上验证集的 mIoU 计算有很多细节。用混淆矩阵计算 IoU 是最推荐的方式统计每个像素的真实类别和预测类别得到num_classes x num_classes的混淆矩阵IoU_i TP_i / (TP_i FP_i FN_i)。注意分母不是简单的对角线之外所有元素之和要按类别来算。一些开源代码里直接调sklearn的confusion_matrix但要注意labels参数必须显式指定否则矩阵维度可能和类别数不一致。# 计算每类 IoU 和 mIoU def compute_iou_from_cm(cm): cm: np.ndarray, shape(num_classes, num_classes) intersection np.diag(cm) union cm.sum(axis1) cm.sum(axis0) - intersection iou intersection / np.maximum(union, 1e-6) return iou cm compute_confusion_matrix(gt_masks, pred_masks, num_classes6) iou_per_class compute_iou_from_cm(cm) miou iou_per_class[np.isfinite(iou_per_class)].mean() for cls_id, iou_val in enumerate(iou_per_class): print(fClass {cls_id}: IoU {iou_val:.4f}) print(fmIoU {miou:.4f})union的计算方式是把混淆矩阵的行和列加起来再减去对角线避免了对角线被重复计算。np.maximum(union, 1e-6)防止某一类完全没出现时出现除零。如果验证时某类完全没有预测样本iou_per_class里会是 NaN此时要在mean前用np.isfinite过滤否则 mIoU 会变成 NaN。最后讲一个我自己的习惯。每次训练完一个实验我都会把预测图按照类别透明度叠加到原图上另存为blend.png和img_gt_pre.png这种文件。资源和项目里已经带了这两张图说明作者也认为这是验收的必要环节。叠加方法是用PIL的Image.blend或者直接按通道加权原图做底图、预测结果做半透明覆盖层。叠加出来的图能直观看到到底边界偏了多少、是否存在破碎的小图斑。我从那以后每次验收都强制走一遍指标、混淆矩阵、叠加图三重检查检查不过关就回头调整损失函数权重或者增强策略不急着提交结果希望这份流程也能帮到你。本文还有配套的精品资源点击获取
返回列表