ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

高分二号遥感影像语义分割实战:从数据切块到PyTorch模型部署

高分二号遥感影像语义分割实战:从数据切块到PyTorch模型部署 简介面向遥感图像处理研究者与工程师这套基于Pytorch的高分遥感图像语义分割资源提供了从数据准备、模型设计到训练评估的完整落地教程适合具备深度学习与Pytorch基础的学习者用于算法复现和项目扩展。资源包共1029个文件以819个png图像样本为主体涵盖GF2原始影像、像素级标注和预测融合图另有35个py代码脚本用于网络构建与训练推理辅以一个数据说明csv和项目说明md压缩包整体约577.48MB。已有93人学习下载目录结构清晰包含样例影像、类别样本展示、README及多种结果对比图方便快速定位实验材料并核验分割效果。通过逐模块阅读代码与配套图例读者可掌握遥感影像预处理、语义分割模型训练调试及结果评估的关键流程为后续科研或工程应用提供可复用的基础范式。1. 高分二号L1A影像的Pytorch语义分割从数据切块到模型落地拿到这份项目包时最吸引我的不是predict.png里那片层次分明的分割结果而是GF2_PMS2_E116.0_N39.1_20170302_L1A0002214760-MSS2.csv这个文件名背后完整的数据链路高分二号GF-22017年拍摄的北京西北方向多光谱 L1A 产品被切成了_380.png、_381.png、_382.png这样的训练分块配好classes_sample.jpg类别样本和img_gt_pre.png对比图。这不是拿 VOC 数据集跑个 Demo而是把带地理位置、带辐射定标参数的遥感影像做成标准语义分割数据集的完整闭环。对刚接手遥感识别课题的研究生以及需要在业务系统里挂一个分割服务的工程师来说这份资源最大的价值在于省掉了从原始影像到train/val目录这段最脏最累的活。下面我按拆项目的方式把数据解析、Dataset 封装、模型选型、训练评估和滑动窗口推理逐层讲透。2. 从GF2原始影像到可训练数据文件命名、波段含义与Dataset封装2.1 GF2多光谱分块逻辑文件名里藏着分割数据集的源头GF-2 卫星搭载两台相机文件名里的PMS2就是其中一台多光谱相机蓝色、绿色、红色、近红外四个波段地面分辨率约 3.2 米。文件命名E116.0_N39.1_20170302_L1A0002214760的含义是东经 116.0 度、北纬 39.1 度附近的一景影像2017 年 3 月 2 日拍摄L1A表示已经做过辐射校正但没做几何精校正的 L1 级产品。整景 GF-2 影像的幅宽有几十公里直接送入 GPU 不现实。项目里_380.png、_381.png、_142.png、_382.png这些分块就是从原始大图上按固定像素窗口切出来的。遥感影像切块有个细节不能像自然图像那样随便切因为影像自带地理坐标切出来的每个分块的左上角经纬度要记录到 csv 里后续做预测图拼接或与矢量叠加时才对得上。项目包里的MSS2.csv就是承担这个角色的记录每个分块的位置和类别统计信息。GF-2 多光谱四个波段的具体用途是波段选择时的依据波段波长范围(nm)典型用途蓝450~520水体识别、大气校正绿520~590植被反射峰、人工地物区分红630~690植被吸收带、裸地与道路区分近红外770~890植被健康度、水体边界提取实际训练时一般不直接用 4 波段原始数据训练卷积网络常见做法是取蓝绿红做成真彩色或者把近红外一并保留做 4 通道输入。我一般会保留近红外因为植被、水体这些类别在近红外波段的可分性远高于 RGB。2.2 用Pytorch的Dataset类封装遥感分块数据数据集的读取逻辑决定训练脚本能跑多快。下面的 Dataset 是这份项目场景下最常用的封装方式import os import numpy as np from PIL import Image from torch.utils.data import Dataset class RSSegDataset(Dataset): def __init__(self, img_dir, mask_dir, label_ids(0, 1, 2, 3, 4)): self.img_dir img_dir self.mask_dir mask_dir self.names [f for f in os.listdir(img_dir) if f.endswith(.png)] self.label_ids label_ids # 0:背景 1:道路 2:建筑 3:植被 4:水体 def __len__(self): return len(self.names) def __getitem__(self, idx): name self.names[idx] image np.array(Image.open(os.path.join(self.img_dir, name)))[:, :, :3] mask np.array(Image.open(os.path.join(self.mask_dir, name.replace(.png, _mask.png)))) image image.astype(np.float32) / 255.0 mean np.array([0.485, 0.456, 0.406], dtypenp.float32) std np.array([0.229, 0.224, 0.225], dtypenp.float32) image (image - mean) / std image torch.from_numpy(image).permute(2, 0, 1) mask torch.from_numpy(mask).long() return image, mask这里的label_ids是类别清单顺序要和训练时 Loss 函数的ignore_index对应。mask必须是long类型CrossEntropyLoss不接受 float 标签。归一化用 ImageNet 的均值方差就能收敛不必在遥感数据上单独算统计量数据量小时反而容易过拟合。分块质量直接影响模型上限。检查classes_sample.jpg时我一般会做两个验证把影像分块和对应 mask 叠加透明度设为 0.5肉眼判断边界偏移是否超过 2 个像素再统计每个分块里各类别像素占比如果某块建筑占比超过 80%要单独处理否则训练时模型会学到位置偏置而不是地物特征。2.3 语义分割数据集如何制作从影像分块到像素级标签classes_sample.jpg里展示的类别是怎么标出来的是新手最容易卡住的地方。首先在 QGIS 里打开影像分块用栅格渲染让植被呈红色、建筑呈灰色然后对照真实地物画多边形。标注导出后转成 mask 的姿势要统一类别按0,1,2,...编号一个像素只能有一个类别背景通常是 0。一个直观的转换流程是把每个类别的 Shapefile 先栅格化为独立的二值图再按类别编号相加。有一个很容易踩的坑是不同类别的图幅稍微差一个像素相加后会出现空洞或重影解决办法是先给所有类别指定同一个resolution和extent再栅格化。项目里如果直接用别人标注好的 mask 训练也要先跑一遍像素值统计确认类别编号没有从 1 开始而不是 0。3. 模型选型与训练超参DeepLabV3加深ResNet50还是U-Net3.1 遥感大图分割场景下的网络对比GF-2 影像里的道路、裸地、农田是连续的大区域建筑则密集且边界锐利这要求模型具备两个能力大的感受野覆盖地物上下文以及较强的边界保持能力。把常用的三个模型放到同一份遥感分割数据集上看模型骨干网络优势劣势U-Net自定义编码器小样本表现好边界细感受野有限大尺度地物易碎DeepLabV3ResNet50/101空洞卷积扩大感受野多尺度语义强显存占用稍高需要更多迭代SegFormerMiT-B2全局上下文最强遥感小数据集上容易欠拟合在我实际跑 GF-2 这种单景数据量有限的项目时DeepLabV3配 ResNet50 是性价比最优的配置空洞卷积不需要下采样太狠就能看到更广的范围道路和农田这种连通性地物分割得比较完整U-Net 虽然参数少但是在建筑和道路交错的区域容易出现细碎噪点。3.2 Pytorch基础框架下的模型构建与损失函数组合使用我一般会选择的版本组合import torch.nn as nn from torchvision.models.segmentation import deeplabv3_resnet50 def build_deeplabv3(num_classes5, pretrainedTrue): model deeplabv3_resnet50(weightsCOCO_WITH_VOC_LABELS_V1 if pretrained else None) model.classifier[-1] nn.Conv2d(256, num_classes, kernel_size1) return modeldeeplabv3_resnet50是 Pytorch 官方分割模型套件里训练成本最低的入口COCO 预训练权重已经学到通用的边缘和纹理特征遥感影像虽然和自然图像分布不同但底层特征仍然可迁移。替换classifier最后一层卷积是为了把输出通道数从 21 改成自己的类别数。损失函数只用一个交叉熵在类别不平衡时会出问题遥感数据里植被往往占一半像素道路和水体只有零星几条。我常把交叉熵和 Dice Loss 按 0.7 比 0.3 混合class HybridLoss(nn.Module): def __init__(self, weightNone): super().__init__() self.ce nn.CrossEntropyLoss(weightweight, ignore_index255) def dice_loss(self, logits, target, eps1.0): probs torch.softmax(logits, dim1) target_onehot torch.eye(logits.shape[1])[target.cpu()].to(logits.device) intersection (probs * target_onehot).sum(dim(0, 2, 3)) union probs.sum(dim(0, 2, 3)) target_onehot.sum(dim(0, 2, 3)) return 1.0 - (2 * intersection eps) / (union eps) def forward(self, logits, target): return self.ce(logits, target) 0.3 * self.dice_loss(logits, target)这里weight参数可以传入每个类别的像素占比倒数缓解少数类被淹没的问题ignore_index255是给分块边界或无效区域留的口子标注时把明显无法判别的像素设为 255不参与 Loss 计算。Dice Loss 对类别不平衡的鲁棒性好但单独用容易梯度抖动混合 CE 后训练稳定很多。3.3 训练超参从学习率到数据增强的推荐配置遥感分割训练不像分类那样可以直接套lr0.001的默认值。我通常按这张表初始化超参推荐值说明优化器SGD momentum0.9比 Adam 泛化好泛化性需求优先初始学习率0.01配合 poly 衰减后面会细说Batch Size8~16由 GPU 显存决定至少 4训练轮次50~100小数据集配合早停输入尺寸512x512和分块尺寸保持一致数据增强随机翻转、随机缩放、色彩抖动遥感有旋转不变性可加强旋转增强学习率策略上StepLR在遥感分割里效果一般poly衰减更贴合分割任务学习率按(1 - iter/total_iter)^0.9缓慢下降后段训练能精修边界。数据增强里最容易忽略的是加入随机旋转因为遥感影像是俯视视角地物不会因为旋转而改变语义这和自然图像行人不能倒立有本质区别。4. 训练闭环checkpoint保存、mIoU评估与可视化输出解析4.1 训练脚本结构记录epoch、优化器状态与最佳mIoU分割训练中断是常事所以 checkpoint 里不能只存模型权重优化器状态和学习率调度器状态也要一起存。下面的训练骨架适合直接替换在这个项目里跑best_miou 0.0 for epoch in range(start_epoch, epochs): model.train() train_loss 0.0 for images, masks in train_loader: images, masks images.cuda(), masks.cuda() optimizer.zero_grad() outputs model(images)[out] loss criterion(outputs, masks) loss.backward() optimizer.step() train_loss loss.item() val_miou evaluate(model, val_loader, num_classes) if val_miou best_miou: best_miou val_miou torch.save({ epoch: epoch, model_state_dict: model.state_dict(), optimizer_state_dict: optimizer.state_dict(), best_miou: best_miou, }, best_model.pth)注意outputs model(images)[out]torchvision里的 DeepLabV3 返回的是 dict除了out还有aux辅助输出。如果显存允许把辅助输出也加进 Loss 能提升收敛速度。保存模型时用torch.save传 dict 而不是直接用torch.save(model.state_dict())这样恢复训练时能精确回到断点。4.2 mIoU评估与predict/blend/img_gt_pre对比图生成项目文件里的predict.png、blend.png、img_gt_pre.png正好覆盖了评估的三个视角predict.png是模型输出 mask 转成的伪彩色图blend.png是把预测结果以半透明方式叠在原图上方便看边界贴不贴img_gt_pre.png是三联图左原图、中标签、右预测。生产这三张图的评估代码是def evaluate(model, val_loader, num_classes): model.eval() confusion np.zeros((num_classes, num_classes), dtypenp.int64) with torch.no_grad(): for images, masks in val_loader: preds model(images.cuda())[out] preds preds.argmax(dim1).cpu().numpy() masks masks.numpy() for p, m in zip(preds, masks): for c in range(num_classes): confusion[c] np.bincount( m[p c], minlengthnum_classes)[:num_classes] iou np.diag(confusion) / ( confusion.sum(axis1) confusion.sum(axis0) - np.diag(confusion) 1e-10) return np.nanmean(iou)这段代码的核心是混淆矩阵逐像素统计真实类别为 c、被预测成各类别的次数然后按intersection / (pred_count gt_count - intersection)计算每类 IoU。用np.bincount避免逐像素for循环在 512x512 的分块上速度能快一个数量级。img_gt_pre.png里的对比图可以用PIL.Image.blend或者matplotlib的subplots拼接重点是要把标签类别的配色固定下来否则每次生成的图颜色不同肉眼对比就没有基线了。4.3 训练时最容易忽略的坑类别权重与标签错位第一类坑是类别权重缺失。如果 Loss 不加class_weight模型会倾向把所有像素预测成占比最多的类别mIoU 看着有四五十实际上道路和水体完全没学会。第二类坑是 mask 的类别编号与模型输出不对齐。classes_sample.jpg里如果类别顺序是道路为 1、建筑为 2那 Dataset 返回的 mask 长整型值也必须从 1 开始一旦混入255之外的无效值Loss 会出现NaN。第三类坑是数据加载时的num_workers在 Windows 上开多了会报错遥感分块大num_workers4配合pin_memoryTrue基本够用。注意训练时看到 Loss 下降但 mIoU 不涨优先检查验证集里是否存在没有类别标注的分块如果blend.png里预测边界整体偏移检查训练数据是否混入了未配准的 L1A 影像这类数据的地物位置会有几个像素的几何偏差。5. 滑动窗口推理与整景拼接让模型真正用起来的技巧5.1 滑动窗口推理重叠消除边界伪影训练时模型看到的都是 512x512 分块预测整景时直接切块推理再拼接会在边界处出现明显的拼缝。原因是边缘像素的感受野不完整模型只能看到半个目标就下判断。常见做法是滑动窗口时带上重叠区域我一般把重叠率设在 25%每走 384 像素切一次 512x512 窗口预测后只取中心 384x384 区域写入结果这样每个像素至少有两次预测最终取平均概率而非 argmax。def sliding_window_predict(model, big_image, window512, stride384): h, w big_image.shape[:2] pred_map np.zeros((h, w), dtypenp.uint8) count_map np.zeros((h, w), dtypenp.float32) for y in range(0, h - window 1, stride): for x in range(0, w - window 1, stride): patch big_image[y:ywindow, x:xwindow] with torch.no_grad(): prob torch.softmax(model(preprocess(patch).cuda())[out], dim1) pred_map[y:ywindow, x:xwindow] prob.argmax(0).cpu().numpy() count_map[y:ywindow, x:xwindow] 1 return pred_map / np.clip(count_map, 1, None)最后一行pred_map / count_map不是简单求平均而是把每个像素被预测的次数做归一化重叠区域取多次预测的平均类别能显著减少条带噪声。代价是推理时间增加大约 30%但换来的是拼接后不再需要额外的后处理平滑。5.2 波段组合策略与结果校验GF-2 的 MSS2 四波段数据做推理前先确定输入通道如果训练时用了四波段推理时也要用四波段并把近红外波段归一化到和训练时相同的统计区间。predict.png输出的类别图可以继续做连通域分析把面积小于 50 像素的孤立块合并到周围类别这一步用scipy.ndimage一个函数就能完成对去除建筑区里零星的裸地误检非常有效。最后把blend.png与img_gt_pre.png并排打开目测建筑边界是否贴合阴影方向——这比任何指标都更能暴露模型是否真正学到了遥感地物的空间特征。本文还有配套的精品资源点击获取
返回列表