ARTICLE DETAIL

资讯详情

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

航拍语义分割毕设实战:DeepLabv3+多骨干代码包与避坑指南

航拍语义分割毕设实战:DeepLabv3+多骨干代码包与避坑指南 简介这份资源是面向高校计算机、人工智能及相关专业学生的毕业设计参考项目围绕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 的解法是两条线并行。第一条是 ASPPAtrous Spatial Pyramid Pooling用不同膨胀率的空洞卷积在同一特征图上抓多尺度上下文。膨胀率 6、12、18 三路加上全局平均池化一路concat 起来后每个像素都能「看到」不同范围的邻域。第二条是编码器-解码器结构编码器出深层语义解码器把浅层特征拿回来做融合恢复边界细节。航拍图里建筑边缘、道路轮廓这些高频信息全靠解码器这条线救回来。常见做法是把 backbone 设成 ResNet-50 或 ResNet-101配合 output_stride16。output_stride 这个参数控制特征图下采样倍数设 8 精度更高但显存吃紧设 16 是精度和显存的平衡点设 32 就太粗了航拍小目标基本丢光。我一般先跑 output_stride16显存够12G 以上再试 8。2.2 六个骨干文件分别对应什么场景包里那六个.py不是随便凑数的每个对应一类骨干网络换着用就是一组现成的对比实验文件骨干类型特点适合的毕设章节resnet.pyCNN最稳预训练权重好找收敛快基线实验、对照组hrnet.pyCNN全程保持高分辨率小目标友好高分辨率航拍图主实验bisenetv2.py轻量 CNN参数量小推理快实时性/轻量化对比swin.pyTransformer全局建模强显存需求高前沿方法对比twins.pyTransformer局部-全局交替注意力创新点章节beit.pyTransformer掩码预训练范式预训练策略讨论选型逻辑很直接如果你的毕设题目偏「工程落地」用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 python3.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。如果显卡不支持 CUDACPU 也能跑但航拍图训练在 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 是单通道灰度图像素值就是类别 id0 背景、1 建筑、2 道路、3 植被……具体几类看你的数据集。这里有个血泪经验mask 的像素值必须是连续的整数从 0 开始。我见过有人用 0/128/255 标类别结果训练时 loss 直接 NaN因为 CrossEntropyLoss 默认按 0~C-1 的类别索引去取 logits128 这种值直接越界。如果手上只有彩色标注图比如用 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], dtypenp.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(height512, width512), # 航拍图大随机裁剪比缩放好 A.HorizontalFlip(p0.5), # 水平翻转可接受 A.RandomRotate90(p0.5), # 90度旋转保持方向语义 A.ColorJitter(brightness0.2, contrast0.2, p0.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(weightclass_weights, ignore_index255) # 2. 优化器DeepLabv3 常用 SGD poly 策略别一上来就 Adam optimizer torch.optim.SGD(model.parameters(), lr0.01, momentum0.9, weight_decay1e-4) # 3. poly 学习率衰减 def poly_lr(base_lr, iters, max_iters, power0.9): return base_lr * (1 - iters / max_iters) ** power逻辑说明航拍图背景像素通常占 70% 以上不加权的话模型学会「全预测背景」就能拿到高 accuracy但 IoU 惨不忍睹。ignore_index255是分割任务惯例把边界模糊的像素排除掉。优化器用 SGD 而不是 Adam是因为 DeepLabv3 原论文和多数复现都用 SGDpolyAdam 在分割任务上有时收敛到更差的局部解。参数说明base_lr0.01是 batch_size16 时的经验值batch 减半 lr 也要减半power0.9控制衰减速度越小衰减越快。3.4 换骨干网络改一行配置的事这套代码包最大的价值就是骨干可换。以resnet.py换hrnet.py为例通常只需要改模型构建那几行# 原来用 ResNet # from resnet import resnet50 # backbone resnet50(pretrainedTrue, output_stride16) # 换成 HRNet from hrnet import HRNet backbone HRNet(num_classes4, pretrainedTrue) # num_classes 按你的类别数改 # 接上 DeepLabv3 的 ASPP 头 model DeepLabV3Plus(backbonebackbone, num_classes4)逻辑说明换骨干时最容易翻车的是通道数对不上。DeepLabv3 的 ASPP 头期望 backbone 输出特定通道数ResNet 是 2048HRNet 不同配置输出通道不一样接之前先print(backbone_out.shape)确认一下。参数说明pretrainedTrue尽量开航拍数据集通常就几千张从头训 Transformer 类骨干基本不可能收敛。注意换swin.py或beit.py这类 Transformer 骨干时学习率要调小一个量级比如 0.001warmup 轮数要拉长到 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_stride8 时特征图很大显存占用是 output_stride16 的四倍左右。解决三个方向——降 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让前向计算自动用 fp16GradScaler负责把 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_ch2048): 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提示航拍图常见格式是 tifPIL 读某些压缩方式的 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) boolSAM 认为属于同一物体的区域 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_countsTrue) 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 章的坑提前避开基本能省掉一周的调试时间。希望帮到你。本文还有配套的精品资源点击获取
返回列表