多彩编程 多彩编程MZPH · CODE BLOG
ARTICLE DETAIL

文章详情

深耕前端与后端开发技术的一线实战笔记与踩坑复盘。

DeepLabV3+语义分割实战:从原理到课程设计的完整指南

DeepLabV3+语义分割实战:从原理到课程设计的完整指南 简介面向模式识别与机器学习课程设计的小组结课项目也是极市开发者平台的一项打榜方案。项目以DeepLabV3为骨干网络对水体及漂浮物进行像素级分割并根据面积阈值输出告警信息适合学习语义分割、智能巡检的初学者及进阶者参考。压缩包共258个文件大小约29.77MB其中120张png与100张jpg构成涵盖河流、海洋等场景的图像数据集26个Python脚本承担训练、预测与报警逻辑另有txt说明、shell脚本、license及md文档等辅助文件目录分类清晰。目前已有223人学习下载。资源除完整项目代码外还附带了水体漂浮物实景图像与标注数据可帮助读者快速复现分割流程理解从数据准备、模型调用、面积判断到结果输出的完整链路也可直接迁移到类似的水域监测任务中。1. 为什么小组结课项目选 DeepLabV3能跑通、讲得清、出结果很多小组到答辩前两周才意识到机器学习课程设计选题如果只做一个分类模型既没新意也很难讲出深度。模式识别课的最优落地方式其实是语义分割——一张图进去每个像素都有类别结果直观、能可视化。DeepLabV3恰好是这类任务里最成熟的候选之一torchvision 直接带预训练权重几十行代码就能搭出训练闭环而且它内部的空洞卷积和特征金字塔结构正好对应课上讲的模式识别流程。适合想在有限课时内把一个机器学习模型从数据处理一路做到评估的小组。这个方向最值钱的地方在于它不依赖你去造新模型而是考验你把数据、模型、训练、评估串成一条可靠流水线的能力。2. DeepLabV3 的架构拆解ASPP、Decoder 与 backbone 怎么选课程设计的常见误区是拿到模型就开训直到答辩 PPT 需要讲原理时才对着源码发愣。DeepLabV3 虽然名字长但拆开就三块backbone 负责抽特征ASPP 负责抓多尺度上下文Decoder 负责恢复边界细节。理解这三块不只为答辩也直接决定你要选哪个预训练权重、Batch Size 开多大、loss 为什么这样设。2.1 从 FCN 到 DeepLabV3语义分割的演进逻辑语义分割是一个逐像素的分类问题比图像分类多了一个难点既要保留位置信息又要扩大感受野拿到上下文。FCN 用全卷积把分类网络改造成分割网络但上采样太粗糙边界细节基本靠猜。U-Net 用跳连接把 encoder 特征拉回来对小目标友好但感受野有限。DeepLab 系列则用空洞卷积解决“下采样太多导致分辨率下降”的老问题。到 DeepLabV3 这一版原作者把模型拆成 encoder-decoder 两部分。encoder 部分的核心是 ASPPAtrous Spatial Pyramid Pooling四个并行的空洞卷积带不同 dilation rate加一个全局平均池化分支相当于让模型同时看小目标和大物体最后 concat 到一起。decoder 部分则把 backbone 浅层的低阶特征用 1x1 卷积降通道再与上采样后的 ASPP 输出 concat通过卷积融合后恢复成原分辨率。对小组项目来说ASPP 最大的现实意义是不需要手工设计特征。课堂上讲的“特征提取”在这类模型里被显式替换为多尺度空洞卷积组合答辩时从这一点切入比泛泛说“神经网络自动学习特征”要有说服力得多。而 decoder 的跳连接设计也正好对应模式识别里常说的高层语义加低层细节的融合思路。2.2 backbone 选 MobileNetV2 还是 ResNet101按课程设计的目标定DeepLabV3 的 backbone 一般有 ResNet101 和 MobileNetV2 两条路线课程设计选型要按自己的显存和课时定不要照搬论文。backbone显存占用训练速度mIoU 上限适合场景ResNet50/101高8G 显存容易爆慢一个 epoch 可能要十几分钟高有 24G 以上显存追求指标MobileNetV2低中端卡能跑快一个 epoch 十分钟内中无独立显卡、课时紧、先跑通管线我一般会建议优先 MobileNetV2。课程设计真正要验证的是“模式识别流程能不能闭环”不是刷榜单。流程跑通之后如果想加亮点再换 ResNet50 也不迟代码不需要动改一行模型构造函数就行。另一个关键决策是预训练权重。常见做法是用 torchvision 里的 deeplabv3_resnet50 或 deeplabv3_mobilenet_v3_large 直接加载 COCO 预训练权重然后在自己数据集上 fine-tune。除非老师明确禁止用预训练否则不要自己从头训 backbone——DeepLabV3 参数量在课程设计的时间尺度内很难凭空训出来这一点第 4 章会专门展开。2.3 把任务映射到模式识别与机器学习的完整流程模式识别课程讲的是五件事数据采集、预处理、特征提取、分类决策、评估。DeepLabV3 正好能一一对应上。VOC 图像及标注是数据采集resize、归一化、数据增强是预处理ResNet backbone 前几层输出是特征提取ASPP 与 decoder 融合后再过一个 1x1 卷积得到每个像素在各类别上的得分是分类决策mIoU 和像素准确率就是评估环节。这个映射很重要它让答辩从“我们跑了一个模型”变成“我们按模式识别方法论完整地做了一次像素级分类任务”。比如评审老师问“特征是怎么提取的”你可以直接答backbone 的 layer2 到 layer4 输出就是不同分辨率的特征图ASPP 在这些特征图基础上用不同空洞率的卷积采样等价于在多个尺度上做特征提取。再比如问“为什么用交叉熵损失”可以答分割的 ground truth 是像素级类别标签每个像素独立做多分类交叉熵是这类逐像素分类的标准选择加上 ignore_index255 是为了跳过标注边界这和课上讲的类别不平衡处理是同一种思路。如果老师要求写课程设计报告这一节内容也基本就是“模型原理”那一章的大纲。别从网上抄那些讲不清的模型结构描述按 encoder、ASPP、decoder 三段写配上自己的数据流说明比什么都有说服力。3. 从数据下载到训练评估把 DeepLabV3 跑通的最小闭环这一章的代码可以直接抄。我会把课程设计里最常用、也是我自己验证过最稳的一条链路写出来Pascal VOC 2012 数据集torchvision 自带 DeepLabV3CrossEntropyLoss 加 SGD训练二十轮左右最后算 mIoU 并输出可视化结果。每一步都附上参数怎么改、出了错看哪里。3.1 数据集与目录准备VOC 2012 为什么是课程设计默认选择课程设计一般选 Pascal VOC 2012原因很实际只有 21 类含背景标注是单通道 PNG不像 ADE20K 有复杂的分割协议也不像 Cityscapes 要处理车载鱼眼图。VOC 的下载方式固定用下面脚本可以拉下来解压mkdir -p VOCdevkit cd VOCdevkit wget https://pjreddie.com/media/files/VOCtrainval_11-May-2012.tar tar -xvf VOCtrainval_11-May-2012.tar参数说明数据包大概 2GB 左右包含 JPEGImages 原图、SegmentationClass 分割标注、ImageSets/Segmentation 下的 train.txt 和 val.txt 划分文件。如果官网源慢可以在国内镜像站找 VOC 2012 的 tar 包GitHub 上也有搜索引擎可找到的备份地址。解压完先检查目录结构是否完整常见翻车是只下到了 ImageSets 没有图片训练一启动就报文件找不到。需要重点理解的是标注格式。SegmentationClass 里的 PNG 是 8 位调色板模式P mode每个像素存的是类别 id背景是 0aeroplane 是 1bicycle 是 2一直到 sofa 是 20边缘难例和标注轮廓是 255。255 这个值在训练时要通过 ignore_index 跳过不然会把标注者的描边痕迹也当成一个类别来学。有一个我在第一次做分割时踩过的坑不要用 OpenCV 的 cv2.imread 读标注图OpenCV 对 P mode 的 PNG 支持不好容易把调色板索引读成 RGB 三元组导致 label 变成一个 [H, W, 3] 的数组后面 loss 直接形状不匹配。用 PIL 读最稳。3.2 预处理与数据增强管线label 和 image 的变换必须分开设计图像可以随便做色彩抖动、归一化但 label 只能做几何变换且插值方式必须是最邻近。如果对 label 用了双线性插值类别 id 会被插成小数比如类别 3 和 4 之间的像素变成 3.5四舍五入后就成了伪标签。下面这段 Dataset 代码直接体现这个原则import torch import numpy as np from torch.utils.data import Dataset from PIL import Image import torchvision.transforms as T import random class VOCSegDataset(Dataset): def __init__(self, root, splittrain, img_size512, train_modeTrue): self.split split self.img_size img_size self.train_mode train_mode with open(f{root}/ImageSets/Segmentation/{split}.txt) as f: self.ids [line.strip() for line in f] def __len__(self): return len(self.ids) def __getitem__(self, idx): img_id self.ids[idx] img Image.open(f{root}/JPEGImages/{img_id}.jpg).convert(RGB) label Image.open(f{root}/SegmentationClass/{img_id}.png) # 图片双线性label 最邻近原因见上文 img T.Resize((self.img_size, self.img_size), T.InterpolationMode.BILINEAR)(img) label T.Resize((self.img_size, self.img_size), T.InterpolationMode.NEAREST)(label) if self.train_mode: if random.random() 0.5: img img.transpose(Image.FLIP_LEFT_RIGHT) label label.transpose(Image.FLIP_LEFT_RIGHT) img T.ColorJitter(brightness0.3, contrast0.3, saturation0.3)(img) img T.ToTensor()(img) img T.Normalize(mean[0.485, 0.456, 0.406], std[0.229, 0.224, 0.225])(img) label torch.as_tensor(np.array(label), dtypetorch.long) return img, label参数说明img_size 设 512 在 8G 显存下训练会紧张可以先设 320 跑通再加到 512。ColorJitter 只作用在 img 上不碰 label这是分割任务里不能妥协的一条。随机翻转用同一个 random.random() 判断保证 image 和 label 同步翻不要对 image 和 label 各写一次独立翻转判断否则训练静默出错——loss 会下降但分割图对不上原图。Normalize 用的均值方差是 ImageNet 的统计值原因是预训练权重是在 ImageNet 上训的输入分布要尽量一致。如果不用预训练权重这个 Normalize 也要保留只是效果会差一些。3.3 训练脚本与超参数SGD 与 Adam 怎么选、torchvision 输出格式是什么torchvision 里已经集成好了 DeepLabV3直接调用就行。注意它的返回值是一个 dict包含 out 和可选的 aux 两个键out 才是最终分割输出。aux 是辅助分类器的输出用于辅助训练课程设计里建议关闭 aux_loss少一个损失项更容易排查问题。完整训练循环如下import torch import torchvision.models as models # 用 COCO 预训练权重省去从头训 backbone 的时间 model models.segmentation.deeplabv3_resnet50(pretrainedTrue, progressTrue) model.train() model model.cuda() # ignore_index255 跳过标注边界 criterion torch.nn.CrossEntropyLoss(ignore_index255) # lr 0.01 配 batch size 16如果 batch 缩到 4lr 改成 0.002~0.004 optimizer torch.optim.SGD(model.parameters(), lr0.01, momentum0.9, weight_decay1e-4) epochs 30 # PolynomialLR分割训练的主流衰减方式比 StepLR 平缓 scheduler torch.optim.lr_scheduler.PolynomialLR( optimizer, total_itersepochs, power0.9) for epoch in range(epochs): total_loss 0.0 for images, labels in train_loader: images, labels images.cuda(), labels.cuda() optimizer.zero_grad() outputs model(images)[out] # [B, 21, H, W] loss criterion(outputs, labels) loss.backward() optimizer.step() total_loss loss.item() scheduler.step() print(fepoch {epoch1}, loss {total_loss / len(train_loader):.4f})参数说明这个脚本最需要调的就是 batch size、lr 和 img_size 三者的组合。SGD 加 lr0.01 是 DeepLab 论文里的经典配置但那是基于大 batch size 的显存不够时 batch 缩小lr 也要等比缩小否则 loss 会在前几个 epoch 来回震荡。如果实在不想调 SGD可以换成 Adamlr 用 1e-4batch size 不敏感适合新手只是论文里常用 SGDmomentum答辩时提到的经典性不如前者。训练时长方面VOC train 集约 1464 张图320x320 输入、batch size 8、单张消费级显卡一个 epoch 大概 3~5 分钟30 个 epoch 一下午能跑完。如果时间不够20 个 epoch 也能出及格结果但 mIoU 会在最后几个 epoch 涨得很快建议尽量跑到 25 轮以上。3.4 评估脚本mIoU 的计算方式与可视化输出评估要算两个指标像素准确率和 mIoU。mIoU 是逐类先算 IoU 再取平均对类别不平衡更敏感也是 VOC 挑战赛的标准指标。计算方式看代码import numpy as np def compute_miou(pred, target, num_classes21): # pred, target: [H, W] int 数组语义上是已对齐的 ious [] for cls in range(num_classes): mask_pred (pred cls) mask_gt (target cls) inter (mask_pred mask_gt).sum().item() union (mask_pred | mask_gt).sum().item() if union 0: ious.append(inter / union) return np.mean(ious), ious # 返回平均 IoU 和每类 IoU答辩用逐类分析 # 验证循环里必须带 model.eval() 和 no_grad model.eval() all_ious [] with torch.no_grad(): for images, labels in val_loader: images images.cuda() out model(images)[out] # [B, 21, H, W] pred out.argmax(dim1).cpu().numpy() # [B, H, W] labels_np labels.numpy() p a hrefhttps://download.csdn.net/download/weixin_48083386/89503733 stylecolor:#ec7500;font-size:14px; 本文还有配套的精品资源点击获取 /a img altmenu-r.4af5f7ec.gif srchttps://csdnimg.cn/release/wenkucmsfe/public/img/menu-r.4af5f7ec.gif stylewidth:16px;margin-left:4px;vertical-align:text-bottom;cursor:text; /p
返回列表