资讯详情

资讯详情

Python深度学习城市遥感水体提取:U-Net与AttU-Net实战

简介一套基于Python深度学习的高分辨率城市遥感图像水体提取系统源码配套完整文档说明主要面向本科毕业设计、期末大作业和课程设计等场景解决遥感影像中水体区域的智能识别、分割与可视化问题适合希望快速搭建可运行项目的学习者。资源压缩包共27个文件包含11个Python源文件、13张图片、1个模型权重文件、1个数据文件及1份说明文档整体仅726KB。其中Python代码覆盖数据预处理、模型搭建、训练测试等完整流程图片用于展示系统界面或模型结构pth文件为训练好的权重md文档提供项目说明与使用指引。目前已有144人学习下载项目经过严格调试可直接部署运行。代码注释清晰界面简洁美观操作便捷下载后可快速用于水体提取实验、毕设答辩或二次开发具备较高的参考和实用价值。1. 高分辨率城市遥感图像的水体提取这份 Python 深度学习毕设到底能跑通什么城市遥感图像里的水体提取一直是遥感方向毕业设计的高频选题。难点不在模型本身而在数据高分辨率影像里建筑阴影、深色屋顶、柏油路面和水体在光谱上高度相似传统阈值分割几乎必翻车。用 Python 深度学习做这件事核心是把 U-Net 这类分割网络迁移到遥感场景里并在预处理和训练策略上做针对性适配。这套系统源码正是沿着这条线做的网络结构选的是 U-Net 与注意力机制增强的 AttU-Net训练、评估、单张预测和批量测试的代码是分开的还附带了一份训练好的权重文件 train.pth 和结果记录 res.csv整套代码带注释适合做毕业设计二次开发也适合想快速验证遥感水体分割效果的从业者直接跑一遍流程。先说明一点这套系统的适用对象是城市尺度的遥感影像你可以把它理解为「给高分影像做逐像素语义分割把水体和背景区分开」的完整工程框架。源码里同时放了两种网络结构方便对比实验而对比实验正是毕业设计答辩时最容易被追问的点。下面从网络选型、数据处理、训练评估、踩坑排查到单张预测的完整链路逐层拆开讲。2. U-Net 与 AttU-Net 选型为什么水体分割不选分类网络2.1 语义分割和图像分类的本质区别很多第一次接触遥感水体提取的同学上来会先想到用 ResNet 或者 VGG 做分类——把图像切成小块判每块「是不是水」。这条路在精度上很难走通原因很直接分类网络输出的是整张图的标签边界信息在卷积和池化过程中大量丢失水体边界恰恰是遥感应用里最敏感的指标差几个像素面积估算就失真了。分割网络的输出是逐像素的类别概率图每个像素都拿到一个预测标签天然保留了边界信息。那为什么选 U-Net 而不是 FCN 或者 DeepLab在毕设场景下U-Net 有三个不可替代的优势第一结构简单编码器-解码器加跳跃连接代码量少调试成本低第二对小样本数据友好遥感标注数据通常不会太多U-Net 的跳跃连接让梯度可以更直接地回传到浅层训练收敛速度明显快于同量级的 FCN第三解码器逐渐恢复分辨率的设计本身就是一个隐式的多尺度特征融合过程对城市影像里大小不一的水体——从主干河道到小区景观水池——都有响应。2.2 AttU-Net 在普通 U-Net 上改了什么源码里有一张 AttU-Net.png 结构图对照 U-Net.png 看两者的差异集中在跳跃连接处。普通 U-Net 的跳跃连接是把编码器特征图直接拼到解码器对应层不同尺度的特征简单拼接浅层里大量与水体无关的纹理信息会一并进入解码器导致误检。AttU-Net 在每条跳跃路径上加了一个注意力门控模块Attention Gate它的作用是学习一个空间权重图编码器特征中与水体相关的区域权重被放大无关区域被抑制再与解码器特征融合。这个改动的实际收益非常直接。城市遥感影像里最常见的误检源是建筑阴影和深色沥青屋顶它们在灰度值和局部纹理上都跟水体很像。普通 U-Net 很难在语义层面区分它们因为卷积核看到的都是「暗色区域」但注意力门控可以学到位置和上下文信息——水体通常处于地势低洼处、与周围地物有特定的空间关系这些信息被编码进注意力权重里。# 以 AttU-Net 跳跃连接处的注意力门控为例典型的实现形式如下 def attention_gate(self, g, x): # g: 解码器特征门控信号x: 编码器特征待加权 # 先做通道对齐保证两个特征图能逐元素相加 wg self.conv_g(g) # 1x1 卷积压缩通道数 wg self.bn_g(wg) # 批归一化稳定训练 wx self.conv_x(x) # 1x1 卷积这里不改变空间尺寸 wx self.bn_x(wx) out self.relu(wg wx) # 相加后过 ReLU得到注意力图 out self.psi(out) # 再 1x1 卷积输出单通道权重 out self.sigmoid(out) # 归一化到 0~1作为软权重 return x * out # 对编码器特征逐通道加权这里有几个参数要留意。conv_g和conv_x的卷积核大小一般都设为 1x1目的是在不改变空间尺度的前提下做通道压缩psi同样用 1x1 卷积把多通道注意力图压成单通道。整个注意力门控不会改变特征图的分辨率所以在网络结构上可以无痛替换普通跳跃连接。从工程角度看这套代码把两种网络各自定义在 network.py 里切换方式是修改配置参数而不是改动训练主流程。如果你是拿它做毕设强烈建议两个网络都训练一遍然后对比精度和可视化结果——这几乎是答辩现场最稳的加分项因为你展示的不只是一个能跑的模型而是「我理解了两种结构差异并做了定量验证」。2.3 城市水体提取的独特难点与网络应对高分辨率城市遥感图像天然有三个让模型头疼的特点一是地物尺度差异大一条景观河可能宽几十像素但小区里的喷泉水池只有十几个像素二是背景极其杂乱楼顶、道路、树木、车辆全部挤在一个场景里三是水体光谱不稳定水深、水质、光照角度都会改变水体的表现特征。U-Net 结构里编码器不同层级的 feature map 对应不同尺度的语义信息浅层关注边缘纹理深层关注语义类别解码器逐层恢复分辨率就是把多尺度信息融合回来的过程。AttU-Net 的注意力机制则解决背景杂乱问题它让网络在融合特征时学会「选择性关注」。这套系统里测试阶段用多个尺度对图像进行预测然后取平均值作为最终结果目的也是为了缓解尺度差异带来的漏检。3. 数据与预处理从 Urben_pre 到模型输入的完整链路3.1 代码包里数据目录怎么组织拿到这份源码先不要急着跑 train。打开压缩包你会看到data_set和Urben_pre两个目录这是核心数据位。常见做法是data_set放原始影像和标注Urben_pre放预处理后的产物命名里的_pre就是 preprocessed 的缩写。data_loader.py和dataset.py负责把这两块串起来dataset.py定义数据集类data_loader.py负责按 batch 加载和打乱顺序。# 解压后的典型目录结构以实际包内为准这里给出参考布局 . ├── data_set/ # 原始数据含影像与对应标注 │ ├── images/ # 原始高分影像tif/png 格式 │ └── masks/ # 逐像素标注水体1 背景0 ├── Urben_pre/ # 预处理输出目录切块后的样本 ├── models/ # 训练权重保存位置含 train.pth ├── network.py # U-Net / AttU-Net 结构定义 ├── dataset.py # PyTorch Dataset 类 ├── data_loader.py # DataLoader 封装 ├── data_preprocess.py # 预处理脚本 ├── enhance_image.py # 图像增强脚本 ├── solver.py # 训练主逻辑 ├── evaluation.py # 评估指标计算 ├── test_one_data.py # 单张影像预测 ├── test_data.py # 批量测试 └── res.csv # 评估结果记录先确认一件事标注图是不是二值图。水体提取的标注标准做法是 0 和 255或者 0 和 1的两类图255 代表水体。如果你自己补充数据务必保持这个约定否则 loss 计算会出错——这是最常见的低级问题。3.2 数据预处理脚本做了什么data_preprocess.py是整条流水线的起点。遥感影像有别于自然图像它往往是高空视角、大幅面、多波段直接喂进网络肯定会爆显存。预处理要解决三件事一是裁剪成固定尺寸的 patch二是归一化三是增强多样性。# data_preprocess.py 中典型的切块逻辑参数以实际代码为准 def crop_image(image, mask, patch_size256, stride256): patches_img [] patches_mask [] h, w image.shape[:2] for y in range(0, h - patch_size 1, stride): for x in range(0, w - patch_size 1, stride): patch_img image[y:ypatch_size, x:xpatch_size] patch_mask mask[y:ypatch_size, x:xpatch_size] patches_img.append(patch_img) patches_mask.append(patch_mask) return patches_img, patches_maskpatch_size和stride是两个最关键的超参数。patch_size 决定输入网络的样本尺寸——遥感分割任务里 256 是最稳妥的选择显存压力小训练速度快如果你显卡显存有 16G 以上可以上到 512但要注意边界细节的把握。stride 的含义是步长等于 patch_size 时表示各 patch 之间没有重叠这是最省算力的切法小于 patch_size 时产生重叠区域相当于给边缘像素多了一次出现在训练集中的机会能明显改善切块边缘的预测断裂问题代价是训练样本量膨胀。我一般建议 stride 取 patch_size 的一半比如 256 的 patch 用 128 的 stride既增加样本量又不至于太慢。3.3 图像增强城市遥感场景里的签约技巧enhance_image.py做的增强不是给自然图像用的那种随机裁剪加翻转——遥感影像有自己的特点高分辨率影像动态范围大水体在不同光照、不同天气下表现差异明显。有效的增强策略通常包含随机水平/垂直翻转、随机旋转 90 度、随机亮度对比度扰动、高斯噪声和随机高斯模糊。# enhance_image.py 中增强 pipeline 的典型写法 import random import numpy as np from PIL import Image def enhance(img, mask): # 随机翻转 if random.random() 0.5: img img.transpose(Image.FLIP_LEFT_RIGHT) mask mask.transpose(Image.FLIP_LEFT_RIGHT) # 随机旋转90 的倍数保持标注对齐 k random.choice([0, 1, 2, 3]) img img.rotate(k * 90, expandTrue) mask mask.rotate(k * 90, expandTrue) # 对比度和亮度扰动模拟不同光照条件 img img.point(lambda p: max(0, min(255, p * random.uniform(0.8, 1.2)))) return img, mask这里务必注意增强必须对图像和标注同步做。翻转、旋转这些几何变换没问题但色彩抖动、对比度变化这类针对灰度值的变换只作用于图像标注不能跟着动。如果增强丢掉了 mask 的对应操作训练时标签错位模型大概率学出一个「漂移」的分割结果这是遥感分割任务里非常隐蔽的翻车点也是很多网上教程不会提醒你的地方。实际训练时preprocess 会先把原始影像裁成训练 patch然后再接增强。如果你不打算重新预处理直接用源码里Urben_pre目录下已生成的 patch 也能训练但要跑通全流程、理解每个环节的作用建议自己在小范围数据上重跑一遍预处理脚本。4. 训练与评估solver.py 里的参数与 IoU 指标的门道4.1 训练主进程 solver.py 的工作流solver.py承担的是训练调度的全部工作加载配置、创建网络、定义损失函数、选择优化器、控制 epoch 循环和 checkpoint 保存。它一般不会提供很花哨的命令行交互但会把每一步写清楚方便你改参数。# 启动训练以实际 README 中的 entry point 为准 python solver.py首次跑通之前把注意力放在显存占用上。遥感影像的 patch 即便裁到 256×256batch size 设 8显存占用也常在 8G 以上这还没算 AttU-Net 额外带来的注意力权重计算开销。如果显存不够调低 batch 比调低图片尺寸更划算——图片尺寸影响感受野batch size 只影响梯度的稳定性。4.2 关键训练参数的选择逻辑下面是这套系统训练阶段最核心的几组参数也是你拿去做二次开发时最需要动的配置。这些取值是遥感分割场景下很常见的起点你可以根据自己的硬件和数据量做调整。参数名常见取值调整逻辑batch_size4 ~ 8显存不足时最先降这个优先保证图像尺寸不变learning_rate1e-4 ~ 3e-4遥感分割样本量不大学习率太高会震荡optimizerAdam默认 betas(0.9, 0.999)遥感任务不加 weight decay 通常也没问题epochs80 ~ 150数据量少时模型容易过拟合配合早停策略损失函数BCE Dice正负样本比例失调时Dice 项给前景更多梯度输入尺寸256×256城市水体尺度跨度大尺寸越大保留细节越多但显存压力上升损失函数是这里面的重点。水体掩膜里背景像素远多于水体像素类不平衡问题严重。只用 BCE二分类交叉熵模型很快会学会「全预测为背景」因为这样 loss 也很低但水体一个都提取不出来。常见做法是 BCE 和 Dice Loss 加权相加Dice Loss 直接优化分割目标本身——它的分子是预测和标签的交集分母是两者面积之和天然把小目标和大目标放在同一尺度上比较。上一章里注释代码段提到的sigmoid配合 BCE 输出也是为这个组合服务的。4.3 评估指标为什么 IoU 比准确率更可信evaluation.py是答辩时你最该讲清楚的部分。水体提取的评估不能看 Accuracy——如果水体只占全图的 5%全部预测为背景准确率也有 95%但毫无意义。核心指标是 IoUIntersection over Union也叫 Jaccard 指数。$$ IoU \frac{TP}{TP FP FN} $$分子是预测为水体和真实为水体的像素交集分母再加上两个错判的部分。这个指标对边界误差和多像素偏移都很敏感是遥感语义分割的事实标准。还有 F1-score 和 Kappa 系数也常出现在res.csv里。F1 是精确率和召回率的调和平均对「少检一个水池」和「多检一块阴影」同样敏感Kappa 则衡量预测与真实标注的一致性取值在 -1 到 1 之间大于 0.8 通常被认为高度一致。# evaluation.py 中 IoU 和 F1 的简洁实现逻辑 def compute_iou(pred, mask, eps1e-6): pred (pred 0.5).astype(int) # 二值化0.5 是默认阈值 mask (mask 0.5).astype(int) intersection (pred * mask).sum() # 预测为水体且真实为水体的像素 union ((pred mask) 0).sum() # 两者至少一个是水体的像素 iou intersection / (union eps) # eps 防止除零 return iou def compute_f1(pred, mask, eps1e-6): pred (pred 0.5).astype(int) mask (mask 0.5).astype(int) tp (pred * mask).sum() fp (pred * (1 - mask)).sum() fn ((1 - pred) * mask).sum() precision tp / (tp fp eps) # 预测的水体里有多少是真的 recall tp / (tp fn eps) # 真实的水体里有多少被找到 f1 2 * precision * recall / (precision recall eps) return f1训练完后res.csv里会记录每个 epoch 或每次评估的这些指标。读这张表时有个习惯不要只看最后的 IoU要看 IoU 和 F1 的 变化曲线。如果 F1 高但 IoU 低说明预测结果偏保守边界缩了一圈误检少但漏检多如果 IoU 高但 F1 偏低说明预测偏激进边界外扩了。真正的理想模型应该是两个指标同步上升。5. 测试流程与常见问题排查从单张到批量5.1 单张影像预测的执行路径训练完成后models目录下的train.pth是「可以立即用来推理」的权重文件。test_one_data.py负责加载这个权重对一张完整的遥感影像做水体提取并输出结果图。与训练时的 patch 方式不同单张预测时通常把大图切成重叠 patch 分别预测再把预测结果拼回去。# 执行单张预测以代码包内脚本的调用方式为例 python test_one_data.py --image path/to/your_image.tif --output result_dirtest_data.py则是批处理版适合对一个文件夹下的多张影像做统一预测输出。如果你只需要看效果test_one_data.py是更好的入手点——它能输出叠加可视化图直观看到哪些区域被识别成水体。5.2 完整测试链路从预处理到评价的闭环需要特别注意测试时的预处理必须和训练时完全一致。很多毕设翻车就翻在这里——训练时做了归一化、对比度拉伸测试时直接拿原始图进网络结果精度崩掉却误以为是模型没训练好。正确的测试推理链路应该是对输入影像做与训练时相同的预处理归一化、尺寸调整必要时切块并预测拼回全图对预测概率图做阈值二值化默认阈值 0.5对二值图做连通域后处理去除面积过小的噪声区域与原图叠加保存目视检查结果# test_one_data.py 中推理主流程的缩略示意 import torch import numpy as np from network import AttU_Net # 与训练时一致的结构定义 model AttU_Net(img_ch3, num_classes1) model.load_state_dict(torch.load(models/train.pth)) model.eval() with torch.no_grad(): img preprocess(your_image) # 与训练一致 pred model(torch.unsqueeze(img, 0)) # 增加 batch 维度 prob torch.sigmoid(pred).squeeze(0).squeeze(0) # 转成概率图 mask (prob.numpy() 0.5).astype(np.uint8) # 阈值分割第五步的连通域后处理常常被忽视但它对城市水体提取效果提升非常明显。城市影像中的水体通常是大块连通区域而阴影和误检噪声往往表现为零星小片。用 OpenCV 的connectedComponentsWithStats计算连通域后把面积小于某个阈值的区域直接置为背景误检率能显著下降。5.3 train.pth 到底该怎么用train.pth是这份资源里最容易被人忽略、也最值钱的单文件。它意味着你不必从头训练 80 到 150 个 epoch只要硬件环境没问题加载权重后直接做预测几分钟就能看到效果。这对于两个场景特别有用一是你要快速验证这套方法在你自己数据上的表现二是答辩前需要一组「可复现结果截图」——用现成权重产出可视化结果远比口头描述有说服力。如果你要在自己的数据上微调建议加载train.pth作为初始化权重而不是随机初始化。遥感图像特征有相当强的通用性用预训练权重做初始化通常能让收敛速度快 30% 以上这是遥感分割项目里节省时间的常规操作。6. 避坑与排查城市水体提取的五个翻车现场6.1 显存溢出整图直接送进网络现象训练刚起步就报CUDA out of memory或者推理大图时一个 patch 都跑不动。原因遥感影像幅面大原始 tif 可能是几千乘几千像素直接把整图喂进网络中间特征图会占满显存。毕设用的显卡显存普遍在 6G 到 12G 之间整图推理基本不可能。解决把推理统一改成「切 patch 重叠预测 拼接」的管线。切块尺寸根据显存调6G 显存建议 256×25612G 以上可以尝试 512×512。拼回全图时相邻 patch 之间至少保留 16~32 像素的重叠区域重复预测部分取平均概率能明显缓解拼接痕迹。6.2 模型把建筑阴影全当成了水体现象预测结果图里水体确实提取出来了但阴影部分的像素也被大面积标记为水整张图「黑成一片」。原因阴影和水体在灰度直方图上有很大的交集如果训练时数据增强里没有针对性地引入阴影样本或者注意力模块没有学到位模型就只能靠灰度值做判断自然分不开。解决第一训练数据里人为增加阴影区域的负样本——裁剪一批包含建筑阴影、树影但不含水的 patch 放进训练集第二在评估时不要只看 IoU单独统计「阴影区域里的误检率」如果偏高说明注意力模块训练不充分第三可以在输入里额外加入一个通道比如 NDWI归一化水体指数这个指数基于绿光波段和近红外波段的比值能显著拉大水体与阴影的区分度代价是输入通道从 3 变成 4网络结构要相应调整。6.3 小河道和窄水体提取不出来现象主干河道能被正确识别但小区的窄景观河、细支流完全断线或者干脆消失。原因高分辨率影像里窄水体往往只有十几个像素宽。连续多次下采样后这些小目标的特征在深层 feature map 里被淹没解码器也很难恢复。这本质上是目标尺度问题。解决预处理时用重叠 slidding window 增加小目标的重复覆盖次数或者使用多尺度训练机制——同一个 patch 缩放 0.5、1.0、1.5 倍分别输入网络综合三路输出结果。工程上更快的验证方式是先用训练好的模型跑一批小目标密集的测试图观察漏检比例。6.4 拼接后的结果图出现明显的块状边界现象单张 patch 预测看起来很好拼回整图后patch 和 patch 的接缝处出现明显的亮度跳变或割裂感。原因切块推理时靠近边缘的像素缺少上下文信息模型对边缘区域水体边界的预测置信度明显低于中心区域。如果使用的 stride 等于 patch_sizepatch 之间零重叠拼回原图必然出现割裂。解决推理阶段把步伐调到 patch_size 的四分之一到二分之一相邻 patch 重叠区域的多余预测直接取平均。我在实操中一般会用步行值 128、patch 尺寸 256重叠区域取均值后拼接。顺带一提拼图时不要忘了记录每个 patch 在原图里的世界坐标否则拼回去整张图是花的——这事儿处理起来不难但绝对值得先写在草稿里。6.5 训练 loss 不下降或直接 NaN现象loss 在前几十个 iteration 里稳如泰山或者直接变成 NaN然后报错退出。原因NaN 一般来自学习率过大导致梯度爆炸也可能是标注图里出现了超出 [0,1] 区间的异常值。loss 不降则多半是预处理环节出了问题——比如图像没有归一化到合理范围或者标注和图像错位。解决先检查数据——确认标注图的像素值只在 0 和 1 两种取值确认图像和标注是严格对齐的确认切块时没有 index 越界。再把学习率从 1e-4 起步下调到 1e-5 试一次如果 loss 开始下降说明初始学习率过高。最后加载train.pth做几次前向推理如果输出概率图看着合理就说明网络结构没问题问题出在训练配置上。7. 进阶技巧用单张预测链路做一次完整的可视化复盘拿到这份源码后我最建议你做的第一件事不是重新训练而是找到一张你自己的遥感影像走一遍test_one_data.py做一次「从原图到叠加可视化」的完整验证。这个过程能帮你确认整套代码在你机器上的可用性也能直观暴露出问题的所在。第一步准备一张测试图尽量选择覆盖水体类型丰富的场景——有宽河道、窄水渠最好还带点阴影。第二步确认预处理脚本与训练一致读图、归一化、尺寸调整一步都不能少。第三步运行预测脚本保存原始预测概率图。第四步做阈值分割和后处理生成二值水体掩膜。第五步把掩膜叠加到原图上输出影像。# 叠加可视化与后处理的常见写法 import cv2 import numpy as np def overlay_mask(image, prob_map, threshold0.5, min_area100): mask (prob_map threshold).astype(np.uint8) * 255 # 连通域分析去掉小于 min_area 的零碎区域 num_labels, labels, stats, _ cv2.connectedComponentsWithStats(mask, connectivity8) for i in range(1, num_labels): if stats[i, cv2.CC_STAT_AREA] min_area: mask[labels i] 0 # 原图与掩膜叠加水体区域染成红色 overlay image.copy() overlay[mask 0] (0, 0, 255) blend cv2.addWeighted(image, 0.6, overlay, 0.4, 0) return blend, maskmin_area这个参数值得多说一句它是根据影像分辨率和目标尺度定的。如果你的影像空间分辨率是 0.5 米一个 100 平方米的池塘大约对应 400 个像素那min_area可以设置在 50 左右如果是 2 米分辨率同样一个池塘只有 25 个像素设置太高会把真水体也滤掉。所以这个参数在你评价结果时留个心眼不要永远用同一个值否则不同分辨率的数据会被同一套后处理规则埋掉一批目标。复盘这一步尤其建议大家做一组对比分别用U-Net和AttU-Net的权重同一个输入图像推理一遍把两张叠加图放在一起对比去找「阴影里的水泥建筑被哪些模型判断成了水」。如果你找到了一处普通 U-Net 错误、AttU-Net 正确的位置那这句话几乎就是到答辩时最有力的材料。另外用可视化结果做项目文档配图时要记得保留原图、预测结果的二值图和叠加图三个文件建议依次排列。这一张对比图包含的信息量往往比好几页文字描述更直观。从那以后我遇到遥感分割项目都会先强制自己走一遍这条链路加载权重 → 跑单张预测 → 看叠加图 → 翻res.csv指标 → 再回训练配置打磨参数绝不在没有可视化结果的情况下直接调整训练超参。数据集的任何变化我都会在预处理脚本里留一份记录把所有参数变化写进文件名或配置备注里——这样回看时不会被自己埋的坑绊倒。希望这份水源提取系统的拆解和踩坑记录对你有实际帮助祝顺利跑通。本文还有配套的精品资源点击获取
觉得有用,分享给同行:

为您的企业打造数字门面

稳重轻奢商务风格,端正雅致视觉,长效耐看不易过时。

立即咨询 →