ARTICLE DETAIL

资讯详情

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

DeepLabv3+在高分辨率航拍图像语义分割中的实践与调参指南

DeepLabv3+在高分辨率航拍图像语义分割中的实践与调参指南 简介这是一份基于DeepLabv3的高分辨率航拍图像语义分割Python项目面向计算机视觉或遥感方向的毕业设计适合需要完成航拍场景地物分类任务的本科生或研究生。项目围绕DeepLabv3模型展开覆盖数据处理、训练、评估与推理流程并内置ResNet、HRNet、Swin、Twins、BiSeNetV2、BEiT等多种骨干网络实现便于对比不同编解码结构对分割精度和效率的影响。资源包共184个文件其中95个py脚本负责模型搭建、训练与测试84个pyc为编译缓存3个ipynb提供在线与离线交互式运行示例另有1个txt运行说明和1个md文档辅助上手压缩包仅477KB结构紧凑适合快速部署与二次开发。已有204人学习整体轻量实用尤其适合作为毕业设计基线方案。借助这套代码读者可直接获得可运行的语义分割训练脚本、多骨干网络实现、交互式示例和配套文档省去环境准备与代码整理时间把精力集中到航拍图像分割实验对比和论文撰写上。1. 高分辨率航拍图像的语义分割为什么DeepLabv3是毕业设计的稳妥选型高分辨率航拍图像和普通自然照片差别很大一个像元在地面上可能是几十厘米到几米见方屋顶、树木、道路、水体这些类别往往挤在一起边缘互相遮挡曝光还随拍摄时间和天气变化。直接拿分类网络或普通的FCN上去做语义分割要么边缘糊成一团要么小目标被下采样吞掉。DeepLabv3是处理这类遥感影像语义分割比较成熟的选择它用空洞卷积扩大感受野又用Decoder把细节恢复到原分辨率训练收敛平稳框架资料也齐全拿来当毕业设计既不会太冷门到没参考也不会太简单到撑不起工作量。这篇笔记直接按照你拿到航拍数据后最需要走的路径来写怎么组织数据、怎么把模型跑通、哪些参数值得调、哪些坑一定会踩。2. 高分辨率航拍图像的语义分割数据从原始影像到能喂给模型的补丁2.1 高分辨率航拍与常规自然图像的差异几何、光照与类别尺度自然图像语义分割的数据集比如PASCAL VOC或Cityscapes图像中物体边界相对清晰类别分布比较均衡拍摄视角也基本固定。航拍图像不一样同样的地面物体在不同航高下尺度差异很大太阳高度角变化会让同一栋楼的屋顶在上午和下午形成完全不同的阴影建筑与植被、裸地之间的光谱特征重叠严重。这些差异意味着你不能拿着ImageNet预训练模型直接推到航拍图上去至少要在数据组织阶段就把光照不变性、阴影处理和类别不平衡问题考虑了。我一般会先做一次快速的数据摸底用全局统计查看各类别像素占比再目检几十张图确认标注质量。这一步会决定后续损失函数怎么设计、要不要做数据增强、要不要按类别加权。如果某类像素占全图不到5%但又是答辩里必须展现的类别那只靠普通交叉熵基本学不到东西后面会提到怎么处理。2.2 数据切片与Dataset组织重叠采样与边界处理高分辨率航拍图动辄几千乘几千像素直接整图输入训练会撞爆显存所以第一步是把大图切块。切片不是简单用np.array去截要注意三个问题切块大小、重叠比例、以及测试时怎么拼回去。训练阶段切块建议512到768像素见方太小会让模型看不到上下文太大又容易显存溢出重叠采样通常取20%到30%因为切块边界处的物体往往被切成两半重叠采样能让模型多见到完整目标。推理阶段则要反过来用滑窗加重叠并在拼接时对重叠区域做加权平均避免出现网格状伪影。下面是训练时数据集组织的一个最小PyTorch实现把大图和标注矩阵做成可迭代的补丁import numpy as np import torch from torch.utils.data import Dataset from torchvision import transforms class AerialPatchDataset(Dataset): def __init__(self, image, mask, patch_size512, overlap96, transformNone): # image: [H, W, C] uint8, mask: [H, W] int64 self.patch_size patch_size self.stride patch_size - overlap # 步长由重叠量决定 self.transform transform # 计算水平和垂直两个方向的起始坐标 h_starts list(range(0, image.shape[0] - patch_size, self.stride)) w_starts list(range(0, image.shape[1] - patch_size, self.stride)) if (image.shape[0] - patch_size) not in h_starts: h_starts.append(image.shape[0] - patch_size) if (image.shape[1] - patch_size) not in w_starts: w_starts.append(image.shape[1] - patch_size) self.coords [] for hs in h_starts: for ws in w_starts: self.coords.append((hs, ws)) self.image image self.mask mask def __len__(self): return len(self.coords) def __getitem__(self, idx): hs, ws self.coords[idx] img self.image[hs:hs self.patch_size, ws:ws self.patch_size, :] msk self.mask[hs:hs self.patch_size, ws:ws self.patch_size] if self.transform is not None: img self.transform(img) return img, torch.from_numpy(msk).long()这里stride由patch_size - overlap算出控制的是相邻补丁的间距。最后两行append保证了即使大图尺寸不是补丁大小的整数倍最后一块也不会漏掉。transform里我通常会放随机水平翻转、垂直翻转和随机旋转90度遥感图像对方向不敏感这类几何增强几乎白捡mIoU。注意颜色增强要克制航拍图的光谱关系比自然图像更脆弱色彩抖动过了头反而会让模型学偏。2.3 标注数据的类别处理与数据划分航拍数据集的标签往往存在类别不平衡比如植被覆盖了大半张图、车辆像素很少。处理思路有几个层次最基础的是在损失函数里做类别权重权重可以和像素占比成反比。更实用的是从数据层做裁剪增强对含稀有类别像素的补丁提高采样概率。我在代码里用一个简单的办法先统计每个补丁中稀有类的像素数然后按这个数的平方设置采样权重让标签里稀有的目标多进模型。数据划分也有讲究航拍图之间可能有重叠覆盖区域所以划分一定要按图而不是按补丁否则同一栋楼的一部分进训练集、另一部分进验证集验证指标会虚高得离谱答辩时专家一问就露馅。3. DeepLabv3的模型结构ASPP空洞卷积与边界恢复为什么契合遥感3.1 空洞卷积如何扩大感受野且不丢失空间细节语义分割的核心矛盾是分辨率与感受野的矛盾想要精细边界就不能反复下采样想要看懂大目标就需要足够大的感受野。DeepLabv3用空洞卷积化解了这个矛盾。空洞卷积在卷积核内部填充0比如rate6的3x3卷积实际覆盖范围是13x13但参数仍然是3x3的9个。这意味着你在不增加参数量的前提下扩大了感受野也不需要为了感受野而把特征图缩到很小空间细节得以保留。航拍图像里的目标尺度跨度大——一条小路可能只有几个像素而一片农田横跨上千像素空洞卷积的多分支设置恰好能覆盖这种多尺度。纯用空洞卷积还有个问题对于每个像素卷积核覆盖率其实很低特征中心点周围的信息存在空洞所以DeepLabv3在实际使用中主干仍是ResNet等带下采样的结构空洞卷积只在最后几个block逐步增大rate。这个改动能让网络在高层保持32像素步长的特征图不继续缩小保证输出分辨率不会太低。3.2 ASPP模块与Decoder把高层语义“画”回高分辨率DeepLabv3有两个关键部件ASPPAtrous Spatial Pyramid Pooling和Decoder。ASPP在主干输出的特征上并行做几个不同rate的空洞卷积通常是1x1、rate6、rate12、rate18外加一个全局平均池化分支最后拼接起来。这样每个位置既能看到小范围细节又能感知大范围上下文对于航拍图中“农田挨着建筑”“阴影挨着水体”这类容易混淆的组合ASPP提供的多尺度上下文能明显降低误分。池化分支也很重要它捕捉整幅图的全局统计信息对光照变化有一定的鲁棒性。Decoder部分把ASPP输出的低分辨率特征上采样再与主干中较早层的特征拼接逐步恢复建筑边缘、道路边界这类高频信息。这个设计很适合遥感因为遥感图最难处理的恰好是边界——屋顶和阴影的交界、林地和草地的过渡。DeepLabv3的整个结构可以快速搭出来下面是ASPP模块的核心代码import torch import torch.nn as nn class ASPP(nn.Module): def __init__(self, in_channels2048, out_channels256): super().__init__() self.branches nn.ModuleList([ # 1x1卷积分支等价于rate1 nn.Sequential( nn.Conv2d(in_channels, out_channels, 1, biasFalse), nn.BatchNorm2d(out_channels), nn.ReLU(inplaceTrue) ), # 三个不同rate的空洞卷积分支 nn.Sequential( nn.Conv2d(in_channels, out_channels, 3, padding6, dilation6, biasFalse), nn.BatchNorm2d(out_channels), nn.ReLU(inplaceTrue) ), nn.Sequential( nn.Conv2d(in_channels, out_channels, 3, padding12, dilation12, biasFalse), nn.BatchNorm2d(out_channels), nn.ReLU(inplaceTrue) ), nn.Sequential( nn.Conv2d(in_channels, out_channels, 3, padding18, dilation18, biasFalse), nn.BatchNorm2d(out_channels), nn.ReLU(inplaceTrue) ), ]) # 全局平均池化分支先池化到1x1再上采样回原尺寸 self.pool nn.Sequential( nn.AdaptiveAvgPool2d(1), nn.Conv2d(in_channels, out_channels, 1, biasFalse), nn.BatchNorm2d(out_channels), nn.ReLU(inplaceTrue) ) self.project nn.Sequential( nn.Conv2d(out_channels * 5, out_channels, 1, biasFalse), nn.BatchNorm2d(out_channels), nn.ReLU(inplaceTrue) ) def forward(self, x): size x.shape[-2:] branch_outs [branch(x) for branch in self.branches] # 池化分支要上采样回输入特征图的尺寸 pool_out self.pool(x) pool_out nn.functional.interpolate(pool_out, sizesize, modebilinear, align_cornersFalse) branch_outs.append(pool_out) return self.project(torch.cat(branch_outs, dim1))这里的out_channels就是ASPP输出到Decoder的统一通道数一般取256。在PyTorch里使用需要注意空洞卷积的padding必须与dilation匹配公式是padding dilation * (kernel_size - 1) / 2否则输出尺寸会算错。实际工程里很少自己写ASPP用torchvision.models.segmentation.deeplabv3_resnet101直接改就行但理解结构对调参很重要比如你想把ASPP的rate从24加到36来适配更大尺度目标就得知道每个分支的padding怎么同步改。Decoder的细节设计权重很高。主干backbone输出的低层特征通常是C1层分辨率高但语义弱需要先用1x1卷积降通道再和上采样后的ASPP高层特征相加或拼接最后再上采样回原图尺寸。需要注意低层特征和ASPP输出在尺寸上必须对齐差一个像素都会导致运行时崩溃。4. 在PyTorch里跑通DeepLabv3训练最小训练管线与参数说明4.1 训练循环与损失函数交叉熵和Dice的融合比例航拍语义分割的损失函数选择直接影响小类别的分割质量。我用得比较多的是交叉熵和Dice损失的加权组合公式为L alpha * CE beta * Dice。交叉熵收敛平稳Dice则对类别不平衡更敏感。遥感图像中道路、车辆这类类别占比小但形状细长纯Dice损失有时会震荡纯交叉熵又容易忽略稀有类两者的组合在多数遥感任务上比单一损失都要好。常见配置是把alpha设为0.6到0.8、beta设为0.2到0.4具体比例要看验证集表现。训练循环的写法其实都一样但有三个细节值得注意一是开启torch.cuda.amp混合精度训练航拍图像输入尺寸大显存省30%以上二是用torch.utils.data.DataLoader的num_workers和pin_memory否则数据切块会成为瓶颈三是验证时要关闭增强并开启torch.no_grad()滑窗方式的预测结果要按重叠区域加权平均。下面是训练循环的骨架代码import torch import torch.nn as nn from torch.cuda.amp import autocast, GradScaler def train_one_epoch(model, dataloader, optimizer, criterion_ce, criterion_dice, scaler): model.train() total_loss 0.0 for images, masks in dataloader: images, masks images.cuda(), masks.cuda() optimizer.zero_grad() with autocast(): outputs model(images)[out] # 用线性插值统一输出尺寸后计算损失 outputs nn.functional.interpolate( outputs, sizemasks.shape[-2:], modebilinear, align_cornersFalse ) loss_ce criterion_ce(outputs, masks) loss_dice criterion_dice(outputs, masks) loss 0.7 * loss_ce 0.3 * loss_dice scaler.scale(loss).backward() scaler.step(optimizer) scaler.update() total_loss loss.item() return total_loss / len(dataloader)这里model(images)[out]是torchvision分割模型的标准返回格式输出尺寸通常比输入小所以要插值回mask尺寸再算损失。GradScaler是混合精度训练必需的它会自动管理梯度缩放防止fp16的梯度下溢。Dice损失我建议自己实现时加上smooth项避免分母为0同时要对预测概率做softmax而不是直接对logits操作。4.2 优化器、学习率与Batch Size可复现的默认参数表训练DeepLabv3时最常踩的一个坑是照搬ImageNet的训练超参数。航拍遥感的分割任务输入是大图、标签是稠密像素Batch Size不可能像分类任务那样开大学习率必须等比下调。我推荐一套入门默认参数Backbone用ResNet101预训练权重从torchvision加载、优化器用SGDmomentum0.9, weight_decay1e-4或AdamWweight_decay0.01、初始学习率3e-5到1e-4、Batch Size在8左右、epoch数40到60。下面这张表是我实际项目里比较适用的参考值但不要盲目照抄要根据自己的显存和数据量微调。参数项推荐值说明输入补丁尺寸512x512过小上下文不足过大容易显存溢出Batch Size8显存受限时配合梯度累积初始学习率5e-5比ImageNet常用值低一个数量级学习率调度OneCycle前10%线性上升后90%余弦衰减优化器SGD momentum在分割任务上比AdamW收敛波动小BackboneResNet101显存不够用ResNet50后期再换训练epoch50航拍数据量较大时训练一轮较久观察早停这里的OneCycle调度器在torch.optim.lr_scheduler里就有实现OneCycleLR很喜欢搭配不同初始化率。如果显存不足可以在优化器之后手动做梯度累积每4个batch更新一次权重效果和Batch Size翻4倍近似。batch_size8配合梯度累积4步相当于有效批大小32但要注意BN层的统计量仍按单卡的实际batch计算太小的batch会让BN统计不准这时候可以考虑SyncBN或在训练前先跑若干步预热。4.3 验证与可视化预测叠加与mIoU怎么算毕业设计不能只看loss曲线还要有面向评审的可视化产出。验证阶段我会写一个独立的评估脚本用滑窗推理拼出整张预测图然后和真值对比。滑窗推理时重叠区域要用加权平均否则拼接缝会很明显。具体做法是为每个补丁维护一个权重矩阵越靠近中心权重越高边界越靠近权重越低最后除以权重和归一化。这样做出来的预测图没有接缝提交通道时也更好看。评估指标除了总体的mIoU必须给出每个类别的IoU和F1。很多时候总体mIoU看着还行但关键类别比如道路可能只有0.3答辩时一眼就能看出问题。错误可视化也有一个强烈推荐的做法把预测错误像素分成三类——真阳、假阳、假阴然后用不同颜色叠加在原图上这种图在毕业论文里比对单独的预测图更有说服力。下面这段话是拼图预测的核心部分def slide_predict(model, image, patch_size512, overlap96, num_classes6): model.eval() stride patch_size - overlap H, W, _ image.shape # 累积预测得分和权重矩阵 score_map torch.zeros(num_classes, H, W, dtypetorch.float32) weight_map torch.zeros(H, W, dtypetorch.float32) # 生成三角权重核中心高、边缘低 weight np.outer( np.hanning(patch_size), np.hanning(patch_size) ).astype(np.float32) with torch.no_grad(): for y0 in range(0, H - patch_size, stride): for x0 in range(0, W - patch_size, stride): patch image[y0:y0 patch_size, x0:x0 patch_size] patch_t torch.from_numpy(patch.transpose(2, 0, 1)).unsqueeze(0).cuda() output model(patch_t)[out] # [1, C, H, W] output nn.functional.interpolate( output, size(patch_size, patch_size), modebilinear, align_cornersFalse ).squeeze(0).cpu() score_map[:, y0:y0 patch_size, x0:x0 patch_size] output * torch.from_numpy(weight) weight_map[y0:y0 patch_size, x0:x0 patch_size] weight # 对重叠区域做归一化 score_map / weight_map.unsqueeze(0) pred score_map.argmax(dim0).numpy() return pred这段代码的核心是weight_map它记录每个像素被多少次滑窗覆盖最终用累积得分除以覆盖权重等效于对重叠位置做了加权融合。nn.hanning生成的是正弦窗形状权重边缘压低可以防止接缝锯齿。运行这个脚本前要先确认image的通道顺序是HWC且值为0-255如果和训练时的预处理不一致预测结果会明显变差。5. 避坑与常见问题高分辨率语义分割项目翻车现场5.1 训练loss下降但mIoU不上来这是最常遇到的一个现象。loss曲线很好看一路降下去验证mIoU却原地踏步甚至有的类别还在倒退。原因通常是两类一是类别极度不平衡占比大的背景类主导了loss下降小类别一直学不到二是数据增强不匹配航拍特性比如做了过强的色彩抖动破坏光谱一致性。解决这个问题的第一步是看每个类别的IoU不要只看总体。如果道路、汽车这类小目标IoU惨不忍睹就把损失切换成CE与Dice的组合并增加稀有类像素的采样权重。第二步是把色彩增强关掉对比一轮实验航拍图不同时相的光照差异已经足够大不需要额外加太多颜色扰动。5.2 整图推理OOM切成补丁又出现网格状伪影高分辨率航拍图单张可能超过1亿像素一次性forward必然OOM所以需要滑窗。但滑窗拼回去后边缘常能看到明显网格线。原因在于补丁边界处上下文不连续模型预测结果在边界上概率震荡。解决办法就是4.3节里的重叠加权平均不要用硬拼接。还有一个容易忽略的点补丁尺寸要能被下采样比例整除DeepLabv3的backbone下采样倍率通常是8或16倍如果补丁尺寸不是8的倍数插值对齐时会有微小偏差边界处会周期性错位。优先选528、512这类能被16整除的尺寸。5.3 真值图与影像发现系统性错位航拍数据来源很多比如从公开数据下载或自己用无人机飞但影像和标注往往不是同一套坐标系生成。有一个很典型的失误DSM数字表面模型和RGB影像空间参考不一致导致建筑屋顶的标签偏移一到两个像素训练出来的模型预测图也自带这种偏移验证的时候边界区域算出来的IoU极低。我的建议是在建模前先做一次严格的对齐检查用GDAL读取两个文件的地理变换参数比较原点和分辨率如果偏差较大就重投影后再做切片。肉眼检查时可以在叠加图上反复切换半透明标签看建筑边缘是否贴合。这一步不做后面的超参数调优全是白费功夫。5.4 torchvision预训练backbone输出尺寸与自定义数据不匹配torchvision自带的deeplabv3_resnet101默认输出21类COCO你的数据集可能是6类或8类直接改最后一层输出维度时容易弄错位置。正确做法是把model.classifier[-1]替换为新输出头但更隐蔽的问题在于backbone的预训练权重覆盖了除最后分类层之外的所有层替换最后一层后其他权重照常加载。如果你连backbone一起换掉了预训练权重又没对齐键名训练的时候会发现loss半天降不下去。遇到这类问题先打印model.state_dict()的键名和前几层参数是否来自预训练再用torchvision官方自带的迁移示例为标准对照。5.5 训练集和验证集来自同一区域导致指标虚高航拍数据的采集往往是一个区域飞一遍同一区域内相邻图像之间有大量重叠如果你按文件随机划分数据补丁训练集和验证集可能包含同一栋建筑的不同部分验证mIoU会高得让人兴奋但模型换个区域立刻现出原形。正确划分是按空间位置分块或者按航线条带划分训练集、验证集、测试集之间没有任何物理上的重叠区域。我在做数据目录时会先给每个原始文件按地理坐标算一个聚类标签再按这个标签做分层划分。这一步对毕业设计答辩尤其重要评审老师一旦发现你的验证集和训练集同源整篇论文的可信度都要打折扣。6. 进阶技巧与正确验证从能跑通到能答辩6.1 换backbone与测试时增强让分数再涨两个百分点当你用ResNet101跑通基线以后想提高指标但不想伤筋动骨最有效的是换backbone。torchvision官方模型支持deeplabv3_resnet50和deeplabv3_mobilenet_v3_large但航拍遥感任务上我更建议用带空洞卷积的ResNet101变体或者换用视觉库里的ResNeSt、ConvNeXt。换backbone时只需要修改backbone层的输出通道数并同步调整ASPP的in_channels。这个操作看起来简单但实际很多人忘了ASPP输入通道要和backbone输出统一导致加载权重时报尺寸不匹配。一个实用的做法是先用model torchvision.models.segmentation.deeplabv3_resnet101(pretrainedTrue)保存权重再修改分类器头这样backbone的预训练是完整加载的。测试时增强是另一种不用重训就能提升指标的手段推理时把图做水平翻转和垂直翻转分别预测然后对预测概率取平均。这个操作在遥感语义分割里几乎无脑白赚1到2个mIoU点尤其对建筑物这种轴对称目标效果显著。代价是推理时间翻三倍不过毕业设计完全可接受。我在实际项目中会把TTA开放成命令行开关方便在最终版本的测试集上统一启用。import torch.nn.functional as F def predict_with_tta(model, patch_t): model.eval() with torch.no_grad(): pred torch.softmax(model(patch_t)[out], dim1) pred_flip_h torch.softmax(model(torch.flip(patch_t, dims[3]))[out], dim1) pred_flip_v torch.softmax(model(torch.flip(patch_t, dims[2]))[out], dim1) # 翻转后需要翻回来再累加 pred_flip_h torch.flip(pred_flip_h, dims[3]) pred_flip_v torch.flip(pred_flip_v, dims[2]) pred (pred pred_flip_h pred_flip_v) / 3.0 return pred这里的torch.flip分别对宽度和高度维度做镜像翻转翻转预测后再翻回来相当于三个视角的预测做集成。执行TTA前要先确认模型处于eval()模式并且输入patch_t的维度是[N, C, H, W]。TTA无法弥补模型本事的缺陷如果基线mIoU不到0.5先别急着上TTA回头查数据。6.2 可视化错误分析毕业设计答辩的加分项毕业设计答辩时评委不关心你调了几个超参数他们更看重你是否系统性地分析过结果。我强烈建议做一个错误模式可视化页面把验证集上预测错误的区域分三类统计真阳率、假阳率、假阴率然后按类别把最典型的错误样本挑出来贴到论文里。比如“建筑边缘的阴影被误分为水体”“狭窄道路在阴影处断开”这两类错误几乎每个航拍语义分割项目都会出现你提前把它们可视化并解释成因答辩效果会远好于只丢一张mIoU大表。做法很简单预测图和真值都是整数标签矩阵逐像素比较得到error图用matplotlib的imshow叠加在原始影像上半透明展示。可以按类别筛选出错误率最高的前几个补丁分析它们的空间分布特征。我第一次做这个分析时才发现有一类错误集中出现在图像边界的补丁上后来确认是滑窗推理时跨界上下文缺失导致的。从那以后我每次实验都会把错误可视化作为固定产出而不是只在最后补一张对比图。我后面再跑航拍分割项目会把这一步提前到第一次验证后就做尽早发现数据或流程里的系统性错误不要攒到所有实验做完才回头查。你可以把TTA和错误可视化当成一套默认流程保存成脚本后续换数据集、换backbone都可以直接复用。希望这篇笔记能帮你把DeepLabv3在航拍数据上跑通也祝你答辩顺利。本文还有配套的精品资源点击获取
返回列表