尧图精选

DeepLabV3+实战指南:从原理到微调评估的完整课程设计路径

🕒 发布时间:2026/9/16 6:32:26 📁 来源:尧图网络
简介基于开源模型DeepLabV3的水体漂浮物分割项目脱胎于模式识别与机器学习课程的小组结课作业同时为极市开发者平台打榜项目。项目以深度学习语义分割为主线对水体及其上的漂浮物进行像素级识别并通过面积阈值判断是否报警适合计算机视觉初学者、高校学生以及需要课程设计参考的开发者。压缩包共258个文件整体约29.77MB包含120张png和100张jpg图像样本样本覆盖河流、海洋等典型场景可用于模型训练与验证26个Python脚本为核心代码涉及模型搭建、推理、阈值判断及报警输出另有txt和sh文件辅助环境配置与说明目录结构清晰便于快速定位数据、代码和文档。已有223人学习提供完整的项目方案、代码和数据可以帮助学习者掌握DeepLabV3在真实分割任务中的部署流程以及课程项目从数据处理到报警反馈的完整落地链路。1. 从跑通到能答辩的 DeepLabV3 课程设计路径小组结课项目选 DeepLabV3最常见的坑是把“下载权重、跑出一张分割图”当成项目完成。开源模型给的是起点不是结果真正决定课程设计分数的是你能不能解释清楚网络结构、能不能在小数据集上微调、能不能把 mIoU 算明白。这篇内容按一条完整路径展开先理清 DeepLabV3 的设计逻辑再搭环境跑通最小推理然后在 100300 张图上做单卡微调最后落到评估指标和答辩准备。适合小组里负责模型部分的同学也适合一个人从数据到报告全链路走完的场景。模式识别与机器学习课程里讲的特征表示、梯度下降、损失函数、类别不平衡都能在这个项目里找到对应落点。2. 为什么课程设计选 DeepLabV3结构、理论点与选型对照语义分割是一个像素级的分类任务输入一张 H×W×3 的图像输出一张 H×W 的标签图每个像素被分到预定义的类别里。它和图像分类最大的区别在于分类只输出一个全局标签而分割必须对每个像素给出局部决策所以模型既要有足够的感受野理解“这是什么物体”又要有足够的分辨率判断“物体的边界在哪”。这两个需求天然冲突DeepLabV3 的编码器-解码器结构就是为调和这对矛盾设计的。2.1 语义分割任务与模式识别课程的知识点对应从模式识别角度看DeepLabV3 把课程里的多个核心概念串起来了。特征表示对应 backbone 输出的特征图感受野决定了一个像素能“看到”多大的上下文训练过程用的是梯度下降和反向传播模型越深低层特征的梯度信号越弱这也是解码器存在的意义损失函数用逐像素交叉熵而类别不均衡、评估指标选择这些问题在分割任务里比在普通分类任务里更突出。举个例子一张街景图里背景像素可能占 80%行人只占 5%。如果只算整体准确率模型把所有像素都预测为背景也能拿到 80% 以上的分数但这在分割任务里毫无意义。课程设计如果想讲出深度这个点就是很好的切入点。2.2 DeepLabV3 的三个核心组件DeepLabV3 是在 DeepLabV3 基础上加了解码器整体结构可以拆成三块来理解。2.2.1 空洞卷积不增加参数地扩大感受野普通卷积在 stride1 时3×3 卷积的感受野就是 3×3。空洞卷积通过在卷积核的采样位置之间插入空洞让同样 3×3 的卷积核覆盖更大的范围。PyTorch 里只需要一个参数import torch.nn as nn # 3x3 卷积dilation6padding 要跟着 dilation 调整 conv nn.Conv2d(in_channels512, out_channels256, kernel_size3, padding6, dilation6)dilation6 时3×3 卷积核的等效尺寸变成 (3-1)×6113感受野显著扩大但参数量和计算量几乎不变。这里的padding6是为了让输出特征图尺寸不变计算方式是padding dilation * (kernel_size - 1) // 2。这个设计直接回答案辩时最常被问的问题为什么不用更大的卷积核因为大卷积核参数太多而空洞卷积用更小的代价拿到同样的感受野。2.2.2 ASPP多尺度上下文信息ASPP 是 Atrous Spatial Pyramid Pooling 的缩写结构上是一组并行的空洞卷积加上一个图像级分支。具体来说输入特征图经过四个分支一个 1×1 卷积、三个 dilation 分别为 6、12、18 的 3×3 空洞卷积、以及一个全局平均池化。四个分支的输出拼接到一起再经过 1×1 卷积融合。为什么需要多尺度同一个物体在图像里的大小是变化的远处的行人只有几十个像素近处的行人可能占据半个画面。固定 dilation 的感受野只能适配某一尺度并行使用多个 rate 才能覆盖不同大小的物体。全局平均池化分支把整张图的全局信息压缩成一个向量再 broadcast 回去相当于给每个像素一个“全局上下文提示”。2.2.3 解码器恢复边缘细节编码器输出特征是输入分辨率的 1/16直接上采样回原尺寸会得到边缘模糊的粗糙结果。DeepLabV3 的做法是取编码器浅层的高分辨率特征经过 1×1 卷积降到指定通道数与深层特征拼接再用 3×3 卷积融合。浅层特征分辨率高、空间信息完整深层特征语义强、类别判断准两者互补。这一部分在课程报告里可以对应“梯度消失”的讨论。网络越深浅层特征在反向传播时获得的梯度越弱如果只靠深层特征做预测边界很难学出来。解码器的 skip 连接为梯度提供了从高层到低层的短路路径目前在 torchvision 的实现里这一层通常是取 ResNet 第一个 stage 的输出。2.3 选型对照为什么不是 FCN、U-Net 或 PSPNet课程设计选模型时除了效果好还要考虑理论可讲程度和开源生态的完善程度。四个常见候选对比如下模型核心理论点开源权重可得性对课程知识点覆盖FCN全卷积、上采样少且老旧理论太单薄报告写不厚U-Net编码器-解码器、skip connection医疗影像为主结构简单可讲内容有限PSPNet金字塔池化 PPM有少量实现与 ASPP 思路接近DeepLabV3空洞卷积、ASPP、编码器-解码器torchvision、paddle 均有官方权重知识点最全每个模块都有理论支撑实际做课程设计还有一个现实因素torchvision 直接提供 DeepLabV3 的预训练权重和预处理逻辑省去自己归一化、转换标签的麻烦。组员可以分工一个人负责跑通推理一个人负责数据标注一个人负责指标计算和报告整理分工边界相对清晰。3. 先跑通最小推理开源 DeepLabV3 的安装与第一次分割课程设计的时间预算里环境搭建通常比想象中更耗时间。一个可行的策略是先用最小集跑通推理确认整条链路没问题再开始准备训练数据。这样环境问题不会和数据问题混在一起排查。3.1 环境依赖一组稳定的版本组合以下组合在常见的实验室机器上都能跑通CUDA 11.8 的兼容性覆盖了 30 系和 40 系显卡conda create -n deeplab python3.10 conda activate deeplab pip install torch torchvision --index-url https://download.pytorch.org/whl/cu118 pip install opencv-python pillow matplotlib tqdm tensorboard没有独立显卡的机器把cu118换成cpu即可CPU 跑单张图推理没问题只是后续训练会很慢。依赖组件建议按下表核对组件推荐范围说明Python3.93.11过新版本的 Python 可能与部分依赖冲突PyTorch / torchvision2.x 系列版本必须对应避免单独升级CUDA 驱动11.8 或更高驱动版本要能兼容所选 cu118 wheel显存推理 4GB训练建议 8GB不够用就换 ResNet50 backbone推理阶段显存占用很小真正的压力在训练阶段。确认 torch 能调用 GPU 的命令是python -c import torch; print(torch.cuda.is_available())输出True再继续往下走。3.2 用 torchvision 的预训练权重做推理torchvision 从 0.13 开始用 Weights 枚举管理预训练权重代码写法比旧版本更规范也避免了手动设置归一化参数的常见错误。下面这段代码可以直接保存为infer.py运行import torch from PIL import Image import numpy as np import matplotlib.pyplot as plt from torchvision.models.segmentation import ( deeplabv3_resnet101, DeepLabV3_ResNet101_Weights, ) # 加载预训练权重并进入评估模式 weights DeepLabV3_ResNet101_Weights.DEFAULT model deeplabv3_resnet101(weightsweights) model.eval() # 权重自带的 transforms 会处理 resize 和 normalize preprocess weights.transforms() img Image.open(input.jpg).convert(RGB) batch preprocess(img).unsqueeze(0) # [1, 3, 520, 520] with torch.no_grad(): out model(batch)[out] # [1, 21, 520, 520] pred out.argmax(dim1).squeeze().numpy() # VOC 21 类颜色映射背景为黑色 VOC_COLORMAP np.array([ (0,0,0),(128,0,0),(0,128,0),(128,128,0),(0,0,128),(128,0,128), (0,128,128),(128,128,128),(64,0,0),(192,0,0),(64,128,0),(192,128,0), (64,0,128),(192,0,128),(64,128,128),(192,128,128),(0,64,0),(128,64,0), (0,192,0),(128,192,0),(0,64,128) ], dtypenp.uint8) plt.imsave(prediction.png, VOC_COLORMAP[pred])model 输出的out是最终预测形状为[1, 21, 520, 520]21 对应背景加 20 个 PASCAL VOC 类别。argmax(dim1)在类别维度取最大值得到每个像素的类别编号。weights.transforms()做了两件事将图像缩放到 520×520以及使用预训练时相同的均值和标准差做归一化。很多 tutorial 里手写mean[0.485,0.456,0.406]但忘了 resize或者用错插值方式直接使用内置 transforms 可以避免这些偏差。3.3 第一次推理容易踩的四个报错跑通最小推理的过程里绝大多数报错集中在下面几类。每一条后面给出直接解法。第一权重下载失败。报错信息通常是Unable to download weights这与网络环境有关。解法是手动到 torchvision 官方权重列表找到对应 URL下载后放到~/.cache/torch/hub/checkpoints/目录文件名必须与报错提示完全一致PyTorch 会先检查本地缓存再触发下载。第二显存不足。推理时默认输入 520×520batch size 为 1单卡 4GB 基本够用。如果自定义输入分辨率过高导致 OOM可以在预处理前先把图像缩小或者换用deeplabv3_resnet50效果略降但显存占用更低。第三通道数不一致。输入如果是 RGBA 四通道 PNG直接送入预处理器会报维度错误。convert(RGB)可以解决这一行代码必须放在打开图片之后。第四归一化数值错乱。常见的手写预处理里有人先img / 255.0再减均值前后顺序不对会直接把颜色分布破坏分割结果出现大片暗色错误。所以推理阶段认准weights.transforms()训练阶段如果需要自定义增强再按训练数据的格式单独实现。4. 数据集准备与单卡微调在 100300 张图上训练 DeepLabV3课程设计的训练环节最忌讳直接拿完整 PASCAL VOC 从头训练。2012 的训练集有 1464 张图单卡要训练几十个小时小组作业的预算完全不够。常见做法是从公开数据集中抽取一个子集或者在自建的小规模数据集上微调100 到 300 张图对于课程设计已经足够。4.1 数据集怎么来公开子集和自建标注如果项目没有特定场景要求从 PASCAL VOC 2012 中按类别抽取子集是最快的路线。比如只保留包含 person 的图片复制对应JPEGImages和SegmentationClass里的文件再手动切分train.txt和val.txt。如果课程设计有具体场景比如校园绿地、桌面物件就需要自建标注。开源工具推荐 LabelMe导出 JSON 后可以用自带的命令行工具转成 VOC 格式。数据集目录结构统一为data/ ├── JPEGImages/ # 原始图片 ├── SegmentationClass/ # 标签图单通道 PNG ├── train.txt └── val.txt标签图的规则是背景为 0类别从 1 开始编号无法确定的区域用 255 标记。255 这个值不是随便选的在计算损失时通过ignore_index255直接跳过这些像素。边界区域的标注本来就模糊强行让模型学一个不明确的标签还会干扰训练这正是 255 存在的意义。4.2 一个可以直接抄的 Dataset 与增强下面的 Dataset 实现覆盖了随机缩放、随机裁剪和归一化标签图用INTER_NEAREST防止插值产生新类别import os import random import cv2 import torch from torch.utils.data import Dataset class VOCSegDataset(Dataset): def __init__(self, root, splittrain, crop_size321): self.root root with open(os.path.join(root, f{split}.txt)) as f: self.ids [x.strip() for x in f if x.strip()] self.crop_size crop_size # 归一化使用 ImageNet 统计量与预训练权重保持一致 self.mean torch.tensor([0.485, 0.456, 0.406]).view(3, 1, 1) self.std torch.tensor([0.229, 0.224, 0.225]).view(3, 1, 1) def __len__(self): return len(self.ids) def __getitem__(self, idx): img_id self.ids[idx] img cv2.imread(os.path.join(self.root, JPEGImages, img_id .jpg))[:, :, ::-1] mask cv2.imread(os.path.join(self.root, SegmentationClass, img_id .png), 0) # 随机缩放增强对目标尺寸的鲁棒性 sc random.uniform(0.5, 2.0) h, w mask.shape img cv2.resize(img, (int(w * sc), int(h * sc))) mask cv2.resize(mask, (int(w * sc), int(h * sc)), interpolationcv2.INTER_NEAREST) # 随机裁剪到固定大小保证 batch 内尺寸一致 h, w mask.shape if h self.crop_size or w self.crop_size: img cv2.resize(img, (self.crop_size, self.crop_size)) mask cv2.resize(mask, (self.crop_size, self.crop_size), interpolationcv2.INTER_NEAREST) else: x0 random.randint(0, w - self.crop_size) y0 random.randint(0, h - self.crop_size) img img[y0:y0 self.crop_size, x0:x0 self.crop_size] mask mask[y0:y0 self.crop_size, x0:x0 self.crop_size] t torch.from_numpy(img / 255.0).float().permute(2, 0, 1) t (t - self.mean) / self.std return t, torch.from_numpy(mask).long()随机缩放让模型在不同尺度上都能识别目标随机裁剪则相当于对位置做增广。crop_size 选 321 而不是更大的原因有两个一是和 DeepLab 论文中的训练设置一致二是 321 在 8GB 显存下能支撑更大的 batch。如果显存紧张把crop_size降到 256 会更稳。需要注意这里的简化解法默认图片至少和 crop 一样大实际使用建议先按最大边缩放到 600 再进 Dataset。4.3 训练超参一组不用改也能收敛的配置微调阶段采用 DeepLab 论文中的经典设置稳定性已经过大量公开实现验证。关键参数如下表参数取值理由BackboneResNet50torchvision 提供对应预训练权重显存友好输出层替换最后的 1x1 卷积保留 ASPP 和 backbone 的预训练参数LossCrossEntropyLoss(ignore_index255)逐像素多分类跳过未标注区域优化器SGD, lr0.007, momentum0.9, weight_decay1e-4分割任务的标准配置学习率策略poly, power0.9后期学习率缓慢下降微调边缘训练轮数3060小数据集 30 轮已足够Batch Size8显存小则 4影响 BatchNorm 统计量训练主循环的关键代码如下注意学习率的动态调整import torch.optim as optim from torch.nn import CrossEntropyLoss from torchvision.models.segmentation import ( deeplabv3_resnet50, DeepLabV3_ResNet50_Weights, ) num_classes 2 # 背景 你的目标类别 model deeplabv3_resnet50(weightsDeepLabV3_ResNet50_Weights.DEFAULT) # classifier 是序列结构索引 4 是最后的 1x1 卷积 model.classifier[4] torch.nn.Conv2d(256, num_classes, kernel_size1) criterion CrossEntropyLoss(ignore_index255) optimizer optim.SGD(model.parameters(), lr0.007, momentum0.9, weight_decay1e-4) total_iters len(train_loader) * epochs def poly_lr(epoch, it): cur epoch * len(train_loader) it return 0.007 * (1 - cur / total_iters) ** 0.9 for epoch in range(epochs): for i, (x, y) in enumerate(train_loader): lr poly_lr(epoch, i) for g in optimizer.param_groups: g[lr] lr out model(x)[out] loss criterion(out, y) optimizer.zero_grad() loss.backward() optimizer.step()model.classifier在 torchvision 中是一个顺序容器索引 4 对应最后那个 1×1 卷积只替换这一层即可ASPP 的全部参数继续沿用预训练权重。poly_lr函数的曲线是前期学习率下降慢后期下降快让模型在接近收敛时做小步长的精细调整。课程报告里讲这部分时可以把它和机器学习课程里的学习率调度对照起来写。训练过程中的 loss 记录可以一行搞定print(fepoch {epoch} iter {i} loss {loss.item():.4f})。想可视化更完整加一个SummaryWriter(runs)每 50 步写入writer.add_scalar(train/loss, loss.item(), global_step)然后用tensorboard --logdirruns在浏览器里观察曲线。4.4 训练状态观察与两个常见问题第一个问题是 loss 一直不降。先确认 mask 里的类别编号没有错位背景是 0、前景从 1 开始再看ignore_index是否有效如果 mask 里真实背景像素是 255 而不是 0模型会认为整张图都没有背景类。第二个问题是验证集 loss 不降反升这是典型的过拟合信号小数据集上出现得很早。对策是按顺序试减少 epoch、加强随机缩放和翻转、最后才考虑冻结 backbone 浅层。冻结的方式很简单for p in model.backbone.parameters(): p.requires_grad False optimizer optim.SGD( filter(lambda p: p.requires_grad, model.parameters()), lr0.007, momentum0.9, weight_decay1e-4 )这样梯度只回传到 ASPP 和解码器backbone 的预训练特征完全保留训练参数量大幅减少收敛速度会明显加快。缺点是可学习的特征不再针对你的数据集做适配适用于数据量特别少的场景。5. DeepLabV3 微调结果的评估、可视化与答辩准备5.1 自己算 mIoU一段可直接放进报告的代码语义分割最常用的指标是 mIoU即所有类别 IoU 的平均值。IoU 的计算方式是交集除以并集对每个类别单独计算import numpy as np def compute_miou(pred, target, num_classes2, ignore255): pred pred.copy() target target.copy() pred[target ignore] ignore ious [] for c in range(num_classes): p (pred c) t (target c) inter (p t).sum() union p.sum() t.sum() - inter if union 0: continue # 该类别在验证集中没有出现 ious.append(inter.item() / union.item()) return np.mean(ious), ious这段代码对整个验证集统计而不是逐张图计算 IoU 再平均。课程报告里建议直接给出每个类别的 IoU 列表和整体 mIoU比单独一个数字更有说服力。为什么不直接用像素准确率因为背景像素占比太高准确率会被背景拉高模型即使漏掉全部前景目标也能得到不错的数值。mIoU 给每个类别相同的权重能真实反映模型在少数类上的表现。5.2 报告可视化与答辩准备报告里放两组图一组是同一张图的原图、标签、预测图并排列另一组是预测图半透明叠加到原图上突出边界误差。叠加可以用cv2.addWeighted(img, 0.5, colorized, 0.5, 0)只对前景区域混合。答辩时最常被问的三个问题为什么用空洞卷积回答“扩大感受野而不增参数”ASPP 的作用回答“并行多尺度上下文适配不同大小的目标”为什么用预训练而不是从头训练回答“图像特征具有通用性预训练权重提供了良好的初始化小数据避免了欠拟合”。如果时间允许还可以提一下类别不平衡的解决方案比如按类别频率的倒数给损失加权这正好呼应机器学习课程里的代价敏感学习。提示小数据集上微调时冻结 backbone 后再训练是性价比最高的改进。分割的先验都保存在预训练参数里后续 ASPP 和解码器只需要学会把特征映射到新类别的决策空间收敛更稳报告里也多了一个可以展开讨论的设计决策。本文还有配套的精品资源点击获取
上一篇/下一篇内容由系统自动关联 返回资讯列表 →