恒美微站 Logo 恒美微站
  • 首页
  • 关于我们
  • 建站服务
  • 主题模板
  • 案例展示
  • 资讯中心
  • 联系我们

DeepLabv3+航拍图像语义分割实战:ASPP与解码器详解

  • 首页
  • 资讯中心
  • /
  • DeepLabv3+航拍图像语义分割实战:ASPP与解码器详解

相关资讯

TSN时间敏感网络全解析:时间同步、数据调度与工程落地 2026/10/12 12:04:35
SpringBoot+小程序毕设选题:新能源汽车4S店试驾平台全指南 2026/10/12 12:04:35
IDEA构建Spring Boot项目部署生产环境全流程与避坑指南 2026/10/12 12:04:35

最新资讯

PCA动态风险平价:让投资组合真正抗波动的实战方法
代码审查自动化实践:open-code-review 工具的设计与落地
操作系统引论核心知识点与答题技巧全解析
open-code-review:从零搭建开放式代码评审流程与知识沉淀实践
开放代码审查工具 open-code-review:设计、实现与踩坑实录
VSCode插件开发:根据文件路径一键跳转到对应文件的实现方案与TaoToken配置

今日推荐

Debian新手入门:从部署到日常操作的完整指南
MongoDB复制集扩缩容实战:从rs.add到选主事故复盘
条形码目标检测数据集实战:从YOLOv8训练到部署

本周热门

UE动画修改实战:从资产编辑到重定向与蒙太奇驱动
统计随机数生成器攻击下的KLJN安全密钥交换协议Matlab仿真
政务API安全治理:资产测绘、低代码编排与行标对标实践

本月精选

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证
2026 大模型集体涨价:用 Python 做企业 Token 成本测算与选型避坑(附配置)

DeepLabv3+航拍图像语义分割实战:ASPP与解码器详解

