简介:面向遥感、地理信息等专业毕业设计的高分辨率城市遥感图像水体提取项目,基于 Python 深度学习实现,内置 U-Net 与注意力 U-Net 两种网络结构,有效解决城市复杂背景下水体自动提取的精度不足问题,可广泛应用于内涝分析、水域监测等场景。资源共 27 个文件,以 Python 源码为主,另含模型权重、数据集、说明文档、结果汇总表和网络结构示意图;代码注释详细,训练、验证、预测脚本齐备,独立的数据预处理与增强工具可迁移到其他遥感分割任务,训练好的权重文件支持直接加载验证,无需从头训练。包体约 726KB,体积轻巧而功能完整,简单部署即可运行,适合作为毕业设计、期末大作业或课程设计的完整参考。已有 199 人学习浏览,项目经过充分调试确保稳定可用,新手阅读文档后即可复现水体提取结果,并能基于清晰代码结构继续改进网络,用于高分论文或答辩展示。
1. 高分辨率城市遥感图像的水体提取:这个毕设课题到底在做什么
打开一张城市的高分遥感影像,第一眼最抢眼的不是楼,而是河网、湖面和零散的水塘。但如果真拿它去做水体提取,很快就会发现:传统的 NDWI 阈值一上城市影像就翻车,高层阴影、暗色屋面、刚铺的沥青路全都被算成“水”。这正是高分辨率城市遥感图像的水体提取这个毕设课题要解决的核心问题——不是“见蓝即水”,而是利用深度学习和像素级语义分割,从城市复杂地物里把真正的水体像元剥出来。
这个课题的本质是一门“图像语义分割”的落地演练:输入高分辨率遥感影像,输出逐像素的水体掩膜。一套完整的高分毕设交付物通常包含四块——Python 源代码、文档说明、带标注的数据集、以及训练好可复现的模型权重导出流程。适合做遥感、GIS、深度学习方向毕设或课设的学生,也适合把分割模型迁移到其他地物(建筑物、道路、植被)提取的从业者。你不需要会写遥感算法,但要能跑通 PyTorch 训练流程,并且愿意在数据处理上花时间。
2. 为什么水体提取不能只看“蓝色”:原理与模型选型
2.1 城市高分影像里“水”长什么样
在低分辨率影像上,水体像一个均质的“暗色吸光体”:蓝绿波段有少量反射,红波段开始变暗,近红外波段几乎全吸收。所以传统方法喜欢构造 NDWI = (G - NIR) / (G + NIR),把水面顶到高值区,再用阈值一分为二。这套逻辑放到城市高分影像上会出问题,因为城市里“在近红外波段也暗”的东西太多了。
高层建筑阴影在可见光和近红外上都很暗,光谱曲线和水体高度相似;新铺沥青路面的近红外反射率也低;深色屋顶、桥底透光区、湿地泥滩,每一类都可能让查表式阈值翻车。更麻烦的是,高分辨率影像把水的“不均质性”暴露出来了:水面有波纹、有船、有漂浮物,岸线有混凝土硬边界,湖里还有荷花和芦苇。也就是说,水体在像素级不再是一个干净的光谱类别,而是一个“由上下文关系定义”的地物类别。这是你选模型和设计损失函数时始终要记住的前提。
2.2 UNet 与 DeepLabV3+:两个能直接上手的基线
毕设水体提取的主流做法是用带预训练 backbone 的编码-解码结构,最常见的是 UNet,对比方案用 DeepLabV3+。UNet 的跳跃连接能把 encoder 下采样丢掉的空间细节找回来,对岸线边缘比较友好;DeepLabV3+ 的 ASPP 模块用多个不同空洞率的卷积并行提取特征(rate 一般为 6、12、18),对“一条大江 + 一堆小水洼”这种多尺度水体并存的情况更有优势。
我一般建议主线用 UNet + resnet34 预训练 backbone,显存占用适中,数据集 500 张 patch 就能训出能看的精度。DeepLabV3+ 作为对比实验写进论文,这两者在代码层面的差异主要集中在 decoder 上:UNet 是逐层拼接特征,DeepLabV3+ 是 concat 多尺度空洞特征后上采样。需要注意,不要自己从头训练 backbone,城市遥感影像虽然和下采样 ImageNet 的图片风格差异大,但预训练权重能显著加快收敛,也能缓解标注样本不足的问题。
代码里模型结构通常长这样,重点不是把每一层抄对,而是理解输入输出尺寸的变化:
import torch.nn as nn class UNetResNet(nn.Module): def __init__(self, backbone, in_channels=3, num_classes=1): super().__init__() # 以resnet34为例,取前4个stage作为encoder self.encoder = backbone self.filters = [64, 64, 128, 256, 512] # decoder每一层先上采样,再和encoder对应层awa7拼接 self.up1 = UpBlock(512 + 256, 256) self.up2 = UpBlock(256 + 128, 128) self.up3 = UpBlock(128 + 64, 64) self.up4 = UpBlock(64 + 64, 64) self.head = nn.Conv2d(64, num_classes, kernel_size=1) def forward(self, x): feats = self.encoder_features(x) # [c1, c2, c3, c4] out = self.up1(feats[3], feats[2]) out = self.up2(out, feats[1]) out = self.up3(out, feats[0]) out = self.up4(out, feats[0]) return self.head(out)这里的encoder_features需要你在 resnet 中间层手动把特征导出来,常见的做法是注册 forward hook,或者直接改 forward 函数返回各 stage 输出。UpBlock的典型实现是反卷积或双线性上采样 + 卷积 + BN + ReLU。整条链路里最容易写错的是通道数:resnet34 四个 stage 的输出通道是 64、128、256、512,拼接后要自己在代码里对齐,否则跑起来 dim mismatch 报错会找半天。
2.3 损失函数与类不平衡
城市影像里水体面积占比常常只有 5% 到 15%,大面积的非水区会把交叉熵梯度“稀释”掉,模型学到最后变成了“全都预测为非水也损失不大”。解决这个问题最稳定的做法是 Dice Loss 和 BCE 一起用,Dice 负责拉高水体类的前景召回,BCE 负责稳定梯度和边界细节。水域占比越低,Dice 在总损失里的权重就应该越大。
import torch import torch.nn as nn import torch.nn.functional as F class DiceBCELoss(nn.Module): def __init__(self, dice_weight=1.0, smooth=1e-6): super().__init__() self.dice_weight = dice_weight self.smooth = smooth def forward(self, logits, targets): # logits: [B, 1, H, W] 未经过sigmoid # targets: [B, 1, H, W] 取值0或1 probs = torch.sigmoid(logits) bce = F.binary_cross_entropy_with_logits(logits, targets) inter = (probs * targets).sum(dim=(2, 3)) union = probs.sum(dim=(2, 3)) + targets.sum(dim=(2, 3)) dice = 1.0 - (2.0 * inter + self.smooth) / (union + self.smooth) return bce + dice.mean() * self.dice_weight参数上要留意两点:第一,Dice 在 batch 维度上取平均,而不是把整批图拼成一张大图再算。因为不同 patch 的水体占比差异极大,逐样本算 Dice 能避免大水体的 patch 主导梯度。第二,dice_weight默认 1.0,水体占比特别小时可以调到 1.5 到 2.0;如果发现训练早期 loss 波动大,可以把 BCE 换成带权重的pos_weight,让正样本的梯度放大 3 到 5 倍。不要一上来就用 Focal Loss,它的两个超参数对新手不友好,调不好反而比 Dice 还难收敛。
3. 把数据集跑起来:整理、裁剪与增强的固定流程
3.1 数据集的目录约定与划分原则
不管数据集是老师给的、网上下载的公开遥感数据集,还是自己用无人机飞出来的 DOM,拿到手第一步都是“洗数据”。你需要的核心文件只有两类:原始影像和对应的逐像素标签掩膜。原始影像常见格式是 TIF 或 PNG,多波段时先确认是 RGB 三波段还是 RGBN 四波段;标签掩膜必须是单通道、和影像同分辨率的栅格,习惯上非水体记 0,水体记 255 或 1。先拿 Python 看一眼每个 band 的 dtype 和取值范围,经常能撞见标签是 uint16 的 0 和 65535,这种直接用会把你整个训练曲线带歪。
建议目录固定成这个结构,所有脚本都按这个约定读写:
project/ ├── data/ │ ├── images/ # 原始影像瓦片或整幅图 │ ├── masks/ # 标签掩膜,同名单通道png │ ├── train.txt # 每行一个文件名(不含扩展名) │ ├── val.txt │ └── test.txt ├── configs/ ├── models/ ├── utils/ ├── train.py ├── eval.py └── predict.py划分数据时有一条铁律:按“影像”划分,而不是按“patch”划分。也就是说先把整景影像列表切成 train/val/test 三份,再对每一景做滑窗裁剪。如果先把所有影像裁剪成 patch 再随机划分,同一景影像上有几十个 patch 被拆到 train 和 val,模型在验证集上见过来自同一场景的相邻子图,验证 IoU 会虚高 5 到 10 个点,答辩时一拿整幅影像测试就露馅。这个操作不需要代码,就是先写文本列表再裁剪,但很多毕设翻车都翻在这一步。
3.2 影像裁剪成 patch:尺寸、重叠与边界处理
高分影像动辄几万像素宽,原图没法直接进 GPU。常见做法是滑窗裁剪成 512×512 或 256×256 的 patch。patch 越大,上下文越完整,河流不容易被拦腰截断,但显存占用和单张 patch 的水域占比波动都变大;patch 越小,训练样本越多,但小河道在 patch 里只占几个像素,模型很容易把它们当成噪点忽略。城市水体提取我通常默认 512,训练时对应 stride 256,推理时 stride 128 并做重叠平均,缓解拼图边缘的响应不一致。
import cv2 import numpy as np import os def crop_patches(img_path, mask_path, out_root, patch=512, stride=256): img = cv2.imread(img_path, cv2.IMREAD_UNCHANGED) mask = cv2.imread(mask_path, cv2.IMREAD_GRAYSCALE) h, w = mask.shape[:2] idx = 0 for y in range(0, h - patch + 1, stride): for x in range(0, w - patch + 1, stride): p_img = img[y:y + patch, x:x + patch] p_mask = mask[y:y + patch, x:x + patch] # 过滤掉几乎全背景或几乎全水体的patch water_rate = (p_mask == 255).mean() if water_rate < 0.005 or water_rate > 0.995: continue cv2.imwrite(os.path.join(out_root, "images", f"{idx}.png"), p_img) cv2.imwrite(os.path.join(out_root, "masks", f"{idx}.png"), p_mask) idx += 1 print(f"generated {idx} patches from {os.path.basename(img_path)}")这段脚本的两个参数要注意:water_rate < 0.005过滤掉的不是“无水体”的样本,而是“水体几乎不存在”的样本。如果城市里水体非常稀少,把阈值改成 0.01,能显著加快收敛,因为模型不用把太多时间花在无数个全是楼房的 patch 上;但如果你的数据里有大量极细的排水渠,过滤阈值设太高会把它们全滤掉,漏检率会直接拉高。> 0.995是保留极少量全水体 patch,防止模型把“整块水体”当成默认输出。
还有边界问题:滑窗到达图像右下边缘时,如果剩余宽度不足 patch,脚本会直接丢弃这些边缘区域。城市影像的边缘经常是岸线或道路,直接丢弃意味着模型没学过这类边界样本。更稳的做法是对图像做 reflect 填充,再在填充区上裁剪;如果嫌麻烦,至少把边缘样本单独补采一次,别让模型对边界风格一无所知。
3.3 数据增强:哪些能用、哪些会把河道搞断
数据增强对水体提取的收益很高,因为城市水体在形态上高度重复——无非就是带状、块状、不规则形状,旋转和翻转就能产生大量等价样本。翻转变换、90 度旋转、亮度对比度扰动、高斯噪声都是安全的,它们不改变水陆拓扑关系;但随机缩放、弹性变形、随机擦除这类会拉断细长河道的增强,在城市小水体数据集上要谨慎。做毕设时我一般只保留第一类,否则训练过程中细小支流的标签被拉伸到变形,模型学到的边界是歪的。
import albumentations as A train_transform = A.Compose([ A.HorizontalFlip(p=0.5), A.VerticalFlip(p=0.5), A.RandomRotate90(p=0.5), A.RandomBrightnessContrast(p=0.3, brightness_limit=0.15, contrast_limit=0.15), A.GaussNoise(p=0.2), ]) val_transform = A.Compose([])这里的RandomBrightnessContrast是专门针对遥感影像增强稳定性的:城市阴影像元本身和水分不清,如果亮度扰动范围过大,等于人为制造更多“假水”样本,所以我限制在 0.15 以内。GaussNoise概率也设得不高,因为高分影像的传感器噪声已经很小,过度加噪反而让模型学的不是真实水体纹理。需要注意的是 albumentations 的随机旋转、翻转会同时作用于原图和掩膜,但它的文档里明确写了 mask 要传 key 为mask的数组,代码里写错成masks会导致标签和影像错位。训练时把增强放在 DataLoader 的__getitem__里做在线增强,不要离线生成一份膨胀的数据集,否则硬盘写爆而且 shuffle 后相邻样本相关性太高。
4. 从命令行到训练出模型:代码结构与关键参数
4.1 代码包怎么组织
一套好交付的水体提取毕设源码,不需要花哨,但要路径清晰。常见结构是configs放超参数配置文件,models放网络定义,utils放损失函数和评估指标,train.py、eval.py、predict.py三个入口脚本分别是训练、验证和推理。配套文档一般按四块来写:环境配置、数据准备、训练评估流程、常见问题排查。环境部分最关键,因为评分老师很可能照着你的文档在自己电脑上复现,PyTorch、OpenCV、albumentations 的版本要对齐。
环境配置这一块,最容易被卡住的是 Windows 上装 GPU 版 PyTorch。用 vscode 配 python 环境时,建议先用 conda 创建独立环境,再在 vscode 右下角选择解释器指向这个虚拟环境,不要直接用系统全局解释器。装 torch 时如果pip install torch默认装了 CPU 版,训练会慢得让人怀疑人生,正确做法是去 PyTorch 官网选 CUDA 版本对应的安装命令。没有 GPU 的机器也能跑,但建议把 backbone 换成 resnet18,patch 缩到 256,batch 设成 2,勉强能在一夜之间跑完一个 mini 实验用于验证代码正确性。
4.2 训练命令与超参数:batch size、学习率、epoch 怎么定
训练入口脚本支持从命令行传配置路径,这是我比较推荐的做法,因为毕设后期要做多组对比实验,硬编码超参数在代码里会累死自己。一个典型的启动命令长这样:
python train.py --config configs/unet_resnet34.yaml对应的 yaml 里核心参数如下,这些都是城市水体提取比较稳的起点,不是拍脑袋:
model: unet_resnet34 input_channels: 3 patch_size: 512 batch_size: 8 num_epochs: 120 base_lr: 1.0e-4 optimizer: adamw weight_decay: 0.05 lr_schedule: cosine val_interval: 5 save_best_metric: val_ioubatch size 8 的前提是有 12G 以上显存,8G 卡要降到 4,6G 卡降到 2 并配合梯度累积。学习率 1e-4 对 resnet 预训练骨干是一个通用起点;如果 backbone 不冻结,通常骨干层用 0.1 倍学习率,decoder 用全速率,但毕设实验不必做得这么细,统一 1e-4 也能收敛。epoch 不是越多越好,120 个 epoch 配合 cosine 退火,大部分结果在 60 到 80 个 epoch 时已经稳定,训练时盯着验证 IoU 保存最优权重即可。
optimizer = torch.optim.AdamW(model.parameters(), lr=args.base_lr, weight_decay=0.05) scheduler = torch.optim.lr_scheduler.CosineAnnealingLR(optimizer, T_max=args.num_epochs) best_iou = 0.0 for epoch in range(args.num_epochs): model.train() for step, (imgs, masks) in enumerate(train_loader): imgs = imgs.to(device) masks = masks.to(device) logits = model(imgs) loss = criterion(logits, masks) optimizer.zero_grad() loss.backward() optimizer.step() if epoch % args.val_interval == 0: val_iou = evaluate(model, val_loader) print(f"epoch {epoch}, loss {loss.item():.4f}, val_iou {val_iou:.4f}") if val_iou > best_iou: best_iou = val_iou torch.save(model.state_dict(), "best_unet.pth")这段训练挖的关键点是优化器选了 AdamW,配合 weight_decay 0.05 在大模型上比 SGD 稳;判断最优模型的指标是验证集 IoU 而不是训练 loss,因为水体提取的最终目标是分割精度不是拟合程度。训练初期 loss 下降不快别着急,预训练 backbone 的底层特征在适应遥感图像域,一般在 10 个 epoch 后 IoU 才有肉眼可见的提升。如果 loss 出现 NaN,优先检查是否输入图里出现了 NaN 像素或标签越界。
4.3 评估指标与可视化:把预测结果叠回原始影像
毕设答辩最看重两个东西:一张精度表格和一组可视化对比图。精度表格至少要包含 IoU、F1、Precision、Recall 和总体精度 OA。对水体这种单类分割,IoU 就是水体的类别 IoU,F1 是 Precision 和 Recall 的调和平均。城市场景里漏检和虚警的代价不一样,如果项目要求水系监测完整性,Recall 优先;如果要求是“图上画出水边界,错别太多”,IoU 优先。
import numpy as np def compute_metrics(pred, gt): # pred/gt 传入前需归一化为0/1 pred = (pred > 0.5).astype(np.uint8) gt = (gt > 0.5).astype(np.uint8) tp = np.sum((pred == 1) & (gt == 1)) fp = np.sum((pred == 1) & (gt == 0)) fn = np.sum((pred == 0) & (gt == 1)) tn = np.sum((pred == 0) & (gt == 0)) iou = tp / (tp + fp + fn + 1e-6) precision = tp / (tp + fp + 1e-6) recall = tp / (tp + fn + 1e-6) f1 = 2 * precision * recall / (precision + recall + 1e-6) oa = (tp + tn) / (tp + tn + fp + fn + 1e-6) return {"IoU": iou, "F1": f1, "Precision": precision, "Recall": recall, "OA": oa}读取预测图时要注意存图格式。模型输出是 0 到 1 的概率,要比较或存图时先乘 255 再转 uint8,否则 cv2.imwrite 直接写入浮点数组会得到全黑或全白的废图。可视化对比时,把原图、真值、预测、差异四列并排放进一张图里,差异图用红色标注漏检、绿色标注虚警,这是所有答辩老师都能一眼看懂的表达。
5. 水体提取的常见翻车现场与排查方法
5.1 阴影和暗色屋顶被误判成水体
现象:预测结果里,上午时段高层建筑的东侧阴影一整片都是“水”,深蓝色玻璃幕墙也被提取成水体。整体 IoU 看着还行,但投影到整景影像上满是“假河”。
原因:水体在高分影像上的光谱形态和阴影太接近,尤其是近红外波段同样暗。模型如果没在训练集里见过足够的“带建筑物阴影的水体样本”,就会把“暗”当成“水”。
解决:治本手段是让数据里阴影样本足够多,训练时不要手动过滤那张满是阴影的 patch,反而要保留它们。治标手段有两个,一是给模型加输入通道,把 NDWI 或 NDVI 作为第 4 通道拼进去,让模型在光谱上有额外线索区分阴影(阴影区域 NDVI 也很低,但水体 NDWI 更高);二是在后处理阶段对预测结果做阴影检测掩膜,把判定为阴影的连通域从水体结果中扣掉。如果你用了多光谱影像,我建议做 5 通道输入:R、G、B、NDWI、NDVI,比单靠三通道稳得多。
5.2 细小河流漏检,连通性不达标
现象:主干河流和湖泊都提取出来了,但城区段细河道断成数截,一些只占 3 到 5 个像素宽的小水渠直接消失。指标上 Recall 很低。
原因:裁剪成 patch 后,细河流在单张 patch 里只占很小面积,Dice Loss 对它几乎不敏感;且划窗裁到水陆交界处时,patch 里只有一小段河道,模型学不到“这截暗色细线延伸到图外是水道”的上下文。
解决:训练阶段把 patch 内水体占比的过滤阈值调低,特意保留含细水体的样本,并可以按水体面积做重采样,让包含细小水体的 patch 权重加大;推理阶段用重叠滑窗加平均投票,stride 取 patch 的一半,这样细河道至少被两个窗口覆盖;后处理用形态学闭运算连通断口。闭运算核不能大,3×3 即可,核一大容易把小水塘和真实水体糊成一团。
5.3 显存溢出:patch 太大或 batch 太大
现象:训练刚起步就报RuntimeError: CUDA out of memory,代码没有任何逻辑问题,纯是资源不够。
原因:512×512 patch、batch 8、resnet34 编码器在 8G 显存显卡上显存占用约 7G 左右,算上计算图细节很容易超。
解决:三选一或组合用。batch 降到 2 最省事,但收敛会变慢;用梯度累积模拟大 batch,在 4 步内累积梯度再更新一次参数,等效 batch 仍是 8;或者把骨干换成 resnet18,参数量少一半,512 patch 下能保住语义信息。梯度累积的代码就是跳过 optimizer.step,代码如下:
accum_steps = 4 optimizer.zero_grad() for step, (imgs, masks) in enumerate(train_loader): logits = model(imgs.to(device)) loss = criterion(logits, masks.to(device)) loss = loss / accum_steps # 平均累积损失的梯度 loss.backward() if (step + 1) % accum_steps == 0: optimizer.step() optimizer.zero_grad()注意第 8 行的loss / accum_steps不能省,否则累积得到的梯度是原来 batch 的 accum_steps 倍,等价于学习率被放大,训练会震荡甚至发散。显存不够时优先降 batch,其次是降 patch,最后才是换模型。尽量别用 torch.no_grad 推理来解决训练显存问题。
5.4 训练集和验证集同源导致指标虚高
现象:训练日志里 val_iou 冲到 0.95,看着稳得很,但用训练好的权重对另一景未参与训练的影像做预测,效果明显下降。
原因:这是毕设里最常见也最阴的坑——数据划分按 patch 随机分,而不是按影像分。同一景影像上相邻 patch 高度相似,验证集里混进了训练集的“近亲”,模型记忆的是影像风格,不是水体泛化特征。
解决:回到第 3.1 节,严格按影像级划分。更进一步的建议是:如果数据集包含多个不同来源或不同季节的影像,尽量让 train、val、test 各自覆盖不同来源,测试集务必从没参与过训练调参的影像中选。写裁剪脚本时先按影像名哈希分桶,再进循环裁剪,这样后续新加的影像不用改逻辑,也不会再犯同源划分错误。
5.5 标签里的“半水半陆”像元
现象:训练 loss 在某个水平下不去,预测结果在岸线附近总有一圈模糊带;指标不高不低卡在 0.8 附近上不去了。
原因:手工标注的岸线区域存在大量灰色地带——浅水、湿地、软泥、苔藓,标注人员可能把同一块地方在不同的 patch 里标成 0 或 255。模型被迫拟合这些互斥标签,学到的边界是概率模糊的。
解决:清洗标签是最痛苦但收益最高的一步。把掩膜中既不是 0 也不是 255 的像素强制二值化,做法是取 128 为阈值:小于 128 归非水,大于等于 128 归水。评估时不要对整条岸线求 IoU,而是把真值掩膜向内腐蚀 5 个像素再算指标,只评估水体内部的稳定区域。这不算作弊,因为论文里要说明清楚评估策略,主体指标之外的边界指标可以单独报告。如果标签实在太乱,可以在 loss 里对靠近岸线的像素降权,但对毕设而言,直接清洗标签更省事。
6. 验收前要做的事:精度把关与进阶方向
跑通训练只是完成了一半,答辩前一定要用真正没见过的测试影像做一次整景推理验收。做法是把测试影像裁成 patch,逐 patch 预测后按滑窗位置拼回整幅图,拼接时把重叠区域取预测概率平均值,这样能消除窗口边缘强度偏低的拼缝伪影。拼完后再把小于一定像素面积(例如 50 像素)的孤立连通域当成噪声滤掉,但注意不要误删真实的小水塘,过滤阈值按你数据里最小水体的面积来定。
精度把关的核心是把结果按场景拆开看,而不是只看一个总 IoU。我习惯把预测结果叠加到影像上,按“城市河道、开阔湖面、山区暗色区域、桥下阴影”四类场景各截取一张大图,统计每个场景里的虚警与漏检。这样做的好处是提前发现自己模型最怕哪一类样本,并能在答辩 PPT 上给出诚实的失败案例分析——老师更认可你知道边界在哪里。
进阶方向主要有三条:一是把 UNet 换成带 transformer encoder 的 SegFormer,看 pretrained 权重下能不能提升细河道的长程依赖建模;二是给模型加一个边缘监督分支,对岸线做 3 像素宽的边缘权重增强,能明显改善边界精度;三是把传统特征工程接进来,比如将 NDWI 作为第四通道输入,拿它和纯 RGB 的结果做消融,这种对比实验是大论文里最有说服力的一节。
我自己当年做这个题目时,最大的教训就是数据集划分图省事,导致答辩前一周才发现验证集指标虚高,临时重新划分、重训、重调,整个人被折腾到脱层皮。从那以后,我拿到任何分割数据第一件事永远是先按景划分、再裁剪、再清洗标签,顺序错一步后面全是血泪。这套流程对建筑物提取、道路提取同样适用,希望帮到你。
本文还有配套的精品资源,点击获取