简介面向计算机相关专业课程设计或期末大作业的Python项目源码基于图像分割实现卫星遥感图像国土分类适合高校图像处理、遥感应用方向的学生及需要完整项目参考的实战学习者。整个压缩包共12个文件大小约2.49MB核心为8个.py程序文件覆盖数据预处理与加载、PSPNet/DeepLabV3/DeepLabV3等主流模型定义、训练执行与日志记录另附说明文档与26组结果展示图便于按模块理解从数据准备到结果评估的完整分类流程。代码经过严格调试下载后可直接运行无需额外配置既可作遥感影像分割任务的基线项目也可在此基础上替换网络结构、调整训练参数开展对比实验资源目录层次分明训练日志与展示图还可用于结果分析和性能量化比较项目整体结构清晰能帮助学习者快速掌握遥感图像分割任务的工程实现方法适合课程设计答辩和实验报告撰写时参考。目前已有158人学习使用可作为同类课题的可靠起点。1. 拿到“python实现基于图像分割对卫星遥感图像进行国土分类”这个题目真正该盯住的是哪一环很多有Python基础的同学看到这个标题后的第一反应是去找一份U-Net源码跑通一个公开的汽车分割示例后直接把遥感影像往里丢。结果通常很一致显存不够报错就算把图压缩进去预测出来的东西也看不出地物语义。其实问题不在网络结构而在遥感图像和自然图像的两个关键差异分辨率高、类别极不均衡。前者要求做切片和拼接后者要求标签体系设计合理、损失函数做类别加权。这两步没做好模型再新也出不了成果。这篇笔记把“python实现基于图像分割对卫星遥感图像进行国土分类”串成一条完整链路先定类别与数据集再做预处理和切片然后用U-Net训练最后拼回整幅图并计算指标。你不必有遥感背景只要会Python和基础PyTorch。如果环境还没装好可以先按常见的《python安装教程》《vscode python环境配置》准备好Python 3.9到3.11再回到这里继续。很多课程设计案例源码其实只展示了模型片段真正让系统能出图的是喂数据和把结果搬回原图这些外围工程。2. 国土分类到底在分什么类别体系、数据集与U-NeT为什么是首选2.1 把地物定义成5类还是10类直接决定训练难度遥感图像分类的常见目标包括水体、林地、草地、耕地、裸地、不透水面建筑与道路等。一个典型错误是上来就把标签细化把道路、建筑、停车场、人行道都当成独立类别结果模型在细类之间互相混淆mIoU始终上不去。课程设计阶段的正确做法是在语义层面把容易混的类别先合并掉。以建筑和道路为例两者的材质不同但在中分辨率遥感影像上都是灰色调、轮廓规整边界很容易黏连。第一版先合并成“不透水面”训练稳定度会明显提升。同样耕地和草地虽然都是绿色系判断依据一个看纹理、一个看地块边界仅靠RGB信息很难分得干净不如合并成“农作物与自然植被”等基础模型跑通后再逐级拆分。原则其实很朴素类别数越少每个类别可用的像素量越大权重稳、收敛快、翻车概率低。我一般建议第一版做6类背景、水体、植被、不透水面、耕地、裸地。类别体系不只是语义描述它直接决定了损失函数里的类别权重也决定后续混淆矩阵的分析维度。做到10类以上类别权重就需要反复调对做课设的时间预算来说不太划算。2.2 公开数据集怎么选DeepGlobe、LoveDA与本地数据的取舍课设阶段肯定优先用公开遥感分割数据集不要在标注上花几周时间。下面三个数据集的名字在遥感与计算机视觉圈里都常见可以在公开学术平台检索到这里列出的是它们的基本特性和适用场景。数据集影像来源常见尺寸标签特点适合场景DeepGlobe Land Cover卫星RGB1024x10247类含urban、agriculture、forest、water、barren、unknown首选语义接近国土分类LoveDA遥感RGB1024x1024城市/乡村土地覆盖含background与多类地物想做跨区域泛化验证时用Massachusetts Roads/Buildings航拍1500x1500二分类目标道路或建筑只做单类别提取练习时用DeepGlobe是我推荐的首选因为它的类别定义本身就是一份可用的国土分类方案urban对应不透水面agriculture对应耕地forest对应植被。把它合并成5到6类和真实需求非常接近。LoveDA则自带城市和乡村两种域适合在报告里写“测试了模型在跨区域数据上的泛化表现”作为加分描述。如果老师给了本地影像那就需要在QGIS里做矢量化标注再栅格化成标签图。这个过程建议控制在两天以内。标注大图前先用512x512网格切块逐块标注避免在整张大图上画多边形时卡顿。QGIS里画完导出为TIFF再用脚本把同名的影像和标签一起切片这就是第3章的操作。2.3 U-Net会比DeepLabV3更合适的三个理由U-Net不是最新模型但在遥感分割里始终是最稳的基线。它的核心机制是编码器-解码器加跳跃连接编码器逐层下采样提取多尺度特征解码器逐步恢复分辨率跳跃连接把低层细节直接带回高层。道路、田埂这类细长结构靠的就是低层边缘信息这也是图像分割算法里U-Net长盛不衰的原因。对比来看FCN的解码器比较简单恢复出来的边界容易发糊DeepLabV3的ASPP和空洞卷积理论上对大物体更友好能在降低分辨率的同时扩大感受野但参数量大训练更吃显存超参也更敏感。在课程设计的常见硬件条件下U-Net是综合成本最低的选择。此外U-Net实现非常简洁双卷积块加转置卷积量级在两百行以内。后续想做改进加注意力模块或者把编码器换成ResNet都是顺理成章的。用U-Net跑通“unet图像分割”这条链路之后再试验其他结构也能更清楚地知道瓶颈到底在模型还是在数据。3. 拿到卫星影像别急着训练预处理、切片与数据划分3.1 从原始影像到8bit RGB直方图拉伸与通道统一公开数据集的影像通常已经做过辐射校正和正射纠正直接读入就是8bit RGB。但如果你拿到的是L1级产品或者16bit TIFF直接imread出来的图会是一片死黑或一片死白因为动态范围没有做映射。这种情况下首先要做直方图拉伸。import cv2 import numpy as np def linear_stretch(img, low_percent2, high_percent98): # 将低百分位与高百分位之间的像素线性映射到0-255 lo, hi np.percentile(img, [low_percent, high_percent]) if hi - lo 1e-6: return np.zeros_like(img, dtypenp.uint8) out (img.astype(np.float32) - lo) / (hi - lo) out np.clip(out, 0.0, 1.0) * 255 return out.astype(np.uint8) img cv2.imread(scene.tif, cv2.IMREAD_UNCHANGED) b, g, r cv2.split(img) b linear_stretch(b) g linear_stretch(g) r linear_stretch(r) rgb cv2.merge([r, g, b])逻辑说明用百分位数而不是最大最小值做归一化是为了避开云层、白色屋顶这类极亮像素的影响。如果直接用最大值整张图会被拉暗地物细节反而看不清。low_percent和high_percent一般取2和98即可云量多时可以放宽到5和95牺牲两端动态范围换取主体地物的对比度。另一个高频翻车点是通道顺序。OpenCV读图默认BGRmatplotlib和PIL读图是RGB。通道顺序一旦不一致模型依然能训练但会学到错误颜色特征。更隐蔽的是训练用cv2、预测时换成PIL导致验证集指标上下波动。我习惯在数据读取后统一转成RGB并且在整个项目脚本里固定这一约定。3.2 切片而不是缩放为什么512x512是课程设计的默认尺寸卫星遥感单景影像通常至少1024x1024整图直接进网络显存根本撑不住。常见课设显卡是GTX 1660、RTX 3060、RTX 4060显存从4G到12G不等。更麻烦的是大图直接缩放会丢失道路和边界的细节分类结果肉眼看就是一片糊。所以标准做法是滑动窗口切片。import os import cv2 import numpy as np def crop_dataset(img, mask, out_dir, crop_size512, stride256): h, w, _ img.shape idx 0 for y in range(0, h - crop_size 1, stride): for x in range(0, w - crop_size 1, stride): img_crop img[y:y crop_size, x:x crop_size] mask_crop mask[y:y crop_size, x:x crop_size] # 跳过几乎全为背景的无效切片减少训练噪音 if np.sum(mask_crop 0) crop_size * crop_size * 0.95: continue os.makedirs(os.path.join(out_dir, img), exist_okTrue) os.makedirs(os.path.join(out_dir, mask), exist_okTrue) cv2.imwrite(os.path.join(out_dir, img, f{idx:05d}.png), img_crop) np.save(os.path.join(out_dir, mask, f{idx:05d}.npy), mask_crop) idx 1 return idx这里crop_size512是U-Net在遥感分割任务上的甜点值。再大对显存压力陡增且最终有效感受野不一定吃得下再小比如256会切断道路和田埂的连续性模型容易把细长地物当成背景。训练时stride取crop_size的一半也就是256相邻切片有部分重叠等同于扩充样本量验证时就取512无重叠方便和整幅图的拼接策略区分开。mask保存用np.save而不是png主要是为了杜绝标签在中间环节被压缩或插值。PNG本身无损但一旦被OpenCV的imwrite或resize做了处理类别边界就可能被插值成新的数值。用npy保存读回来永远是原始整数矩阵这是最简单有效的后悔药。3.3 训练集、验证集与目录结构设计不少课程设计把数据混在一起随机划分模型在训练集上指标很高预测新影像时完全不能用。原因是同一幅大图里相邻切片高度相关如果随机划分训练集和验证集里很可能出现来自同一幅原始影像的切片验证指标虚高。所以划分必须按影像文件进行一个影像要么进训练集要么进验证集不能两边都出现。dataset/ ├── train/ │ ├── img/ # 512x512的8bit RGB切片 │ └── mask/ # 同名的.npy标签 ├── val/ │ ├── img/ │ └── mask/ └── test/ ├── img/ └── pred/这个目录结构的好处是训练脚本只需要扫描img目录下的文件名再按同名取mask不需要额外维护CSV清单。标签里的类别ID必须从0开始连续编号0通常定义为背景或未分类。PyTorch的CrossEntropyLoss要求label在0到C-1之间如果标签里混入255或-1训练会直接报错或者更隐蔽地导致梯度异常。我每次开始训练前都会打印一次np.unique(mask)确认标签值是否合法。4. 模型实现与训练流程让U-Net在遥感数据上真正收敛4.1 U-Net的PyTorch实现可以照抄的双卷积编码解码骨架完整的U-Net包含编码器、解码器和跳跃连接三部分。下面的代码对通道数做了收缩在保证精度的同时更适应课程设计显卡可以直接复用到自己的工程里。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), nn.BatchNorm2d(out_ch), nn.ReLU(inplaceTrue), nn.Conv2d(out_ch, out_ch, 3, padding1), nn.BatchNorm2d(out_ch), nn.ReLU(inplaceTrue), ) def forward(self, x): return self.conv(x) class UNet(nn.Module): def __init__(self, in_ch3, out_ch6, base64): super().__init__() self.enc1 DoubleConv(in_ch, base) self.enc2 DoubleConv(base, base * 2) self.enc3 DoubleConv(base * 2, base * 4) self.enc4 DoubleConv(base * 4, base * 8) self.pool nn.MaxPool2d(2) self.bridge DoubleConv(base * 8, base * 16) self.up4 nn.ConvTranspose2d(base * 16, base * 8, 2, stride2) self.dec4 DoubleConv(base * 16, base * 8) self.up3 nn.ConvTranspose2d(base * 8, base * 4, 2, stride2) self.dec3 DoubleConv(base * 8, base * 4) self.up2 nn.ConvTranspose2d(base * 4, base * 2, 2, stride2) self.dec2 DoubleConv(base * 4, base * 2) self.up1 nn.ConvTranspose2d(base * 2, base, 2, stride2) self.dec1 DoubleConv(base * 2, base) self.outc nn.Conv2d(base, out_ch, 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.bridge(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.outc(d1)参数说明in_ch3对应RGB三通道out_ch改成自己的类别数base是初始通道数默认64。显存不足时把base降到32参数减少约四分之三精度损失在课设体量下并不明显。forward里的torch.cat就是跳跃连接把解码器上采样结果与编码器同尺寸特征拼接再进入DoubleConv。BatchNorm在遥感影像样本量大、像素分布差异大的场景下能明显加速收敛。4.2 类别不平衡时交叉熵为什么失灵遥感分割的类别分布极不均衡。以城市区域为例建筑和道路可能只占百分之十的像素其余全是大片植被和空地。如果直接使用普通交叉熵模型会把所有像素都预测成占多数的那一类因为这样能最大化整体准确率但少数类mIoU趋近于零。常见的解法是给loss加权。做法是先统计训练集里每类像素比例再按反比生成权重向量。假如类别0是背景、类别1是水体、类别2是植被、类别3是不透水面、类别4是耕地、类别5是裸地那权重可以设置成类似下面的值。import torch.nn.functional as F # 权重向量顺序必须和label id严格对应 class_weights torch.tensor([0.5, 1.0, 1.0, 2.5, 1.5, 2.0]) loss F.cross_entropy(logits, target, weightclass_weights.to(logits.device))权重的顺序极其关键。label里0是背景因此class_weights[0]就是背景权重label里3是不透水面不透水面样本少权重给到2.5。权重向量长度和类别数不一致时PyTorch会直接报错更难排查的是长度一致但顺序错位这个时候训练曲线一切正常只有某些类别指标始终起不来。另一种常用策略是叠加Dice Loss。交叉熵关心逐像素命中Dice关心区域重叠度在少数类上更敏感。常见组合是total_loss 0.7 * ce_loss 0.3 * dice_loss。这个比例不用精确核心目的是让少数类在梯度中的占比重新被抬高。4.3 训练超参数区间与早停判断U-Net在遥感分割上有一组比较可靠的起始配置直接照着设通常前20个epoch就能看出趋势。参数推荐值备注input_size512显存不足可降到384或256batch_size8RTX 3060 12G可到84G显存用2~4optimizerAdamW相比Adam多一层权重衰减边界更干净lr1e-4数据量大时5e-5更稳lr_schedulerReduceLROnPlateaupatience5factor0.3epochs50~80通常第30轮前后开始稳定数据增强随机翻转、90度旋转、轻微颜色抖动img和mask必须同步变换模型保存逻辑建议只用验证集上最好的模型。每轮结束计算验证mIoU超过历史最高就整体覆盖保存best.pth。训练完成后用best.pth重新预测一遍验证集而不是用最后一轮权重因为最后一轮不一定是泛化最好的那个。这个细节在很多源码包里很容易被忽略。4.4 怎样判断模型是否正常看趋势而不是看单点战绩经常有人看到一个epoch的验证分数提高就认为模型没有问题。实际上训练loss下降但验证loss在第20轮开始反弹是过拟合的典型信号。这时优先做两件事增强数据的随机翻转和颜色抖动或者把base通道数从64降到48。如果训练loss和验证loss都保持高位并且不下降多半是学习率过大或加权配比有问题可以先把lr降到5e-5试一轮。遥感分割本质上是个黑匣子曲线不干净才是常态。不要因为一两轮波动就去换模型结构先保证数据处理和划分逻辑没有漏洞再折腾网络。课程设计里很多报错最终都指向数据管线。5. 避坑遥感分割最容易翻车的5个典型问题5.1 图读对了标签读偏了通道顺序不一致现象训练时loss持续下降但验证集预测结果中所有类别的边缘都错位或者颜色完全错乱。原因训练脚本用cv2.imread读图像得到BGR验证脚本换成了PIL的Image.open得到RGB。网络输入通道顺序变了模型学到的颜色语义全部失效。解决统一读图方式推荐全项目只用cv2读入后一律用cv2.cvtColor转成RGB或者干脆用cv2.COLOR_BGR2RGB转一次后始终以RGB张量进网络。5.2 标签被插值语义被污染现象loss很低但mIoU上不去预测图里的道路、田埂等细长结构总是断掉。原因切片时把mask和img一起resize了img用双线性插值没问题但mask的类别ID经过插值后出现了2.7、3.2这类小数最后四舍五入到错误类别。解决所有涉及mask的几何变换一律用最近邻插值保存标签用npy或PNG不用JPG预处理脚本里从头到尾不出现对mask的亮度归一化。5.3 全部像素都预测成背景类现象训练结束后验证集上的mIoU很高但看预测图整片都是背景色只有零星几个孤点。原因类别0即背景占数据集80%以上交叉熵的默认权重让模型倾向于把所有像素判为背景。解决使用4.2节的类别权重并检查权重顺序是否和label id一一对应同时统计每类像素占比确认少数类在训练集中不是完全缺失。5.4 显存不足导致训练中断现象程序跑到中途报CUDA out of memory有时在第20轮才出现。原因batch_size过大、输入512x512再加上U-Net四层下采样中间特征图占用很可观。解决先把batch_size降到2如果还爆把输入降到384最后再考虑把base通道数从64降到32。很多情况下缩到384x384对遥感大目标的精度影响并不大但显存占用能下降一半以上。5.5 整图预测出现棋盘格状接缝现象把验证集切片逐块预测再拼回大图时相邻切片边界处有明显分界带。原因推理时stride取等于切片尺寸相邻块之间没有上下文重叠边缘像素预测概率不稳定拼起来就成网格。解决推理时把stride调整为切片大小的一半重叠区域用softmax概率累加后取平均再接缝基本消失。这段代码会在第6章给出。6. 用“预测到拼接再到评估”的闭环把课程设计做出完整度课程设计能不能拿高分看的不只是训练曲线而是有没有一套可复用的流程输入任意一张大图输出一张带图例的分类专题图并附带各类别的mIoU。要做到这一步预测脚本需要自己写不能只调用训练时的逐块逻辑。def predict_full(big_img, model, size512, stride256, num_classes6): h, w, _ big_img.shape score np.zeros((h, w, num_classes), dtypenp.float32) cnt np.zeros((h, w, 1), dtypenp.float32) model.eval() with torch.no_grad(): for y in range(0, h - size 1, stride): for x in range(0, w - size 1, stride): crop big_img[y:y size, x:x size] crop_t torch.from_numpy(crop.transpose(2, 0, 1)[None]).float().cuda() / 255.0 logits model(crop_t) probs torch.softmax(logits, dim1)[0].cpu().numpy() score[y:y size, x:x size] probs.transpose(1, 2, 0) cnt[y:y size, x:x size] 1 return np.argmax(score / np.maximum(cnt, 1.0), axis-1)这里用的是softmax概率叠加而不是直接把argmax结果投票。原因很简单硬投票在重叠区会频繁抖动区域大的类别容易吞掉细长类别概率叠加近似于平滑平均最终边界稳定许多。评估阶段至少算两类指标整体mIoU和每个类别的IoU。下面的代码简洁也足够写进报告。def compute_miou(pred, true, num_classes): miou 0.0 for c in range(num_classes): inter ((pred c) (true c)).sum() union ((pred c) | (true c)).sum() iou inter / (union 1e-6) miou iou return miou / num_classes最后输出一张可视化图时可以用一个固定色板映射类别再叠加colorbar和图例让结果更像专业成果。这种做法会让答辩老师看到你对结果有校验意识。palette np.array([ [0, 0, 0], # 背景 [30, 144, 255], # 水体 [255, 140, 0], # 不透水面 [34, 139, 34], # 植被 [255, 215, 0], # 耕地 [128, 128, 128] # 裸地 ]) color_pred palette[pred]我自己在遥感分割上踩过最多的坑后来都归为三类通道顺序、标签插值、推理拼接。这三件事理顺之后模型不管换成VGG还是ResNet骨干指标波动都变得可控。上手阶段不要急着堆改进模块先把一条完整的predict和evaluate链路跑通再去动模型。希望帮到你。本文还有配套的精品资源点击获取