ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

遥感语义分割实战:SegNet与UNet双模型毕设源码解析

遥感语义分割实战:SegNet与UNet双模型毕设源码解析 简介这份资源面向计算机、人工智能、遥感等方向的在校学生与教师提供基于Segnet与Unet两种深度学习算法实现遥感图像语义分割的完整项目源码可用于毕业设计、课程大作业或项目立项演示。压缩包共15个文件以11个Python脚本为主涵盖Segnet与Unet网络定义、训练主函数、推理预测脚本及数据预处理模块另含3张png效果图与1份项目说明md文档整体约190KB结构清晰便于按模块查阅。项目配套数据增强脚本可对大图进行256×256切割、旋转与加噪处理并附有数据集下载地址与已处理数据网盘链接同时给出Windows10、Anaconda3、PyTorch1.0及TensorBoard可视化环境下的运行方式。目前已有376人学习浏览适合希望快速上手遥感图像分割、对比两种经典网络结构并在此基础上扩展创新的读者参考借鉴。1. 遥感语义分割双模型实战SegNet 与 UNet 这套毕设源码能跑出什么遥感图像语义分割这件事真正上手做过的都知道难的不是把 UNet 的编码器-解码器结构背下来而是拿到一份能直接跑通的工程代码。我见过太多人卡在数据格式对不上、标签图是彩色还是灰度、训练时 loss 不降这些环节上。这套资源给的是一个完整的遥感图像语义分割项目包含 SegNet 和 UNet 两个模型的独立训练入口、数据增强脚本、预测脚本以及配套数据集说明环境是 Windows 10 Anaconda 3 PyTorch 1.0用 tensorboardX 做训练可视化。适合正在做毕设、课程大作业或者想拿一个真实遥感分割项目练手的人。它解决的核心问题是你不用从零搭数据管线直接改路径就能跑训练两个模型还能横向对比效果。2. 工程结构拆解从 train_Seg.py 到 dataset.py 的数据流2.1 两个训练入口的设计逻辑拿到压缩包解压后根目录下有两个训练主函数train_Seg.py和train_U.py。这种设计的好处是 SegNet 和 UNet 各自独立互不干扰你想跑哪个直接执行对应脚本就行。我一般会先看训练脚本里的参数配置区因为学习率、batch size、epoch 这些直接影响你能不能跑出结果。# train_Seg.py 典型结构基于项目实际文件推断 import torch import torch.nn as nn from models.seg_net import SegNet from data_loader.dataset import RemoteSensingDataset from torch.utils.data import DataLoader # 超参数配置区——这里是你第一个要改的地方 BATCH_SIZE 4 # 显存不够就降到 2 LR 1e-3 # 遥感数据通常从 1e-3 或 1e-4 起步 EPOCHS 100 NUM_CLASSES 2 # 根据你的标签类别数改 device torch.device(cuda if torch.cuda.is_available() else cpu) # 数据集加载 train_dataset RemoteSensingDataset(root_dir./data/train) train_loader DataLoader(train_dataset, batch_sizeBATCH_SIZE, shuffleTrue) # 模型实例化 model SegNet(num_classesNUM_CLASSES).to(device) criterion nn.CrossEntropyLoss() optimizer torch.optim.Adam(model.parameters(), lrLR) for epoch in range(EPOCHS): model.train() for imgs, labels in train_loader: imgs, labels imgs.to(device), labels.to(device) optimizer.zero_grad() outputs model(imgs) loss criterion(outputs, labels) loss.backward() optimizer.step()这段代码里几个关键点NUM_CLASSES必须和你标签图的类别数一致遥感分割常见的是二分类建筑/非建筑或多分类道路、植被、水体等BATCH_SIZE设 4 是因为遥感图像切割成 256×256 后显存占用不算大但如果你显卡只有 4GB建议降到 2LR用 1e-3 是 Adam 的常规起点如果 loss 震荡厉害就降到 1e-4。train_U.py的结构几乎一样只是把模型换成了models/u_net.py里的 UNet。两个脚本分开写的好处是你改一个不会影响另一个方便做对比实验。2.2 数据加载与增强管线data_loader/dataset.py负责把图像和标签读进来utils/DataArgument.py负责数据增强。这两个文件是整个项目能不能跑通的关键。# utils/DataArgument.py 核心逻辑基于项目说明还原 from PIL import Image import numpy as np import os import random def crop_image(img, label, crop_size256): 将大图切割成 256x256 的小图 w, h img.size patches [] for i in range(0, w - crop_size 1, crop_size): for j in range(0, h - crop_size 1, crop_size): img_patch img.crop((i, j, i crop_size, j crop_size)) label_patch label.crop((i, j, i crop_size, j crop_size)) patches.append((img_patch, label_patch)) return patches def augment(img, label): 旋转 加噪 # 随机旋转 90/180/270 度 angle random.choice([0, 90, 180, 270]) img img.rotate(angle) label label.rotate(angle) # 加高斯噪声 img_array np.array(img).astype(np.float32) noise np.random.normal(0, 5, img_array.shape) img_array np.clip(img_array noise, 0, 255).astype(np.uint8) return Image.fromarray(img_array), labelcrop_image函数把原始遥感大图切成 256×256 的 patch这是遥感分割的标准做法因为原图动辄几千像素直接送进网络不现实。augment函数做了旋转和高斯噪声旋转角度只选了 90 的倍数这是为了避免插值带来的标签错位——如果你用任意角度旋转标签图的像素值会被插值成非整数导致类别混乱。噪声标准差设 5 是个保守值加太多会让图像失真。运行数据增强的命令cd utils python DataArgument.py执行前确认DataArgument.py里的输入路径指向你下载的原始数据集输出路径指向你希望保存切割后数据的目录。项目说明里提到相对路径已在代码中配置但实际使用时建议还是打开文件确认一下路径变量。2.3 模型定义文件的关键参数models/seg_net.py和models/u_net.py分别定义了 SegNet 和 UNet。SegNet 的核心是编码器用 VGG16 的前 13 层解码器用最大池化索引做上采样UNet 的核心是编码器-解码器之间的跳跃连接。这两个模型在遥感分割里都是经典 baselineSegNet 参数量更小、推理更快UNet 对小目标的分割精度通常更好。# models/u_net.py 典型结构 import torch.nn as nn class UNet(nn.Module): def __init__(self, num_classes2): super(UNet, self).__init__() # 编码器四次下采样 self.enc1 self._block(3, 64) self.enc2 self._block(64, 128) self.enc3 self._block(128, 256) self.enc4 self._block(256, 512) # 瓶颈层 self.bottleneck self._block(512, 1024) # 解码器四次上采样 跳跃连接 self.up4 nn.ConvTranspose2d(1024, 512, 2, stride2) self.dec4 self._block(1024, 512) # ... 后续层省略 self.final nn.Conv2d(64, num_classes, 1) def _block(self, in_ch, out_ch): return nn.Sequential( nn.Conv2d(in_ch, out_ch, 3, padding1), nn.BatchNorm2d(out_ch), nn.ReLU(inplaceTrue), nn.Conv2d(out_ch, out_ch, 3, padding1), nn.BatchNorm2d(out_ch), nn.ReLU(inplaceTrue) )num_classes默认是 2对应二分类任务。如果你的数据集有更多类别改这个参数就行。_block里用了 BatchNorm这在遥感数据上很重要因为遥感图像的光照和对比度差异大BN 能加速收敛。3. 训练与推理全流程从 python train_Seg.py 到 predict.py 出图3.1 环境配置与依赖安装项目说明里给的环境是 Windows 10 Anaconda 3 PyTorch 1.0。PyTorch 1.0 是比较老的版本如果你现在装新版本比如 1.13 或 2.x大部分代码能兼容但有几个地方要注意nn.ConvTranspose2d的参数没变DataLoader的用法也没变但如果你用了torchvision.transforms里的新 API可能需要微调。# 创建 conda 环境 conda create -n remote_seg python3.7 conda activate remote_seg # 安装 PyTorch根据你的 CUDA 版本选择 pip install torch1.0.0 torchvision0.2.1 # 如果装不上老版本用新版本也行 pip install torch torchvision # 安装可视化和图像处理依赖 pip install tensorboard tensorboardX pillow numpy matplotlibtensorboardX是用来写训练日志的配合tensorboard可以在浏览器里看 loss 曲线和分割效果图。如果你不用可视化这两个可以不装但建议装上因为训练过程中看 loss 曲线是判断模型有没有学到的第一手依据。3.2 训练 SegNet 和 UNet训练命令很简单# 训练 SegNet python train_Seg.py # 训练 UNet python train_U.py但直接跑之前建议先检查三个地方第一data_loader/dataset.py里的数据路径是否指向你切割好的数据目录第二train_Seg.py里的NUM_CLASSES是否和你的标签类别数一致第三确认 GPU 可用如果torch.cuda.is_available()返回 False代码会自动用 CPU但训练速度会慢很多。训练过程中终端会打印每个 epoch 的 loss。正常情况下loss 应该在前几个 epoch 快速下降然后逐渐趋于平稳。如果 loss 一直不降或者变成 NaN常见原因是学习率太大或者数据标签有问题。# 在训练循环里加一段简单的日志打印 if epoch % 10 0: print(fEpoch [{epoch}/{EPOCHS}], Loss: {loss.item():.4f}) # 保存模型检查点 torch.save(model.state_dict(), fcheckpoints/segnet_epoch{epoch}.pth)每 10 个 epoch 保存一次模型是个好习惯万一训练中途崩了不用从头再来。保存的.pth文件就是后面推理要用的权重。3.3 用 predict.py 做推理训练完成后predict.py负责加载模型权重并对新图像做预测。# predict.py 核心逻辑 import torch from PIL import Image import numpy as np from models.seg_net import SegNet # 加载模型 model SegNet(num_classes2) model.load_state_dict(torch.load(checkpoints/segnet_final.pth)) model.eval() # 读取待预测图像 img Image.open(test_image.png).convert(RGB) img_tensor torch.from_numpy(np.array(img)).permute(2, 0, 1).float().unsqueeze(0) / 255.0 # 推理 with torch.no_grad(): output model(img_tensor) pred torch.argmax(output, dim1).squeeze().numpy() # 保存预测结果 pred_img Image.fromarray((pred * 255).astype(np.uint8)) pred_img.save(prediction_result.png)model.eval()这行必须加它会把 BatchNorm 和 Dropout 切换到推理模式否则预测结果会不稳定。torch.no_grad()是关闭梯度计算节省显存。argmax取每个像素最大概率对应的类别得到分割掩码。推理时常见的坑是输入图像的尺寸和训练时不一致。训练用的是 256×256 的 patch推理时如果输入一张 1000×1000 的大图模型可能能跑但结果会很奇怪。建议推理前也把图像切成 256×256 的 patch预测完再拼回去。4. 避坑与排查数据标签、显存和 loss 不降的常见问题4.1 标签图是彩色导致 loss 不降现象训练时 loss 一直在 0.69 附近震荡二分类的随机猜测值完全不下降。原因遥感数据集的标签图经常是彩色的比如建筑是红色、背景是黑色但CrossEntropyLoss要求标签是 0、1、2 这样的类别索引不是 RGB 值。解决在dataset.py的__getitem__里把标签图转成灰度或直接映射到类别索引。# 在 dataset.py 中处理标签 label Image.open(label_path) label np.array(label) # 如果是彩色标签做颜色到类别的映射 if len(label.shape) 3: # 假设红色(255,0,0)是类别1黑色(0,0,0)是类别0 label ((label[:, :, 0] 128) (label[:, :, 1] 128)).astype(np.uint8)4.2 显存不足导致训练中断现象跑几个 batch 后报CUDA out of memory。原因BATCH_SIZE设太大或者模型参数量超出显卡容量。解决把BATCH_SIZE从 4 降到 2 甚至 1或者在训练脚本里加torch.cuda.empty_cache()。如果还不行考虑用更小的输入尺寸比如 128×128但要注意 UNet 的下采样次数是固定的输入太小会导致维度不匹配。4.3 数据增强后图像和标签不对应现象训练出来的模型预测结果完全乱套分割掩码和原图对不上。原因DataArgument.py里对图像做了旋转但标签图没有同步旋转或者旋转时用了不同的插值方式。解决确保图像和标签用同样的变换参数。在augment函数里旋转角度、裁剪坐标这些必须对图像和标签同时应用。如果用了torchvision.transforms可以用JointTransform或者手动同步。4.4 训练 loss 震荡太大现象loss 一会儿高一会儿低没有稳定的下降趋势。原因学习率太大或者 batch size 太小导致梯度估计方差大。解决把学习率从 1e-3 降到 1e-4或者加学习率衰减策略。# 加一个简单的学习率衰减 scheduler torch.optim.lr_scheduler.StepLR(optimizer, step_size30, gamma0.1) # 在每个 epoch 结束后调用 scheduler.step()4.5 推理结果全黑或全白现象predict.py输出的分割图要么全黑要么全白没有任何细节。原因模型权重没加载成功或者输入图像的归一化方式和训练时不一致。解决检查load_state_dict是否报错确认输入图像的像素值范围是 0-255 还是 0-1。训练时如果用了/255.0归一化推理时也要做同样的处理。5. 模型对比与调优SegNet 和 UNet 在遥感数据上的选择策略5.1 两个模型的性能差异跑完两个模型后你大概率会发现 UNet 的精度比 SegNet 高一点但 SegNet 的推理速度更快。这不是玄学是结构决定的UNet 的跳跃连接把编码器的高分辨率特征直接传到解码器保留了更多空间细节对小目标比如细窄的道路分割更好SegNet 用池化索引做上采样参数量少但细节恢复能力弱一些。对比维度SegNetUNet参数量约 29M约 31M推理速度较快稍慢小目标分割一般较好训练显存占用较低稍高适合场景实时性要求高、类别少精度要求高、多类别如果你的毕设要求是「跑通就行」SegNet 足够如果要求「精度尽可能高」UNet 更合适。两个都跑一遍做对比论文里还能多写一章实验分析。5.2 提升分割精度的几个实用技巧第一个技巧是加数据增强的多样性。项目自带的DataArgument.py只做了旋转和高斯噪声你可以加上随机裁剪、亮度对比度调整、水平翻转。遥感图像对水平翻转不敏感不像文字所以翻转是安全的增强方式。# 扩展 DataArgument.py 的增强函数 def augment_v2(img, label): # 随机水平翻转 if random.random() 0.5: img img.transpose(Image.FLIP_LEFT_RIGHT) label label.transpose(Image.FLIP_LEFT_RIGHT) # 随机亮度调整 if random.random() 0.5: enhancer ImageEnhance.Brightness(img) img enhancer.enhance(random.uniform(0.8, 1.2)) return img, label第二个技巧是用预训练权重初始化编码器。SegNet 的编码器是 VGG16UNet 的编码器可以换成 ResNet34用 ImageNet 预训练权重能显著加快收敛。不过项目里没有提供预训练加载的代码需要自己加。# 加载 VGG16 预训练权重到 SegNet 编码器示例 import torchvision.models as models vgg16 models.vgg16(pretrainedTrue) # 把 vgg16.features 的前几层权重拷贝到 segnet 的编码器第三个技巧是调整损失函数。CrossEntropyLoss在类别不平衡时效果不好遥感数据里背景像素通常远多于目标像素可以换成 Dice Loss 或者带权重的 CrossEntropy。# 带权重的 CrossEntropyLoss weights torch.tensor([0.3, 0.7]) # 背景权重低目标权重高 criterion nn.CrossEntropyLoss(weightweights)5.3 验证模型是否真的学到了训练 loss 下降不代表模型学到了有用的特征。我一般会做两件事验证第一用predict.py跑几张训练集里的图看分割结果是否合理第二在验证集上算 IoU交并比。# 简单的 IoU 计算 def compute_iou(pred, label, num_classes2): ious [] for cls in range(num_classes): pred_cls (pred cls) label_cls (label cls) intersection (pred_cls label_cls).sum() union (pred_cls | label_cls).sum() if union 0: ious.append(float(nan)) else: ious.append(intersection / union) return np.nanmean(ious)IoU 低于 0.5 说明模型基本没学到东西需要检查数据标签和训练配置IoU 在 0.6-0.7 之间是正常水平如果能到 0.8 以上说明模型和数据处理都做得不错。从那以后我每次拿到一个新的分割项目都会先跑一遍数据增强脚本用predict.py可视化几张训练样本确认图像和标签是对应的再开始训练。这个习惯帮我省了很多来回排查的时间。希望帮到你。本文还有配套的精品资源点击获取
返回列表