简介:这份资源是面向高校计算机、人工智能及相关专业学生的毕业设计参考项目,围绕DeepLabv3+模型展开高分辨率航拍图像的语义分割实践,适合具备一定Python与深度学习基础、需要完成遥感或航拍场景分割课题的读者。压缩包共184个文件,约477KB,以95个py源码文件为主体,辅以84个pyc编译文件、3个ipynb实验笔记、1个txt与1个md说明文档,覆盖模型搭建、训练与推理等环节。内容预览显示项目涉及DeepLabv3+基线流程,并整合了Swin、ResNet、HRNet、Twins、BiSeNetV2、BEiT等多种骨干网络实现,便于对比不同架构在航拍分割任务上的表现。目前已有204人学习下载,读者可借此获得完整的项目目录结构、可复现的训练推理代码与多模型对照思路,为毕业设计选题、实验复现与论文撰写提供直接参考。
1. 航拍语义分割毕设:这套 DeepLabv3+ 代码到底能不能直接跑
去年帮两个学弟看毕设,一个用 U-Net 跑航拍图,训练到 40 轮 mIoU 卡在 0.52 上不去,另一个换了这套 DeepLabv3+ 的代码包,同样的数据、同样的显卡,第 30 轮就摸到 0.61。差距不在数据,在空洞卷积和 ASPP 对多尺度目标的处理方式——航拍图里一栋楼和一辆车可能只差几十个像素,普通下采样+上采样根本抓不住这种尺度跨度。
这套资源就是围绕这个痛点来的:一份 Python 实现的 DeepLabv3+ 高分辨率航拍图像语义分割项目,定位是毕业设计级别的完整可跑代码。它不只是给你一个deeplabv3plus_baseline_offline_out.ipynb就完事,包里还塞了sam_arch_online.ipynb、sam_arch_offline_out.ipynb两个 SAM 架构相关的 notebook,以及swin.py、resnet.py、hrnet.py、twins.py、bisenetv2.py、beit.py六个骨干网络文件。这意味着你可以在同一套训练框架下换 backbone 做对比实验,毕设里「不同主干网络对分割精度的影响」这一章直接有素材。
适合谁:正在做计算机视觉方向毕业设计、需要一份能跑通、能改、能写进论文的语义分割基线代码的人。如果你连 Python 环境都没配过,建议先把python安装教程和vscode python环境配置过一遍再回来,不然光配环境就能耗掉两天。下面按「资源结构 → 环境与数据 → 训练与换骨干 → 避坑 → 进阶技巧」的顺序拆,每一步都落到能复现的操作上。
2. 拆开代码包:DeepLabv3+ 的 ASPP 与六个骨干文件怎么配合
2.1 为什么航拍图非要用 DeepLabv3+ 而不是普通 FCN
航拍图像有个绕不开的特点:同一张图里目标尺度差异极大。一张 1024×1024 的无人机影像,可能同时包含整片操场(占几百像素)和单个行人(占十几个像素)。普通 FCN 用固定感受野去卷,要么大目标分割破碎,要么小目标直接漏掉。
DeepLabv3+ 的解法是两条线并行。第一条是 ASPP(Atrous Spatial Pyramid Pooling),用不同膨胀率的空洞卷积在同一特征图上抓多尺度上下文。膨胀率 6、12、18 三路加上全局平均池化一路,concat 起来后每个像素都能「看到」不同范围的邻域。第二条是编码器-解码器结构,编码器出深层语义,解码器把浅层特征拿回来做融合,恢复边界细节。航拍图里建筑边缘、道路轮廓这些高频信息,全靠解码器这条线救回来。
常见做法是把 backbone 设成 ResNet-50 或 ResNet-101,配合 output_stride=16。output_stride 这个参数控制特征图下采样倍数:设 8 精度更高但显存吃紧,设 16 是精度和显存的平衡点,设 32 就太粗了,航拍小目标基本丢光。我一般先跑 output_stride=16,显存够(12G 以上)再试 8。
2.2 六个骨干文件分别对应什么场景
包里那六个.py不是随便凑数的,每个对应一类骨干网络,换着用就是一组现成的对比实验:
| 文件 | 骨干类型 | 特点 | 适合的毕设章节 |
|---|---|---|---|
resnet.py | CNN | 最稳,预训练权重好找,收敛快 | 基线实验、对照组 |
hrnet.py | CNN | 全程保持高分辨率,小目标友好 | 高分辨率航拍图主实验 |
bisenetv2.py | 轻量 CNN | 参数量小,推理快 | 实时性/轻量化对比 |
swin.py | Transformer | 全局建模强,显存需求高 | 前沿方法对比 |
twins.py | Transformer | 局部-全局交替注意力 | 创新点章节 |
beit.py | Transformer | 掩码预训练范式 | 预训练策略讨论 |
选型逻辑很直接:如果你的毕设题目偏「工程落地」,用resnet.py或bisenetv2.py把流程跑通、指标做扎实就够了;如果导师要求「体现前沿性」,就上swin.py或twins.py,但要有心理准备——Transformer 类骨干在航拍小数据集上容易过拟合,通常需要更强的数据增强和更长的 warmup。
2.3 三个 notebook 的分工
deeplabv3plus_baseline_offline_out.ipynb是主训练入口,baseline 配置,离线输出。sam_arch_online.ipynb和sam_arch_offline_out.ipynb是 SAM 架构相关的实验,online 和 offline 的区别通常在数据加载方式或推理模式上。实际用的时候,先跑 baseline 那个把流程走通,确认数据管道没问题,再去动 SAM 的两个。
提示:notebook 里的路径大概率是作者本机的绝对路径,第一次跑之前全局搜一遍
/home/或C:\\之类的字符串,换成你自己的数据目录,不然会直接报 FileNotFoundError。
2.4 环境依赖与最小可跑配置
这套代码是 PyTorch 生态,依赖不算复杂。我一般用 conda 建独立环境,避免和系统 Python 打架:
# 创建独立环境,Python 版本建议 3.8~3.10 conda create -n deeplab_rs python=3.9 -y conda activate deeplab_rs # 安装 PyTorch,按你的 CUDA 版本选,这里以 CUDA 11.8 为例 pip install torch torchvision --index-url https://download.pytorch.org/whl/cu118 # 分割任务常用依赖 pip install numpy opencv-python pillow matplotlib tqdm tensorboard逻辑说明:先隔离环境是为了避免python安装时常见的版本冲突——很多毕设翻车就翻在系统里同时装了 torch 1.x 和 2.x。torchvision必须和torch版本对齐,用官方 index-url 装最省事。opencv-python用于读航拍图(tif/png 都吃),tensorboard用来盯 loss 曲线。
参数说明:CUDA 版本用nvidia-smi查右上角那个数字,别照抄我的 11.8。如果显卡不支持 CUDA,CPU 也能跑,但航拍图训练在 CPU 上基本等于挂机,一张图前向就要好几秒,不推荐。
装完验证一下:
import torch print(torch.__version__) print(torch.cuda.is_available()) # 必须 True,否则后面训练全在 CPU 上爬 print(torch.cuda.get_device_name(0))如果is_available()返回 False,先别急着改代码,八成是 torch 和 CUDA 版本没对上,重装 torch 比调代码快。
3. 数据管道与训练:从航拍原图到可训练 mask 的完整链路
3.1 航拍语义分割数据集怎么组织
语义分割的数据组织比分类麻烦,因为每张图要配一张同名的 mask。常见做法是images/和masks/两个目录,文件名一一对应:
dataset/ ├── images/ │ ├── 001.png │ ├── 002.png │ └── ... └── masks/ ├── 001.png ├── 002.png └── ...mask 是单通道灰度图,像素值就是类别 id:0 背景、1 建筑、2 道路、3 植被……具体几类看你的数据集。这里有个血泪经验:mask 的像素值必须是连续的整数,从 0 开始。我见过有人用 0/128/255 标类别,结果训练时 loss 直接 NaN,因为 CrossEntropyLoss 默认按 0~C-1 的类别索引去取 logits,128 这种值直接越界。
如果手上只有彩色标注图(比如用 labelme 导出的),需要先转成灰度索引图:
import numpy as np from PIL import Image # 把 RGB 标注图转成单通道类别索引图 def rgb_to_index(mask_path, color_map): # color_map: {(R,G,B): class_id} rgb = np.array(Image.open(mask_path).convert('RGB')) index = np.zeros(rgb.shape[:2], dtype=np.uint8) for color, cid in color_map.items(): match = np.all(rgb == color, axis=-1) index[match] = cid return index # 示例:三类航拍标注 color_map = { (0, 0, 0): 0, # 背景 (255, 0, 0): 1, # 建筑 (0, 255, 0): 2, # 植被 } idx = rgb_to_index('masks/001.png', color_map) Image.fromarray(idx).save('masks/001_index.png')逻辑说明:np.all(rgb == color, axis=-1)是在通道维度上比对,只有三个通道都等于目标颜色才算命中,避免把相近颜色误判。参数说明:color_map必须覆盖标注图里出现的所有颜色,漏一个颜色那类像素就全变背景了,训练时表现为某一类 IoU 恒为 0。
3.2 数据增强:航拍图别乱翻转
航拍图有个特殊性——它有方向语义。普通自然图像左右翻转没问题,但航拍图里「道路走向」「建筑朝向」是有意义的,垂直翻转会让阴影方向反掉,模型学到错误的空间先验。
我一般这么配增强:
import albumentations as A train_transform = A.Compose([ A.RandomCrop(height=512, width=512), # 航拍图大,随机裁剪比缩放好 A.HorizontalFlip(p=0.5), # 水平翻转可接受 A.RandomRotate90(p=0.5), # 90度旋转,保持方向语义 A.ColorJitter(brightness=0.2, contrast=0.2, p=0.3), # 光照变化 A.Normalize(mean=(0.485, 0.456, 0.406), std=(0.229, 0.224, 0.225)), ])逻辑说明:RandomCrop而不是Resize,是因为航拍图缩放到统一尺寸会破坏小目标的像素占比,512 的裁剪块既保留细节又控制显存。RandomRotate90只转 90 度的整数倍,不引入插值模糊,同时不破坏方向语义。参数说明:ColorJitter的强度别开太大,航拍图本身光照相对均匀,brightness 超过 0.3 容易让模型把光照变化误当成类别特征。
3.3 训练循环里的关键参数
DeepLabv3+ 训练有几个参数直接决定成败,我按重要性排:
import torch import torch.nn as nn # 1. 损失函数:航拍图类别极不平衡,必须加权 class_weights = torch.tensor([0.1, 1.0, 1.0, 0.8]).cuda() # 背景权重压低 criterion = nn.CrossEntropyLoss(weight=class_weights, ignore_index=255) # 2. 优化器:DeepLabv3+ 常用 SGD + poly 策略,别一上来就 Adam optimizer = torch.optim.SGD(model.parameters(), lr=0.01, momentum=0.9, weight_decay=1e-4) # 3. poly 学习率衰减 def poly_lr(base_lr, iters, max_iters, power=0.9): return base_lr * (1 - iters / max_iters) ** power逻辑说明:航拍图背景像素通常占 70% 以上,不加权的话模型学会「全预测背景」就能拿到高 accuracy,但 IoU 惨不忍睹。ignore_index=255是分割任务惯例,把边界模糊的像素排除掉。优化器用 SGD 而不是 Adam,是因为 DeepLabv3+ 原论文和多数复现都用 SGD+poly,Adam 在分割任务上有时收敛到更差的局部解。参数说明:base_lr=0.01是 batch_size=16 时的经验值,batch 减半 lr 也要减半;power=0.9控制衰减速度,越小衰减越快。
3.4 换骨干网络:改一行配置的事
这套代码包最大的价值就是骨干可换。以resnet.py换hrnet.py为例,通常只需要改模型构建那几行:
# 原来用 ResNet # from resnet import resnet50 # backbone = resnet50(pretrained=True, output_stride=16) # 换成 HRNet from hrnet import HRNet backbone = HRNet(num_classes=4, pretrained=True) # num_classes 按你的类别数改 # 接上 DeepLabv3+ 的 ASPP 头 model = DeepLabV3Plus(backbone=backbone, num_classes=4)逻辑说明:换骨干时最容易翻车的是通道数对不上。DeepLabv3+ 的 ASPP 头期望 backbone 输出特定通道数(ResNet 是 2048),HRNet 不同配置输出通道不一样,接之前先print(backbone_out.shape)确认一下。参数说明:pretrained=True尽量开,航拍数据集通常就几千张,从头训 Transformer 类骨干基本不可能收敛。
注意:换
swin.py或beit.py这类 Transformer 骨干时,学习率要调小一个量级(比如 0.001),warmup 轮数要拉长到 5~10 轮,否则前期 loss 会剧烈震荡甚至发散。
4. 避坑与排查:航拍分割训练中最容易翻车的五件事
4.1 现象:loss 一直不降,mIoU 在 0.1 附近晃
原因:最常见的是 mask 像素值和类别数不匹配。比如你有 4 类,mask 里却出现了 5 或 255 这种值,CrossEntropyLoss 直接算错。其次是数据归一化没做,航拍图原始像素 0~255 直接喂进去,梯度爆炸。
解决:训练前先跑一遍数据检查脚本,统计 mask 的唯一值:
import numpy as np from PIL import Image import glob for p in glob.glob('masks/*.png')[:20]: vals = np.unique(np.array(Image.open(p))) print(p, vals) # 期望输出:每张图的值都在 [0, num_classes-1] 范围内发现越界值就回去改标注或转换脚本,别硬训。
4.2 现象:训练集 IoU 很高,验证集一塌糊涂
原因:过拟合,航拍数据集普遍偏小。也可能是训练集和验证集有重叠区域——航拍图常常是同一片区域切块,如果随机划分,相邻块可能一块在训练集一块在验证集,模型等于「见过」验证集。
解决:按地理区域划分而不是随机划分。如果数据是按大图切块的,先按大图分组,整组进训练或验证。增强强度也可以加大,但别用垂直翻转(见 3.2)。
4.3 现象:显存爆了,batch_size 只能设 2
原因:航拍图分辨率高,DeepLabv3+ 的 ASPP 在 output_stride=8 时特征图很大,显存占用是 output_stride=16 的四倍左右。
解决:三个方向——降 output_stride 到 16、用随机裁剪代替整图输入、开混合精度训练:
from torch.cuda.amp import autocast, GradScaler scaler = GradScaler() for imgs, masks in loader: optimizer.zero_grad() with autocast(): # 前向用 fp16,显存省近一半 out = model(imgs) loss = criterion(out, masks) scaler.scale(loss).backward() # 梯度缩放,防 fp16 下溢 scaler.step(optimizer) scaler.update()逻辑说明:autocast让前向计算自动用 fp16,GradScaler负责把 loss 放大再反传,避免 fp16 精度不够导致梯度变 0。参数说明:混合精度在 RTX 系列卡上加速明显,老卡(如 GTX 10 系)支持有限,开了可能更慢。
4.4 现象:换骨干后报通道数不匹配的错
原因:不同 backbone 输出特征通道不同,ASPP 头的输入层是按某个固定通道数写的。
解决:找到 ASPP 定义处,把输入通道改成新 backbone 的输出通道。或者更省事的办法——在 backbone 和 ASPP 之间加一个 1×1 卷积做通道对齐:
import torch.nn as nn class ChannelAlign(nn.Module): def __init__(self, in_ch, out_ch=2048): super().__init__() self.conv = nn.Conv2d(in_ch, out_ch, 1) def forward(self, x): return self.conv(x) # 用法:backbone 输出 -> ChannelAlign -> ASPP4.5 现象:notebook 跑一半卡死,没有任何报错
原因:多半是数据加载的num_workers设太大,或者某个样本损坏(图片能打开但解码失败),DataLoader 卡在等这个样本。
解决:先把num_workers设成 0 跑一遍,能跑通说明是多进程问题,再逐步加到 2、4。如果是样本损坏,加个 try-except 跳过:
from PIL import Image def safe_load(path): try: return Image.open(path).convert('RGB') except Exception as e: print(f'坏样本: {path}, {e}') return None提示:航拍图常见格式是 tif,PIL 读某些压缩方式的 tif 会失败,换成
cv2.imread或rasterio往往能解决。
5. 进阶技巧:用 SAM notebook 做伪标签与结果验证
5.1 把 SAM 当标注加速器,而不是直接当分割模型
包里那两个sam_arch_*.ipynb容易被误解成「用 SAM 做分割」。SAM 是类无关的通用分割模型,它不知道「建筑」和「道路」的区别,直接拿来做语义分割,输出的是无类别掩码,还得再分类。但它有个更实用的用法——生成伪标签。
思路是这样:先用你训好的 DeepLabv3+ 对未标注航拍图做推理,得到粗糙的类别预测;再用 SAM 对同一张图做精细分割,把 SAM 的掩码和 DeepLabv3+ 的类别预测做交集,交集区域作为高置信伪标签,拿去扩充训练集。这样能把标注成本压下来,毕设里「半监督/弱监督」这一章就有内容了。
import numpy as np def merge_pseudo_label(deeplab_pred, sam_mask): # deeplab_pred: (H, W) 类别预测 # sam_mask: (H, W) bool,SAM 认为属于同一物体的区域 pseudo = np.zeros_like(deeplab_pred) for obj_id in np.unique(sam_mask): if obj_id == 0: continue region = (sam_mask == obj_id) # 取该区域内 DeepLabv3+ 预测的众数作为伪标签类别 vals, counts = np.unique(deeplab_pred[region], return_counts=True) pseudo[region] = vals[np.argmax(counts)] return pseudo逻辑说明:SAM 负责「哪里是一个物体」,DeepLabv3+ 负责「这个物体是什么类」,两者互补。参数说明:伪标签别全信,通常只保留 DeepLabv3+ 置信度高于某阈值(比如 softmax 后 0.9)的区域,否则错误会累积。
5.2 验证方法:别只看 mIoU 一个数
毕设答辩时导师最爱问「你怎么证明模型真的好」。只报一个 mIoU 不够,我一般准备三样东西:
第一,逐类 IoU 表格。航拍分割里背景类 IoU 通常 0.9+,但小目标类可能只有 0.4,只报平均会掩盖问题。第二,可视化对比图,原图、GT、预测三列并排,挑几张有代表性的(包含小目标、边界复杂的)。第三,混淆矩阵,看模型主要把哪类错分成哪类,这比单一指标有说服力。
import numpy as np def per_class_iou(pred, target, num_classes): ious = [] for c in range(num_classes): inter = np.logical_and(pred == c, target == c).sum() union = np.logical_or(pred == c, target == c).sum() ious.append(inter / union if union > 0 else float('nan')) return ious # 输出示例 # 背景 0.93 | 建筑 0.71 | 道路 0.58 | 植被 0.665.3 一个我踩过的坑:验证时忘了切 eval 模式
有次跑完训练,验证集 mIoU 比训练集还高,高兴了半天,后来发现是忘了model.eval()。BatchNorm 在 train 模式下用当前 batch 的统计量,eval 模式用滑动平均,两者结果能差好几个点。更坑的是 Dropout 在 train 模式下随机丢神经元,验证结果每次都不一样。
model.eval() # 验证/推理前必须切 with torch.no_grad(): # 关掉梯度,省显存也防意外更新 for imgs, masks in val_loader: out = model(imgs) # ... 算指标 model.train() # 回到训练前记得切回来从那以后我每次写验证循环,第一行就是model.eval(),torch.no_grad()也一起加上,这俩是配套的。还有个小习惯——验证前先print(model.training)确认一下状态,False 才继续,省得又白高兴一场。
这套代码包的价值在于它把 DeepLabv3+ 的完整链路和多个骨干的对比入口都给你了,毕设里该有的基线、对比、改进空间都留好了。拿到手先跑通 baseline,再按第 3 章的换骨干方法做对比实验,第 4 章的坑提前避开,基本能省掉一周的调试时间。希望帮到你。
本文还有配套的精品资源,点击获取