发布时间:2026/10/12 12:09:35
DeepLabv3+航拍图像语义分割实战:ASPP与解码器详解 简介基于DeepLabv3的高分辨率航拍图像语义分割项目面向计算机视觉方向毕业设计选题适合具备Python与深度学习基础、希望快速构建语义分割系统的学生。项目以航拍影像地物分类为核心覆盖道路、建筑、植被等典型分割场景集成ResNet、HRNet、Swin、Twins、BiSeNetV2等多种骨干网络并提供DeepLabv3基线、SAM架构在线与离线对比实验。整个资源包共包含184个文件主体为95个Python源码脚本和84个pyc中间文件另有3个Jupyter Notebook用于流程演示与实验记录以及README说明文档和txt文本压缩包仅477KB体积极小轻量易部署便于直接修改运行。目前已有204人学习下载。资源内容涉及模型结构搭建、数据加载、训练评估与多网络适配等关键环节可帮助读者理解DeepLabv3原理、掌握航拍图像分割的完整实验流程并可为后续算法改进、毕业设计论文写作提供可直接使用的代码基础。1. 高分辨率航拍图的语义分割为什么毕业设计绕不开DeepLabv3研二那年拿到一批几千乘几千像素的航拍正射影像要完成房顶、道路、林地、水体四分类。最开始用FCN跑了一版边界糊得像水彩晕染小房子漏检到没法看。换成DeepLabv3之后同样数据、同样标注mIoU直接提了七八个点。这篇笔记就是我把这条路重新走一遍的记录选型理由、高分辨率影像的切块预处理、PyTorch下ASPP与解码器的实现、训练显存管理、大图滑窗推理以及一路踩过的坑。如果你正在做或打算做遥感图像语义分割方向的毕业设计照着顺序跑通一遍就知道这个方向值不值得投入。2. 航拍语义分割选型DeepLabv3的结构与数据准备2.1 DeepLabv3在航拍场景的优势编码器-解码器与多尺度ASPP高分辨率航拍影像和自然图像最大的区别在于尺度跨度。一棵树的树冠可能只有十几个像素一条主干道却横跨整张大图。FCN只有同一层特征直接上采样小目标的分辨率和上下文两头够不着。U-Net把编码器每层都并给解码器细节够了但缺少对上下文多尺度的显式建模。DeepLabv3恰好是这两条思路的组合编码器部分用带空洞卷积的ASPP提取多尺度上下文解码器部分再融合一层低层特征把边界找回来。ASPP模块里有四路并行一个1x1卷积、三个rate分别为6/12/18的空洞卷积外加一个全局平均池化分支。空洞卷积在参数不增加的前提下放大感受野rate18那一支能覆盖约8倍于普通3x3卷积的范围正好应对航拍里同一个语义类在不同区域的尺度差异。低层特征在这里扮演的角色常被忽略。ASPP输出的是stride16或32的粗糙特征图直接上采样到原图尺寸房顶和道路的边界会偏移好几个像素。DeepLabv3的解码器把stride4的低层特征用1x1卷积降到48通道后与高层特征拼接再走一次3x3卷积。低层特征提供的锐利边缘和高层特征提供的类别语义互相补位这就是它在遥感图像语义分割上比FCN稳定、比U-Net省显存的原因。模型多尺度建模边界恢复训练显存实现复杂度FCN无依赖深层感受野差低低U-Net弱靠跳跃连接中中低DeepLabv3强ASPP多rate空洞卷积好高中选型结论很直接毕业设计要的是可解释性、可调参空间和结果上限。DeepLabv3在这三个维度上恰好都是问题的入口ASPP、空洞卷积、解码器融合这些概念拿出来都可以在答辩时讲清楚。2.2 遥感影像语义分割的数据准备TIFF转RGB、切patch与标签映射拿到原始航拍数据第一步不是写模型而是看数据是什么格式。常见的高分辨率航拍数据有TIFF多波段文件可能带R、G、B、NIR四个波段也可能是16位深度的单波段灰度。直接用PIL或opencv去读这种文件经常会读到奇怪的颜色甚至只读到第一个波段。我一般会先把原始影像统一转成8位RGB的三通道PNG再进入切块流程。转换用tifffile和numpy就行import tifffile import numpy as np import cv2 # 原始TIFF可能是 (波段数, H, W) 的布局 img tifffile.imread(ortho.tif) if img.ndim 3 and img.shape[0] in (3, 4): img img.transpose(1, 2, 0) # 转为 (H, W, 波段数) # 取前三个波段作为RGB四波段数据一般可以保留NIR后面单独用 rgb img[:, :, :3] # 16位数据先做线性拉伸再转8位直接astype会丢失大量细节 if rgb.dtype np.uint16: rgb np.clip(rgb / 257, 0, 255).astype(np.uint8) cv2.imwrite(ortho_rgb.png, cv2.cvtColor(rgb, cv2.COLOR_RGB2BGR))这里的核心参数是数据布局和位深转换。tifffile读出来的多波段影像常常是(C, H, W)需要先转成(H, W, C)16位转8位除以257是因为16位范围是0~65535除以257刚好映射到0~255比直接做除法再四舍五入更接近原始亮度分布。标签文件同样要注意编码。很多公开数据集和人工标注工具导出的标签是RGB颜色索引图比如(255, 255, 255)代表不透水面、(0, 0, 255)代表建筑物。网络训练需要的是0到k-1的整数索引颜色图不能直接喂给CrossEntropyLoss。切patch这一步决定了后面所有训练和推理的体验。航拍原图动辄6000x6000像素整个图进显存基本不可能常见做法是切512x512或256x256的patch送入训练from pathlib import Path def split_image(image, label, patch_size512, overlap64): h, w image.shape[:2] step patch_size - overlap patches [] for y in range(0, h - patch_size 1, step): for x in range(0, w - patch_size 1, step): img_patch image[y:y patch_size, x:x patch_size] lab_patch label[y:y patch_size, x:x patch_size] # 过滤掉几乎全背景的patch航拍数据背景占比经常超过80% if (lab_patch 0).mean() 0.95: patches.append((img_patch, lab_patch, x, y)) return patches # 使用示例 train_data split_image(rgb, label_idx, patch_size512, overlap64)切patch时最重要的参数是overlap。overlap64意味着相邻patch之间有约12%的区域重复这不会影响训练效率但能保证patch边缘的语义被多个patch共同覆盖后续推理时拼接接缝也更好处理。过滤阈值0.95的意思是如果一个patch里95%以上都是背景类这个patch对学习贡献很小直接丢掉省下训练时间。如果目标区域在图上占比很小这个过滤阈值要放宽到0.99甚至关闭过滤否则目标类别可能只剩少量样本。切完的patch我通常会直接存成.npy文件或写入LMDB避免每次训练都重新读原始大图做切块。如果数据量不大也可以直接在Dataset里动态读取用内存换IO简单度。2.3 数据增强与类别不均衡ce_loss加权重还是直接用dice遥感数据里背景类和目标类往往严重失衡。一片城市航拍图里建筑物可能有30%道路10%树木15%其余全是不透水面或空地。这种情况下直接用CrossEntropyLoss模型会倾向于把不确定像素全部预测为高频类。常见做法有两个方向一个是对CrossEntropyLoss按类别频率加权重另一个是直接把损失换成Dice Loss。我一般两者结合主损失用加权交叉熵辅助损失用Dice Loss比例控制在1比1左右。import torch import torch.nn.functional as F def weighted_ce_loss(pred, label, class_weight): # pred: (B, C, H, W), label: (B, H, W) return F.cross_entropy(pred, label, weightclass_weight) def dice_loss(pred, label, n_classes6, smooth1.0): # pred经过softmax后与one-hot标签计算每个类的Dice probs F.softmax(pred, dim1) # (B, C, H, W) one_hot F.one_hot(label, n_classes).permute(0, 3, 1, 2).float() intersection (probs * one_hot).sum(dim(2, 3)) cardinality probs.sum(dim(2, 3)) one_hot.sum(dim(2, 3)) dice (2 * intersection smooth) / (cardinality smooth) return 1 - dice.mean()类别权重怎么算常见公式是median frequency balancing统计每个类像素占比取占比的中位数除以各类占比最后归一化。直接用1 / class_freq也有效但会放大像素占比极小的类别训练早期抖动明显。数据增强方面航拍图不像自然图像那样存在大量上下颠倒的问题但旋转和翻转依然非常有效。90度旋转、水平垂直翻转、随机裁剪是三个最基础也最稳的增强。颜色增强只作用在RGB通道上不要作用在标签上。如果训练数据里同一地点有多个时相影像考虑随机亮度扰动有利于增强泛化。3. 用PyTorch搭建DeepLabv3核心模块与训练参数3.1 从零实现ASPP与解码器核心代码框架这里用PyTorch手动实现DeepLabv3的两个关键模块。不建议直接调torchvision的预训练DeepLabv3模型虽然它更快但答辩时被问到ASPP内部结构会讲不清楚。自己写一遍结构摆在明面上改起来也方便。import torch import torch.nn as nn import torch.nn.functional as F class ASPP(nn.Module): def __init__(self, in_channels2048, out_channels256): super().__init__() # 1x1卷积分支 self.branch1 nn.Sequential( nn.Conv2d(in_channels, out_channels, 1, biasFalse), nn.BatchNorm2d(out_channels), nn.ReLU(inplaceTrue) ) # 三个不同空洞率的分支 self.branches nn.ModuleList() for rate in (6, 12, 18): self.branches.append(nn.Sequential( nn.Conv2d(in_channels, out_channels, 3, paddingrate, dilationrate, biasFalse), nn.BatchNorm2d(out_channels), nn.ReLU(inplaceTrue) )) # 图像级全局池化分支 self.pool_branch nn.Sequential( nn.AdaptiveAvgPool2d(1), nn.Conv2d(in_channels, out_channels, 1, biasFalse), nn.BatchNorm2d(out_channels), nn.ReLU(inplaceTrue) ) # 融合后的1x1投影 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:] # 全局池化分支需要插值回输入尺寸 pooled self.pool_branch(x) pooled F.interpolate(pooled, sizesize, modebilinear, align_cornersFalse) outs [self.branch1(x)] for branch in self.branches: outs.append(branch(x)) outs.append(pooled) out torch.cat(outs, dim1) return self.project(out)关键参数是paddingrate。空洞卷积输出尺寸不变的前提是padding等于dilation这里rate取6、12、18对应的padding也是6、12、18。如果把padding写成普通卷积的1特征图尺寸会逐层缩小后面decode阶段对齐就乱了。align_cornersFalse同样重要。PyTorch官方插值默认是FalseBatchNorm的behavior计算维度也要注意。整个训练过程保持align_cornersFalse采样的坐标对齐方式就统一了否则训练和推理之间会固定差出半个像素结果就是边缘系统性偏移。解码器模块负责把ASPP输出和低层特征融合class Decoder(nn.Module): def __init__(self, high_channels256, low_channels256, out_channels48): super().__init__() # 低层特征先做1x1卷积降维 self.low_conv nn.Sequential( nn.Conv2d(low_channels, out_channels, 1, biasFalse), nn.BatchNorm2d(out_channels), nn.ReLU(inplaceTrue) ) # 融合高低层特征 self.fuse nn.Sequential( nn.Conv2d(high_channels out_channels, 256, 3, padding1, biasFalse), nn.BatchNorm2d(256), nn.ReLU(inplaceTrue) ) def forward(self, high, low): low self.low_conv(low) high F.interpolate(high, sizelow.shape[-2:], modebilinear, align_cornersFalse) return self.fuse(torch.cat([high, low], dim1))低层特征的通道数通常是256或512直接从ResNet的layer1输出取特征图尺寸是原图的1/4。1x1卷积把这个维度压到48目的是让低层特征不要在高维通道里喧宾夺主只作为边界细节的补充。论文里这个数字就是48增大到64或128时边界反而更碎因为低层特征里包含太多纹理噪声。整体模型组装时骨干网络用torchvision的ResNet-50预训练权重。torchvision的resnet50去掉avgpool和fc之后从children()里切分前4个模块是stem输出stride4后面的layer1到layer4输出stride32。这样切分比手动写ResNet更省事也不容易抄错结构import torchvision.models as models class DeepLabV3Plus(nn.Module): def __init__(self, n_classes6): super().__init__() backbone models.resnet50( weightsmodels.ResNet50_Weights.DEFAULT) features list(backbone.children())[:-2] self.stem nn.Sequential(*features[:4]) # stride4 self.layers nn.Sequential(*features[4:]) # stride32 self.aspp ASPP(in_channels2048, out_channels256) self.decoder Decoder(high_channels256, low_channels256, out_channels48) self.seg_head nn.Conv2d(256, n_classes, 1) def forward(self, x): low self.stem(x) # (B, 256, H/4, W/4) high self.layers(low) # (B, 2048, H/32, W/32) high self.aspp(high) # (B, 256, H/32, W/32) out self.decoder(high, low) # (B, 256, H/4, W/4) out F.interpolate(out, sizex.shape[-2:], modebilinear, align_cornersFalse) return self.seg_head(out) # (B, C, H, W)需要注意ResNet-50输出的低层特征来自stem这里stem包含conv1、bn1、relu、maxpool输出的通道数是256而不是论文里的64。64是论文对Xception骨干的取值ResNet骨干改成256降维到48即可不会影响效果。3.2 训练配置batch_size、学习率策略与混合精度配好网络结构后训练参数直接决定收敛速度和最终精度。DeepLabv3在航拍数据集上最稳的训练配置如下import torch.optim as optim optimizer optim.SGD( model.parameters(), lr0.01, momentum0.9, weight_decay1e-4 ) def poly_lr(optimizer, base_lr, iteration, total_iters, power0.9): lr base_lr * (1 - iteration / total_iters) ** power for param_group in optimizer.param_groups: param_group[lr] lrSGD配合poly学习率衰减是语义分割的主流方案。batch_size根据显存来512x512输入、ResNet-50骨干下12G显存可以跑batch_size8显存不够就先降到4再不行降到256patch。注意poly衰减的power取0.9是分割任务的标准值power1.0就是线性衰减实际差不了太多但0.9能保留更长时间的高学习率对前期的收敛速度更有利。混合精度训练在航拍分割里非常值得开显存直接降三分之一到一半而且PyTorch的原生AMP封装很好用from torch.cuda.amp import GradScaler, autocast scaler GradScaler() for imgs, masks in train_loader: imgs, masks imgs.cuda(), masks.cuda() optimizer.zero_grad() with autocast(): preds model(imgs) loss criterion(preds, masks) scaler.scale(loss).backward() scaler.step(optimizer) scaler.update()AMP有一个坑要注意BatchNorm在fp16下统计方差比较敏感所以PyTorch的autocast会自动把BatchNorm保持在fp32。人工实现时不建议自己写half()直接用autocast上下文是标准做法。如果训练中loss突然变成nan大概率是梯度溢出GradScaler会自动跳过这一步但频繁出现就要调小学习率或者检查数据里是否有异常像素值。3.3 训练循环与验证混合损失与最佳模型保存训练循环本身不复杂但每一轮结束要验证一次验证集不参与训练只用来保存最优模型def train_one_epoch(model, loader, optimizer, criterion, scaler): model.train() total_loss 0.0 for imgs, masks in loader: imgs, masks imgs.cuda(), masks.cuda() optimizer.zero_grad() with autocast(): preds model(imgs) loss criterion(preds, masks) scaler.scale(loss).backward() scaler.step(optimizer) scaler.update() total_loss loss.item() return total_loss / len(loader) def evaluate(model, loader, n_classes): model.eval() iou_list [] with torch.no_grad(): for imgs, masks in loader: imgs imgs.cuda() preds model(imgs) preds torch.argmax(preds, dim1).cpu().numpy() masks masks.numpy() iou_list.append(compute_iou(preds, masks, n_classes)) return np.mean(iou_list)这里criterion建议用第2.3节里的加权CE加Dice组合。只加CE小目标类别容易躺平只用Dice整个训练过程会振荡因为Dice对类别分布非常敏感。我常用的比例是0.6CE 0.4DiceLoss。如果验证集mIoU超过几个epoch都不动先检查损失是否还在下降再把Dice比例加到0.5。模型保存只保存推理效果最好的那一份不要看训练loss。判断标准是验证集mIoU不是某个类别的准确率。每轮训练后算一次mIoU比历史最优就覆盖保存一次best_model.pth。毕设后期要做实验对比在模型命名里带上epoch和mIoU两个信息省得后面翻车。4. 高分辨率大图推理滑窗拼接、后处理与评价指标4.1 重叠滑窗推理与高斯基尼权重融合训练时用的是512x512的patch推理时整张大图不能直接喂进模型需要把大图切成patch逐个推理再拼回去。最简单的做法是滑窗时分块推理然后直接拼图但这样做patch接缝处容易出现明显的条带尤其在高分辨率航拍图上看起来就像马赛克拼贴。我一般用带重叠的滑窗推理同时对每个patch的贡献乘一个权重窗口。位于patch中心位置的像素权重高靠近patch边缘的像素权重低重叠区域多个patch的概率按权重加权平均接缝就平滑了。实际实现里常用Hanning窗口近似高斯权重计算成本低且效果稳定import numpy as np import torch def slide_inference(model, image, patch_size512, stride448, n_classes6): h, w image.shape[:2] prob np.zeros((n_classes, h, w), dtypenp.float32) weight np.zeros((h, w), dtypenp.float32) # 二维Hanning窗口作为融合权重 win np.outer(np.hanning(patch_size), np.hanning(patch_size)) win win.astype(np.float32) with torch.no_grad(): for y in range(0, h - patch_size 1, stride): for x in range(0, w - patch_size 1, stride): crop image[y:y patch_size, x:x patch_size] tensor torch.from_numpy( crop.transpose(2, 0, 1) ).unsqueeze(0).float().cuda() # 归一化到0~1再进网络 tensor tensor / 255.0 pred model(tensor) pred torch.softmax(pred, dim1).cpu().numpy()[0] prob[:, y:y patch_size, x:x patch_size] pred * win weight[y:y patch_size, x:x patch_size] win # 权重归一化后取argmax prob prob / (weight 1e-6) return np.argmax(prob, axis0)stridepatch_size-overlap上面stride448对应overlap64。如果显存富余可以把patch调到768或1024窗口权重更平滑但需要确认模型能接受任意尺寸输入。模型用的是全局平均池化加卷积对输入尺寸没有严格限制所以大尺寸patch可以直接推理。推理时还有一个容易忽略的问题原始大图边缘不一定是patch_size的整数倍。上面的代码在滑窗循环里会因range最后一个窗口覆盖不到边缘而导致右下角一圈像素没有预测值。常见的做法是在滑窗前把图padding成patch_size的整数倍网络里只对预测结果做一次对应的裁剪。最高效省事的是用np.pad把大图四边补上镜像像素推理后再裁掉。4.2 预测图后处理形态学开闭运算与类别过滤分割模型的输出是每个像素的分类索引。航拍图上会残留两类噪声一类是孤立的小块错分比如一个大房子中间冒出一两个树的像素另一类是细碎的边缘毛刺。对单类别的预测图做形态学开运算和闭运算能有效处理这些小噪块。from scipy import ndimage def postprocess_mask(pred, n_classes6, structure_size3): # pred: (H, W) 类别索引图 structure np.ones((structure_size, structure_size), dtypebool) refined np.zeros_like(pred) for c in range(n_classes): binary pred c # 开运算去孤点闭运算补小洞 binary ndimage.binary_opening(binary, structurestructure) binary ndimage.binary_closing(binary, structurestructure) refined[binary] c return refined后处理的核心参数是structure_size。3x3可以滤掉单像素噪声5x5会让边界变得过于圆滑在道路和房顶这类需要锐利边缘的类别上反而降低精度。我的经验是formask先用3x3如果跑下来某个类别出现大量碎块单独对这个类别加大结构尺寸而不是整体改。另一个实用后处理是概率阈值过滤。如果网络对某个像素的各类概率都很平均说明置信度低这些像素往往集中在类别边界。把softmax最大概率低于0.5的像素标记为不确定区域再用周围多数类别填充可以减少边界上的带状错分。这个方法对遥感影像尤其有用因为水体边界和树冠边缘本来就是模糊区域。4.3 评估指标计算mIoU、PA与Kappa毕设报告里最常用的分割指标是mIoU、Pixel Accuracy和Kappa系数。mIoU是所有类别IoU的平均值直观反映模型对每一类的分割能力。Kappa系数的特点是能剔除随机一致性的影响在类别比例极不平衡时比mIoU更能暴露问题。import numpy as np def confusion_matrix(pred, label, n_classes6): # pred / label: 展平后的类别索引数组 conf np.zeros((n_classes, n_classes), dtypenp.int64) np.add.at(conf, (label, pred), 1) return conf def compute_metrics(pred, label, n_classes6): conf confusion_matrix(pred, label, n_classes) # 逐类别IoU ious [] for c in range(n_classes): tp conf[c, c] union conf[c, :].sum() conf[:, c].sum() - tp ious.append(tp / (union 1e-6)) mIoU np.mean(ious) # Pixel Accuracy pa np.diag(conf).sum() / conf.sum() 1e-6 # Kappa系数 po np.diag(conf).sum() / conf.sum() pe (conf.sum(axis0) * conf.sum(axis1)).sum() / (conf.sum() ** 2) kappa (po - pe) / (1 - pe 1e-6) return { mIoU: mIoU, IoU_per_class: ious, PA: pa, Kappa: kappa }用np.add.at构建混淆矩阵可以避免双重循环的慢速写法。np.add.at(conf, (label, pred), 1)的括号必须写成元组形式直接传(label, pred)两个数组numpy会自动做重复索引累加。如果写conf[label, pred] 1重复的索引只会累加一次结果就错了这是个小坑。提交毕设时把每类的IoU列成表格再附一张混淆矩阵热力图比只写一个mIoU数字要直观得多。Kappa系数大于0.8通常认为模型一致性很好大于0.9则说明分割结果和真值几乎一致。迁移到新数据集上时先看Kappa再看mIoUKappa掉得比mIoU多时大概率是某个占比小的类被模型忽略掉了。5. 高分辨率分割的避坑清单python环境、显存与标签的5个翻车点5.1 python环境配置与PyTorch版本不匹配现象代码明明写了model.cuda()一跑就报RuntimeError: CUDA error: no kernel image is available for execution on the device或者torch.cuda.is_available()返回False。原因PyTorch编译时绑定的CUDA版本和机器驱动支持的CUDA版本不一致常见于python环境配置得太随意装了一个新版本PyTorch但驱动停留在旧版本。解决先用nvidia-smi看驱动支持的CUDA版本再选择匹配的PyTorch版本。命令行验证环境有效性conda create -n seg python3.8 conda activate seg python -c import torch; print(torch.__version__, torch.cuda.is_available())如果打印出True说明环境没问题如果一直打印False优先检查conda环境是否混用了不同python版本的包缓存。毕设阶段不要在系统全局python里倒腾这些包隔离环境是省时间的首要手段。5.2 12G显存的翻车batch_size调到2还是爆显存现象训练时显存占用一路冲到11.8G然后报CUDA out of memory把batch_size从8降到2仍然报错。原因显存大户不只是输入batchResNet-50中间特征图在512x512输入下每层都有几百兆而DeepLabv3的ASPP和上采样路径又会同时保有多个高分辨率特征。batch_size降到2反而把所有中间张量一起放大因为反向传播需要保存的激活值数量不变。解决先把输入patch降到256x256跑通流程显存会降到1/4开启AMP混合精度见3.2节再用torch.utils.checkpoint包装backbone用计算换显存。真正跑比较的时候再把patch加回来。5.3 标签图读入后全黑或类别值错乱现象训练集加载后可视化几个样本发现标签图全黑或者有些图片标签是RGB三通道彩色有些已经是0~5的索引图混在一起训练出来mIoU恒等于0。原因cv2默认imread会把单通道标签也读成三通道BGR全黑其实是因为标签像素值范围是0~5直接显示在0~255的LUT下几乎看不见。另一个更隐蔽的原因是标注工具导出的RGB颜色图和索引之间的映射表没有统一不同文件里同一颜色可能代表不同类别。解决统一用cv2.imread(path, cv2.IMREAD_UNCHANGED)读取标签禁止走默认三通道转换链路。在Dataset类的__init__里打印一次label的dtype、unique values和shape把这三项作为环境自检。如果出现RGB标签先用颜色字典转成索引再进训练# 假设color2idx是标注工具导出的映射表 # {(255, 255, 255): 0, (0, 0, 255): 1, ...} def rgb2index(rgb_label, color2idx): h, w rgb_label.shape[:2] idx np.zeros((h, w), dtypenp.int64) for color, class_id in color2idx.items(): mask (rgb_label np.array(color, dtypenp.uint8)).all(axis-1) idx[mask] class_id return idx5.4 loss不降、mIoU恒为0所有像素都被预测成背景现象训练了十几个epochloss稳定在一个不低的值不再下降验证集mIoU保持0打开预测图发现整张图全是一种类别。原因这是类别极度不均衡加上初始化不合理共同作用的结果。背景类占90%以上模型一开始偏向预测背景梯度回传时背景类的梯度占绝对主导小类别被淹没。解决启用类权重CE加Dice的混合损失见2.3节另外检查backbone是否加载了预训练权重。没有预训练权重、用随机初始化ResNet在数据量不足时几乎必然翻车。加载预训练权重后前5个epoch可以跑一遍warmup学习率从0到0.01线性增加大模型的收敛稳定性会明显变好def warmup_lr(optimizer, lr_start, lr_max, current_epoch, warmup_epochs5): if current_epoch warmup_epochs: lr lr_start (lr_max - lr_start) * current_epoch / warmup_epochs for g in optimizer.param_groups: g[lr] lr5.5 大图推理后预测图有明显的拼接马赛克现象用小patch训练出的模型对整张大图推理拼出来的结果呈现规则的块状条纹位置正好对应滑窗的patch边界。原因推理滑窗时没有overlap直接硬拼。patch边缘像素感受野与patch中心不同模型对边缘像素稳定性较差每个patch边缘的预测结果本来就是噪声。解决按4.1节使用overlap64的滑窗加Hanning权重融合。如果用了overlap后还有接缝检查是否在边界处使用了reflectpadding模型输入尺寸是512但最后上采样到原图大小时边缘像素被重复计算了。另一个容易忽略的点是训练和推理时的align_corners参数必须一致训练用False推理用True边界也会系统性偏移。6. 把训练好的DeepLabv3用出效果的验证与部署技巧6.1 特征图可视化看看ASPP到底学到了什么毕设答辩时一张特征图比十句话管用。在ASPP的输出投影层后面挂一个forward hook把256通道的特征图抓出来挑激活最强的几张转成灰度图能看到ASPP对不同尺度目标的敏感度差异。如果某些通道专门在大尺度路面上激活某些通道专门在细小建筑轮廓上激活就说明多尺度建模真的起作用了。features {} def hook_fn(module, input, output): features[aspp_out] output.detach().cpu() model.aspp.project.register_forward_hook(hook_fn) sample torch.zeros(1, 3, 512, 512).cuda() _ model(sample) feat features[aspp_out][0] # (256, 512, 512) # 取通道均值最大的4个通道 channel_mean feat.mean(dim(1, 2)) topk_idx torch.topk(channel_mean, k4).indices.tolist() for idx in topk_idx: vis feat[idx].numpy() vis (vis - vis.min()) / (vis.max() - vis.min() 1e-6) # vis 直接存入列表后面拼图输出特征图不需要追求好看重要的是能看出不同通道响应不同的语义区域。如果所有通道都长得差不多说明ASPP退化成了普通卷积这时检查空洞卷积的rate和padding有没有写错。6.2 出图与最终验证把分割结果叠加回原图毕设最终要交的是结果图和指标对比表。预测索引图是黑的直接放论文里没人看得懂需要把每个类映射回颜色再和原图半透明叠加。这个输出脚本值得做得规整一点因为后面要反复跑不同patch、不同后处理参数的对照实验。def overlay_result(image, pred_idx, color_map, alpha0.5): # image: (H, W, 3) 0~255 RGB # color_map: (n_classes, 3) mask_rgb color_map[pred_idx].astype(np.uint8) overlay (image * (1 - alpha) mask_rgb * alpha).astype(np.uint8) return overlay对比实验的保存路径带上参数名比如pred_s512_o64_post3.npy否则调了几轮后就会分不清哪个结果对应哪个配置。我自己因为偷懒吃过这个亏最后重跑了两天真实验从那以后所有推理结果都带参数后缀。最后的验证也不要只看mIoU。从原图里挑几个实际场景放大逐个类别检查错误形态边界是否偏了半个房子、小目标是否断裂、相邻类别是否互相渗透。这些肉眼可见的问题往往比mIoU数字更能帮你发现后续改模型的方向。如果手头有另外一块不同传感器的高分辨率影像哪怕只有100张左右也可以在训练好的模型上做个简单微调实验。通常冻结backbone、只训练ASPP和解码器用较低学习率跑几轮就能让模型在迁移场景下继续提升。这一步做完了整个项目的技术闭环就算是完整了希望帮到你。本文还有配套的精品资源点击获取

关于恒美微站

恒美微站专注于为个体商户、工作室提供极简自助建站服务,让每个人都能轻松拥有专业网站。

快速链接

  • 关于我们
  • 建站服务
  • 主题模板
  • 案例展示
  • 资讯中心

服务项目

  • 可视化建站
  • 拖拽编辑
  • 主题定制
  • SEO 优化
  • 网站托管

联系方式

  • 📍 地址:北京市朝阳区建国路 88 号
  • 📞 电话:400-888-8888
  • ✉️ 邮箱:info@hmyw.cn
  • 🕐 时间:周一至周日 9:00-18:00

© 2024 恒美微站 hmyw.cn 版权所有 | 京 ICP 备 12345678 号