ARTICLE DETAIL

资讯详情

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

航拍图像语义分割毕设实战:Python+DeepLabv3+全流程指南

航拍图像语义分割毕设实战:Python+DeepLabv3+全流程指南 简介这份资源是面向高校计算机、人工智能及相关专业学生的毕业设计参考项目围绕Python与DeepLabv3模型展开高分辨率航拍图像的语义分割任务适合具备一定深度学习基础、需要完成遥感影像分割课题或希望复现经典分割网络的学习者。压缩包共184个文件以95个py源码文件与84个pyc编译文件为主体另含3个ipynb实验笔记、1个txt与1个md说明文档整体约477KB体积轻量便于本地部署与二次修改。源码中涵盖swin、resnet、hrnet、twins、bisenetv2、beit等多种骨干网络实现并配有离线与在线推理的notebook示例可帮助读者理解编码器—解码器结构、空洞卷积与多尺度特征融合等关键设计。目前已有204人学习下载可作为毕业设计选题、模型对比实验与代码调试的实用起点。1. 航拍语义分割毕设为什么 DeepLabv3 仍是性价比最高的起点做遥感或航拍方向的毕业设计绕不开一个核心问题怎么把一张几千乘几千像素的航拍图逐像素分成建筑、道路、植被、水体这些类别。这就是语义分割要干的事。而 DeepLabv3 在这个任务上长期是一个稳字当头的选择——它把空洞卷积和编解码结构捏在一起既能在高分辨率图上保住大感受野又不会像纯 FCN 那样把边界糊成一团。很多同学一上来就想追最新的分割大模型结果显存炸了、训练周期拖到答辩前一周还没收敛血泪经验告诉我们毕设要的是能跑通、能出图、能写进论文的完整闭环不是刷榜。这篇笔记就围绕 Python DeepLabv3 这条线把航拍图像语义分割从环境搭建、数据制作、模型训练到推理出图整条链路拆开讲清楚适合正在做计算机毕业设计、遥感方向或想入门语义分割的从业者照着复现。2. 航拍语义分割的任务边界与 DeepLabv3 选型逻辑2.1 航拍图像和自然图像到底差在哪很多人拿 DeepLabv3 直接套在航拍数据上翻车根源是没意识到航拍图和 COCO、VOC 那类自然图像根本不是一个分布。自然图像里一只猫占画面三分之一航拍图里一栋楼可能只占几十个像素自然图像有明确的透视和前景背景航拍图是近似正射的俯视图同一类目标在不同高度、不同光照下尺度差异极大。这带来三个直接影响第一类别极不平衡道路这种细长目标像素占比可能不到 2%第二边界模糊屋顶和地面的过渡往往只有一两个像素第三图像尺寸大动辄 4000×3000直接送进网络显存扛不住。理解这三点才能理解为什么航拍分割对模型的要求和自然图像不一样。你需要一个能在下采样过程中保留空间细节、又能对大尺度目标维持感受野的结构。DeepLabv3 的 ASPP 模块用不同膨胀率的空洞卷积并行提取多尺度上下文解码器再把浅层特征和深层特征融合恰好对上航拍图多尺度 边界敏感这两个痛点。这也是它在遥感分割论文里被反复当 baseline 的原因。2.2 DeepLabv3 的三个关键结构把 DeepLabv3 拆开看核心就三块。第一块是主干网络常见做法是用 ResNet-50 或 ResNet-101 配合空洞卷积把最后几个 stage 的下采样步长改掉让输出特征图保持较大的空间分辨率。第二块是 ASPP也就是空洞空间金字塔池化用 1×1 卷积加三个不同膨胀率通常 6、12、18的 3×3 空洞卷积再加一个全局平均池化分支五路 concat 起来。第三块是解码器把主干浅层特征经过 1×1 卷积降维和 ASPP 输出上采样后拼接再经过几次卷积恢复分辨率。这里有个容易被忽略的点主干的下采样倍率决定了你最终输出 stride 是 8 还是 16。航拍图边界要求高一般用 output_stride8也就是主干只下采样 8 倍代价是显存和计算量上升。如果显存紧张退到 16 也能用但小目标边界会明显变糙。这个参数在动手前就要定好中途改会牵一发动全身。2.3 为什么毕设场景优先选它而不是追新从落地角度讲DeepLabv3 有三个现实优势。一是开源实现成熟PyTorch 生态里有大量可参考的复现改起来心里有底二是参数量适中ResNet-50 版本在 8G 显存的卡上就能训学生党用实验室或云上的单卡完全够三是论文好写结构清晰、消融实验好设计ASPP 膨胀率、主干深度、output_stride 都能拿来做对比。相比之下一些更新的分割架构要么依赖超大预训练权重要么训练 trick 多到难以复现对毕设这种时间紧、要交付完整文档的场景并不友好。选型不是选最强是选最匹配你约束条件的。3. 用 Python 把航拍数据做成 DeepLabv3 能吃的格式3.1 环境搭建conda 建环境与关键依赖先把环境隔离出来别在系统 Python 里乱装。下面这套命令在 Linux 和 Windows 的 conda 下都通用Python 版本建议 3.8 到 3.10太新有些库轮子还没跟上。# 创建独立环境避免污染系统 Python conda create -n deeplab_aerial python3.9 -y conda activate deeplab_aerial # 安装 PyTorch按你的 CUDA 版本去官网选对应命令这里以 CUDA 11.8 为例 pip install torch torchvision --index-url https://download.pytorch.org/whl/cu118 # 分割任务常用依赖 pip install opencv-python pillow numpy matplotlib tqdm scikit-learn tensorboard逻辑说明conda 负责隔离解释器和底层库pip 装 PyTorch 时一定要带--index-url指向官方轮子源否则容易装到 CPU 版。opencv-python用于读写大图和做几何变换tqdm看训练进度tensorboard记录 loss 曲线方便写论文。参数上CUDA 版本要和驱动匹配用nvidia-smi看右上角支持的 CUDA 版本再决定装哪个。3.2 航拍数据集制作标注、类别与目录结构航拍语义分割的数据来源通常是公开数据集如 LoveDA、Potsdam或自己用无人机采集后标注。标注工具常见的是 LabelMe 或 ArcGIS导出成掩膜 PNG每个像素值对应类别索引。这里有个硬性要求掩膜必须是单通道 8 位图像素值 0、1、2……对应背景、类别一、类别二千万别存成 RGB 彩色图否则读进来全是坑。目录结构建议这样组织训练脚本按这个约定去读dataset/ ├── images/ │ ├── train/ │ │ ├── 001.png │ │ └── 002.png │ └── val/ │ └── 003.png └── masks/ ├── train/ │ ├── 001.png │ └── 002.png └── val/ └── 003.png图像和掩膜文件名必须一一对应这是后面 Dataset 类能对齐的前提。类别数在动手前就统计清楚比如背景 建筑 道路 植被 水体就是 5 类这个数字直接决定模型输出通道数中途改要重训。3.3 大图切分与数据增强脚本航拍图动辄几千像素必须切块。下面这个脚本把大图和对应掩膜按固定窗口切分带重叠避免边界目标被切断。import os import cv2 import numpy as np def slide_crop(img, mask, crop_size512, stride384): 按滑动窗口切分大图和掩膜stride 小于 crop_size 产生重叠 h, w img.shape[:2] patches [] for y in range(0, h - crop_size 1, stride): for x in range(0, w - crop_size 1, stride): img_patch img[y:ycrop_size, x:xcrop_size] mask_patch mask[y:ycrop_size, x:xcrop_size] # 跳过几乎全背景的块减少无效样本 if np.mean(mask_patch 0) 0.02: continue patches.append((img_patch, mask_patch)) return patches def process_folder(img_dir, mask_dir, out_img_dir, out_mask_dir): os.makedirs(out_img_dir, exist_okTrue) os.makedirs(out_mask_dir, exist_okTrue) for name in os.listdir(img_dir): img cv2.imread(os.path.join(img_dir, name)) mask cv2.imread(os.path.join(mask_dir, name), cv2.IMREAD_GRAYSCALE) if img is None or mask is None: continue for i, (ip, mp) in enumerate(slide_crop(img, mask)): base os.path.splitext(name)[0] cv2.imwrite(f{out_img_dir}/{base}_{i}.png, ip) cv2.imwrite(f{out_mask_dir}/{base}_{i}.png, mp) if __name__ __main__: process_folder(dataset/images/train, dataset/masks/train, dataset/crops/images/train, dataset/crops/masks/train)逻辑说明crop_size是切块边长512 是显存和上下文的一个平衡点stride控制重叠设成 384 意味着相邻块有 128 像素重叠能缓解目标被切断的问题。np.mean(mask_patch 0) 0.02这行是过滤纯背景块航拍图里大片空地很多不过滤会让正负样本严重失衡。参数怎么调显存小就把 crop_size 降到 256但太小会丢失大目标的上下文重叠太多会让训练集膨胀、训练变慢一般 stride 取 crop_size 的 0.7 到 0.75 倍比较合适。数据增强方面航拍图适合用随机翻转、90 度旋转、颜色抖动但不要用大幅度的透视变换因为俯视图本身没有透视畸变硬加反而引入噪声。增强在 Dataset 的__getitem__里用 albumentations 做最方便注意图像和掩膜要同步变换。4. 训练 DeepLabv3损失、参数与显存控制4.1 损失函数选择交叉熵不够用的时候航拍分割最大的问题是类别不平衡道路、小建筑这些类像素占比极低纯交叉熵会让模型倾向于全预测成背景或植被。常见做法是交叉熵加 Dice 损失组合Dice 对前景占比敏感能把小类的梯度拉起来。import torch import torch.nn as nn import torch.nn.functional as F class DiceLoss(nn.Module): def __init__(self, smooth1.0): super().__init__() self.smooth smooth def forward(self, logits, targets): # logits: [B, C, H, W], targets: [B, H, W] probs F.softmax(logits, dim1) num_classes logits.shape[1] targets_onehot F.one_hot(targets, num_classes).permute(0, 3, 1, 2).float() intersection (probs * targets_onehot).sum(dim(2, 3)) union probs.sum(dim(2, 3)) targets_onehot.sum(dim(2, 3)) dice (2 * intersection self.smooth) / (union self.smooth) return 1 - dice.mean() class CombinedLoss(nn.Module): def __init__(self, ce_weight1.0, dice_weight1.0): super().__init__() self.ce nn.CrossEntropyLoss(ignore_index255) self.dice DiceLoss() self.ce_weight ce_weight self.dice_weight dice_weight def forward(self, logits, targets): return self.ce_weight * self.ce(logits, targets) \ self.dice_weight * self.dice(logits, targets)逻辑说明ignore_index255让标注里未定义区域不参与损失这在航拍数据里很常见。Dice 用 softmax 后的概率算smooth防止分母为零。两个权重一般各取 1.0 起步如果发现小类还是学不出来把 dice_weight 提到 2.0 试试。注意 Dice 对每个类单独算再平均这样小类不会被大类淹没。4.2 训练参数学习率、batch size 与 output_stride学习率用多项式衰减是分割任务的老套路初始 0.01 配 SGD或者 1e-4 配 Adam。航拍数据量通常不大Adam 收敛更快毕设时间紧可以优先用 Adam。batch size 受显存限制8G 卡上 ResNet-50、crop 512、output_stride8 大概能跑 batch 4用梯度累积凑等效大 batch。import torch from torch.optim import Adam from torch.optim.lr_scheduler import PolynomialLR model DeepLabV3Plus(num_classes5, backboneresnet50, output_stride8) model model.cuda() optimizer Adam(model.parameters(), lr1e-4, weight_decay1e-4) # 多项式衰减power0.9 是分割任务常用值 scheduler PolynomialLR(optimizer, total_iters100 * len(train_loader), power0.9) for epoch in range(100): model.train() for imgs, masks in train_loader: imgs, masks imgs.cuda(), masks.cuda() logits model(imgs) loss criterion(logits, masks) optimizer.zero_grad() loss.backward() optimizer.step() scheduler.step()逻辑说明weight_decay防过拟合航拍数据少的时候尤其重要。PolynomialLR的total_iters要按总迭代次数算不是 epoch 数写错会导致学习率提前衰减到零。output_stride8 精度高但慢如果训练一轮要几小时可以先降到 16 快速验证流程通不通再切回 8 正式训。4.3 显存不够时的三个降级手段显存爆了别急着换卡先试这三招。第一开混合精度训练用torch.cuda.amp显存能省三成左右速度还快。第二减小 crop_size从 512 降到 384 或 256代价是上下文变少。第三用梯度累积把 batch 拆成几次前向再统一更新等效大 batch 但显存占用按小 batch 算。from torch.cuda.amp import autocast, GradScaler scaler GradScaler() accum_steps 4 for i, (imgs, masks) in enumerate(train_loader): imgs, masks imgs.cuda(), masks.cuda() with autocast(): logits model(imgs) loss criterion(logits, masks) / accum_steps scaler.scale(loss).backward() if (i 1) % accum_steps 0: scaler.step(optimizer) scaler.update() optimizer.zero_grad()逻辑说明autocast让前向用半精度GradScaler处理梯度缩放防止下溢。loss 除以accum_steps是为了让累积后的梯度量级和真实大 batch 一致。注意optimizer.zero_grad()要放在累积周期结束时放错位置梯度就白攒了。5. 推理出图与精度评估把结果变成论文里的图表5.1 大图滑窗推理与拼接训练时切块推理时也要切块但拼接要处理好重叠区域否则接缝处会有明显断层。常见做法是重叠区取平均或取最大概率。import torch import numpy as np import cv2 def inference_large_image(model, img_path, crop_size512, stride384, num_classes5): model.eval() img cv2.imread(img_path) img cv2.cvtColor(img, cv2.COLOR_BGR2RGB) h, w img.shape[:2] # 累加概率图和计数图用于重叠区平均 prob_map np.zeros((num_classes, h, w), dtypenp.float32) count_map np.zeros((h, w), dtypenp.float32) for y in range(0, h, stride): for x in range(0, w, stride): y2 min(y crop_size, h) x2 min(x crop_size, w) y1 max(0, y2 - crop_size) x1 max(0, x2 - crop_size) patch img[y1:y2, x1:x2] tensor torch.from_numpy(patch).permute(2, 0, 1).float().unsqueeze(0) / 255.0 with torch.no_grad(): logits model(tensor.cuda()) prob torch.softmax(logits, dim1).squeeze(0).cpu().numpy() prob_map[:, y1:y2, x1:x2] prob count_map[y1:y2, x1:x2] 1 count_map[count_map 0] 1 prob_map / count_map pred np.argmax(prob_map, axis0).astype(np.uint8) return pred逻辑说明y1 max(0, y2 - crop_size)这行保证边缘块也能取满 crop_size不会因为越界而变小。概率累加再除以计数等价于重叠区加权平均比直接取最后一个块的结果平滑得多。参数 stride 和训练时保持一致否则尺度对不上。5.2 mIoU 与混淆矩阵论文里必须有的指标语义分割论文绕不开 mIoU它按类算交并比再平均能反映小类的表现。光看像素准确率会被背景类带偏一个全预测背景的模型准确率可能也有 80%但 mIoU 惨不忍睹。import numpy as np def compute_confusion_matrix(preds, targets, num_classes): mask (targets 0) (targets num_classes) hist np.bincount( num_classes * targets[mask].astype(int) preds[mask], minlengthnum_classes ** 2 ).reshape(num_classes, num_classes) return hist def compute_miou(conf_matrix): intersection np.diag(conf_matrix) union conf_matrix.sum(axis1) conf_matrix.sum(axis0) - intersection iou intersection / np.maximum(union, 1) return np.nanmean(iou), iou逻辑说明混淆矩阵的行是真实类列是预测类np.bincount用类别索引编码一次性统计。np.maximum(union, 1)防止某类完全没出现时除零。返回的iou数组能看出哪一类拖后腿写论文时针对低 IoU 的类做分析比只报一个总分有说服力。5.3 可视化让答辩老师一眼看懂出图时把原图、真值、预测三张并排用固定颜色表映射类别比单张预测图直观得多。颜色表要固定别每次随机否则不同实验的图没法对比。边界处可以叠加原图做半透明突出分割贴合程度。这些图直接进论文的实验章节比堆数字有效。6. 航拍分割避坑清单五个我踩过的坑6.1 掩膜存成 RGB 导致类别全乱现象训练 loss 一直不降预测图颜色诡异。原因标注工具导出的是彩色掩膜读进来是三通道像素值是 RGB 组合而不是类别索引。解决用cv2.imread(path, cv2.IMREAD_GRAYSCALE)读并检查唯一值是不是 0 到 num_classes-1 的整数发现是彩色图就用调色板反查回索引。6.2 图像和掩膜增强不同步现象训练集上表现正常验证集 mIoU 极低。原因数据增强时图像做了随机翻转掩膜没跟着翻标签和内容对不上。解决用 albumentations 的A.Compose把图像和掩膜放一起变换或者手写增强时对两者用同一组随机参数。6.3 忽略背景类导致 mIoU 虚高现象mIoU 报出来 0.7但预测图里道路全断。原因计算 mIoU 时把背景类也算进去背景占比大拉高了均值。解决单独看每个类的 IoU论文里明确说明是否含背景通常汇报时把背景排除或单独列出。6.4 学习率衰减写错迭代次数现象训练到一半 loss 突然不动。原因PolynomialLR的total_iters按 epoch 数填了实际应该是总迭代次数导致学习率提前归零。解决total_iters epochs * len(train_loader)训练前打印一下确认。6.5 推理拼接出现网格接缝现象大图推理结果有明显方块边界。原因滑窗推理时重叠区直接覆盖没有做概率平均。解决按 5.1 的累加计数法拼接重叠区取平均概率接缝自然消失。7. 把 DeepLabv3 用到极致几个能拉开差距的进阶技巧想让毕设从能跑变成有亮点可以在几个地方做文章。第一个是主干替换把 ResNet-50 换成轻量的 MobileNetV2 或更深的 ResNet-101做一组对比实验讨论精度和速度的权衡这在论文里是标准的消融设计。第二个是注意力模块在 ASPP 后面接一个 SE 或 CBAM航拍图里通道间差异明显注意力往往能带来一两个点的 mIoU 提升代码改动量也不大。第三个是损失函数的类别权重针对你数据里最稀少的类在交叉熵里给它更高的权重或者用 Focal Loss 替代普通交叉熵对小目标效果明显。第四个是测试时增强TTA推理时把图翻转、多尺度各跑一遍再平均mIoU 通常能涨零点几个点代价是推理变慢答辩演示够用。验证这些改动有没有效别只看最终 mIoU要固定随机种子、固定数据划分每次只改一个变量记录到表格里。我一般会建一个实验记录表列清楚主干、output_stride、损失权重、mIoU、单类 IoU跑完一组填一行写论文时直接拿来用省得回头翻日志。最后说个习惯航拍数据标注成本高别一上来就标几千张先标一两百张把整条链路跑通确认模型能学起来、推理出图正常再批量标注扩充。我见过太多人闷头标了一个月数据结果训练脚本有 bug全白干。先小后大、先通后优这是做分割毕设最省命的节奏。希望帮到你。本文还有配套的精品资源点击获取
返回列表