
简介这份资源面向图像分割初学者与进阶开发者提供基于PyTorch在VOC与Cityscapes数据集上训练DeepLabv3的完整项目源码与流程教程帮助读者掌握语义分割从数据准备到模型部署的全链路实践。压缩包共43个文件约2.13MB以23个Python脚本为核心覆盖网络结构、数据集加载、损失函数、学习率调度与可视化等模块另含17张png效果图、2个txt依赖与说明文件及1个md文档目录按datasets、network、utils等分层组织便于按模块查阅。已有492人学习下载。读者可参照源码理解ASPP与解码器设计完成数据增强、训练监控、IoU与像素准确率评估并借助可视化结果排查问题适合自动驾驶、医学影像等场景的语义分割入门与实战参考。1. 从一份能跑通的 DeepLabv3 源码包说起VOC 与 Cityscapes 双数据集到底怎么落地如果你正在找一份能在 PyTorch 上把 DeepLabv3 真正跑起来的工程代码而不是只讲 ASPP 结构的 PPT那这份「基于 PyTorch 在 VOC Cityscapes 上训练 DeepLabv3 图像分割算法」的源码包值得拆开看。它把数据加载、空洞卷积主干、解码器、损失、指标、可视化、预测脚本全部摊在一个目录里main.py负责训练主循环predict.py负责单图推理datasets/下voc.py和cityscapes.py分别对接两个数据集network/里_deeplab.py和modeling.py定义模型utils/里塞了loss.py、scheduler.py、ext_transforms.py、visualizer.py。对做图像分割的从业者来说这套结构最大的价值是「可复现」——你不用从零拼装训练框架改几个路径和超参就能看到 IoU 曲线动起来。适合刚入门语义分割、需要一份能跑通 baseline 的工程师也适合想对比 VOC 与 Cityscapes 两个数据集训练差异的人。2. 拆开源码包看结构DeepLabv3 在 PyTorch 里到底由哪几块拼成2.1 目录映射与模块职责拿到压缩包解压后先别急着python main.py花五分钟把目录和职责对一遍后面调参才不会迷路。这份工程的组织方式很典型属于「一个 main 带多个工具模块」的轻量结构没有过度封装反而方便你逐行读。路径职责你大概率会改的地方main.py训练入口解析参数、构建 dataloader、跑 epoch 循环学习率、batch size、epoch 数、数据集选择predict.py单图/批量推理输出彩色 mask 和 overlay权重路径、输入图片路径、类别数datasets/voc.pyVOC 数据集读取与标签映射数据根目录、增强策略datasets/cityscapes.pyCityscapes 读取处理 19 类或 30 类映射类别定义、ignore_indexnetwork/_deeplab.pyDeepLabv3 主体含 ASPP 与解码器主干选择、输出步长network/modeling.py主干网络封装ResNet 等是否加载预训练权重utils/loss.py交叉熵等分割损失类别权重、忽略标签utils/scheduler.py学习率调度策略poly / step 策略参数utils/ext_transforms.py同步对图像和标签做几何变换翻转、缩放、裁剪尺寸metrics/stream_metrics.py在线累计 IoU、像素准确率指标计算方式utils/visualizer.py训练曲线与分割结果可视化是否接 visdomsamples/目录里那批1_image.png、1_target.png、1_pred.png、1_overlay.png以及city_1_*、114_*、23_*系列是作者跑出来的对照样例image 是原图target 是标注pred 是预测overlay 是叠加效果。你训练完自己的模型后可以直接拿predict.py生成同款四件套做肉眼比对这比只看 IoU 数字直观得多。2.2 DeepLabv3 的关键结构在代码里怎么体现DeepLabv3 相比 DeepLabv3 的核心改动有两处一是用空洞卷积Atrous Convolution在不大幅增加参数的前提下扩大感受野二是加了解码器模块把深层粗略的语义信息和浅层精细的边界信息融合。这两点在network/_deeplab.py里能直接找到对应实现。ASPPAtrous Spatial Pyramid Pooling模块通常由多个不同膨胀率的空洞卷积并联组成再叠加一个全局平均池化分支最后拼接。膨胀率一般取 6、12、18 这类值配合 output stride 16 或 8。解码器则把主干浅层特征经过 1x1 卷积降维后与 ASPP 输出上采样结果拼接再经过几层 3x3 卷积得到最终 logits。你读代码时重点看两点低层特征是从主干的哪个 stage 取出来的以及上采样用的是双线性插值还是转置卷积。这两处直接决定边界分割的细腻程度。提示如果显存吃紧先把 output stride 设成 16别一上来就冲 8。stride 8 精度略高但显存和计算量明显上升VOC 上 16 通常够用。2.3 环境依赖与版本对齐requirements.txt里列了依赖但 PyTorch 版本和 CUDA 的对应关系是新手最容易翻车的地方。常见做法是先确认显卡驱动支持的 CUDA 版本再去 PyTorch 官网选对应命令安装不要盲目pip install torch拉最新版。Python 和 PyTorch 版本也有对应关系3.8 到 3.10 一般比较稳。# 建议用 conda 建独立环境避免污染系统 Python conda create -n deeplab python3.9 -y conda activate deeplab # 按你的 CUDA 版本去 PyTorch 官网选对应命令这里以 CUDA 11.8 为例 pip install torch torchvision --index-url https://download.pytorch.org/whl/cu118 # 再装工程其余依赖 pip install -r requirements.txt装完先验证 GPU 是否可用这一步别省import torch print(torch.__version__) print(torch.cuda.is_available()) # 期望 True print(torch.cuda.get_device_name(0)) # 打印显卡型号如果is_available()返回 False先查驱动和 CUDA 版本是否匹配再查是不是装成了 CPU 版。CPU 版也能跑通小规模调试但训练 Cityscapes 会慢到让你怀疑人生。3. 数据准备与训练配置VOC 和 Cityscapes 两条路怎么走通3.1 VOC 数据集的目录约定与标签处理VOC 是语义分割入门最常用的基准20 类前景加背景。工程里datasets/voc.py一般按VOCdevkit/VOC2012/JPEGImages和SegmentationClass两个目录读取图像和标注。标注是调色板 PNG每个像素值对应类别索引读取后需要做映射把 255 这类边界忽略值处理成ignore_index。# datasets/voc.py 里典型的数据读取逻辑示意 import os import numpy as np from PIL import Image import torch from torch.utils.data import Dataset class VOCSegDataset(Dataset): def __init__(self, root, splittrain, transformNone): self.root root self.transform transform # 图像与标注文件名一一对应 img_dir os.path.join(root, JPEGImages) mask_dir os.path.join(root, SegmentationClass) self.images sorted(os.listdir(img_dir)) self.img_dir img_dir self.mask_dir mask_dir def __getitem__(self, idx): name self.images[idx] img Image.open(os.path.join(self.img_dir, name)).convert(RGB) mask Image.open(os.path.join(self.mask_dir, name.replace(.jpg, .png))) mask np.array(mask, dtypenp.uint8) # 边界像素标为 255训练时忽略 mask[mask 255] 255 if self.transform: img, mask self.transform(img, mask) return img, torch.from_numpy(mask).long() def __len__(self): return len(self.images)这里的关键参数是ignore_index损失函数里要设成 255让边界像素不参与梯度。ext_transforms.py里的变换必须同时作用于图像和标注且标注只能用最近邻插值用双线性会把类别索引插成小数直接导致标签错乱这是血泪经验。3.2 Cityscapes 的类别映射与高分辨率处理Cityscapes 专注城市街景原始 30 类训练时通常映射到 19 类。它的图像分辨率高1024x2048直接整图训练显存扛不住常见做法是随机裁剪到 512x512 或 768x768。datasets/cityscapes.py里要处理gtFine和leftImg8bit两个目录标注文件名带_gtFine_labelIds.png后缀。# Cityscapes 类别映射示意把 trainId 之外的 id 归为 ignore import numpy as np def encode_cityscapes(mask, id_to_trainid): out np.full_like(mask, 255, dtypenp.uint8) for k, v in id_to_trainid.items(): out[mask k] v return outid_to_trainid这张映射表是 Cityscapes 训练的核心漏掉任何一项都会让某些类别永远学不到。裁剪时要注意如果裁剪窗口里全是 ignore 像素这个样本对训练没贡献最好在 dataloader 里做重采样。3.3 训练参数配置与启动main.py里通常用 argparse 暴露参数。启动训练前把数据集路径、batch size、学习率、epoch 数确认一遍。VOC 数据量小batch size 可以设 8 到 16Cityscapes 分辨率高batch size 往往只能设 2 到 4配合梯度累积。# VOC 训练示例 python main.py \ --dataset voc \ --data-root /path/to/VOCdevkit/VOC2012 \ --backbone resnet101 \ --output-stride 16 \ --batch-size 8 \ --lr 0.007 \ --epochs 50 \ --crop-size 513 # Cityscapes 训练示例 python main.py \ --dataset cityscapes \ --data-root /path/to/cityscapes \ --backbone resnet101 \ --output-stride 16 \ --batch-size 4 \ --lr 0.01 \ --epochs 200 \ --crop-size 768学习率策略常见的是 poly衰减指数 0.9初始学习率 VOC 上 0.007 左右、Cityscapes 上 0.01 左右是常见起点。batch size 变了学习率要跟着调线性缩放是个粗糙但好用的经验。训练时盯住metrics/stream_metrics.py输出的 IoU如果前几个 epoch IoU 一直是 0八成是标签映射或 ignore_index 设错了。4. 训练过程排查损失不降、IoU 不动、显存爆掉怎么定位4.1 损失函数与类别不平衡分割任务里背景像素远多于前景直接用交叉熵会让模型偏向背景。utils/loss.py里常见做法是加类别权重或者用带 ignore_index 的交叉熵。VOC 上类别相对均衡Cityscapes 上道路、天空占比大行人和交通标志占比小权重设置要更小心。import torch import torch.nn as nn # 带忽略标签和类别权重的交叉熵 criterion nn.CrossEntropyLoss( ignore_index255, weighttorch.tensor([...]) # 按类别频率倒数近似设置 )权重不是越大越好某些类别权重设得过高会让模型疯狂预测该类反而拉低整体 IoU。稳妥做法是先不加权重跑一版 baseline看每类 IoU 再决定给谁加权。4.2 学习率与调度器学习率太大表现为损失震荡甚至变 NaN太小表现为损失下降极慢。utils/scheduler.py里的 poly 策略是lr base_lr * (1 - iter/max_iter)^powerpower 取 0.9。如果你发现训练后期损失几乎不动可能是学习率衰减太快把 power 调小或改用 cosine 试试。4.3 显存与 batch size 的取舍显存爆掉时优先降 batch size其次降 crop size最后才考虑换小主干。把 ResNet101 换成 ResNet50 能省不少显存但精度会掉一两个点。梯度累积可以在小 batch 下模拟大 batch 的效果# 梯度累积示意每 4 步更新一次 for i, (imgs, masks) in enumerate(loader): outputs model(imgs) loss criterion(outputs, masks) / 4 loss.backward() if (i 1) % 4 0: optimizer.step() optimizer.zero_grad()注意 loss 要除以累积步数否则等效学习率被放大。5. 避坑与常见问题这几处翻车点我替你踩过了5.1 现象训练 loss 正常但验证 IoU 始终接近 0原因标签映射错误或者验证集标注读取时没做同样的 encode 处理导致预测类别和标签类别对不上。解决拿一张验证图把标签和预测都可视化出来肉眼比对颜色是否对应同一类别。samples/里的 target 和 pred 对照图就是干这个用的。5.2 现象显存报 OOM但 batch size 已经降到 1原因crop size 太大或者 output stride 设成 8 导致特征图过大。解决先把 crop size 降到 512output stride 设 16再逐步往上加。另外检查是否有中间变量没释放比如在验证阶段忘了torch.no_grad()。5.3 现象训练到一半 loss 变 NaN原因学习率过大或者某批数据里全是 ignore 像素导致 loss 为 0 后梯度异常。解决加梯度裁剪torch.nn.utils.clip_grad_norm_(model.parameters(), 1.0)并在 dataloader 里过滤全 ignore 样本。5.4 现象Cityscapes 上某些类别 IoU 一直是 0原因id_to_trainid映射表漏了该类或者该类在裁剪窗口里出现频率极低。解决核对映射表完整性并在采样时对稀有类别做 oversampling。5.5 现象预测结果边界毛糙、小物体丢失原因解码器低层特征利用不充分或者上采样方式过于粗糙。解决确认解码器确实拼接了浅层特征上采样优先用双线性插值加卷积而不是直接转置卷积。6. 从训练到推理predict.py 出图、指标验证与一个提点技巧训练跑通只是第一步真正验证模型好不好得靠predict.py出图和指标双重确认。推理脚本一般加载权重、读单张图、前向、取 argmax、映射回彩色 mask再和原图叠加成 overlay。import torch import numpy as np from PIL import Image def predict(model, img_path, num_classes21, devicecuda): model.eval() img Image.open(img_path).convert(RGB) # 预处理要和训练时一致归一化参数别搞错 x torch.from_numpy(np.array(img)).permute(2, 0, 1).float() / 255.0 x x.unsqueeze(0).to(device) with torch.no_grad(): out model(x) pred out.argmax(1).squeeze(0).cpu().numpy().astype(np.uint8) return pred这里最容易翻车的是预处理不一致训练时用了 ImageNet 均值方差归一化推理时忘了结果预测一塌糊涂。我一般会把训练和推理的 transform 抽成同一个函数两边共用杜绝这种玄学问题。指标验证方面metrics/stream_metrics.py里的在线 IoU 是累计所有 batch 的混淆矩阵再算比逐 batch 平均更准。验证时记得把模型切到 eval 模式并关掉梯度否则 BN 层统计量和 dropout 会让结果不稳定。一个提点技巧如果 VOC 上训练完想迁移到 Cityscapes别直接从头训。先用 VOC 预训练权重初始化主干再在 Cityscapes 上微调学习率调小一个量级收敛快且精度更高。反过来 Cityscapes 到 VOC 同理。跨数据集迁移时类别数不一致最后一层分类头要重新初始化其余层加载预训练权重。从那以后我每次跑新数据集都强制先拿 20 张图过一遍完整流程——训练一个 epoch、验证一次、推理出 overlay——确认链路通了再开长训练。这个习惯帮我省下了无数次跑到半夜才发现标签映射错位的后悔药。希望帮到你。本文还有配套的精品资源点击获取