新闻详情

新闻详情

首页 / 资讯中心 / 详情

PyTorch实战:SegNet图像分割源码解析与训练避坑指南

发布时间:2026/9/27 5:57:36来源:尧图网络
PyTorch实战:SegNet图像分割源码解析与训练避坑指南
简介这份资源是基于PyTorch实现SegNet图像分割任务的完整Python源码包面向计算机相关专业正在做课程设计、期末大作业的学生以及需要项目实战练习的学习者。项目经导师指导并认可获得98分成绩可作为图像语义分割方向的参考方案。压缩包共119个文件约27.19MB包含14个py源码文件、77个png图像数据、12个pyc编译文件以及pth模型权重、sh运行脚本、Dockerfile、env环境配置、logging.ini日志配置、README.md说明文档和pdf资料等覆盖从数据、模型到部署的完整链路。目前已有175人学习下载。读者可从中获取SegNet网络结构搭建、编码器-解码器实现、训练与推理流程、日志记录及容器化运行等关键代码并借助模型权重与配置脚本快速复现实验理解图像分割任务的工程组织方式与排错思路适合作为大作业模板或进阶练手项目。1. 一份能直接跑通的 SegNet 图像分割源码到底解决了什么如果你正在为期末大作业或者课程设计找一份能跑、能改、能写进报告的图像分割代码那这份基于 PyTorch 实现 SegNet 的 Python 源码包大概率能省掉你从零搭网络结构的两三天时间。SegNet 本身是图像分割里一个非常经典的编码器-解码器结构核心卖点是用最大池化索引来做上采样相比全卷积网络那种反卷积方式参数量更小、边界恢复更准在道路分割、医学图像分割这类像素级分类任务里一直是教学和工程入门的首选。这份源码把数据加载、模型定义、训练循环、指标计算和推理可视化都串起来了适合刚接触 PyTorch 图像分割的本科生也适合想快速验证自己数据集的从业者。你拿到手之后改数据路径、调几个超参就能跑不用再纠结编码器解码器怎么对齐、池化索引怎么传这些细节。2. SegNet 的编码器-解码器结构为什么池化索引是它的命门2.1 从 VGG 骨干到对称解码结构拆解SegNet 的整体结构可以理解成一条“下坡再上坡”的路。编码器部分直接沿用 VGG16 的前 13 层卷积分成 5 个 stage每个 stage 里堆两到三个 3×3 卷积加 ReLU然后接一个 2×2 最大池化。每次池化特征图长宽减半通道数翻倍最终把一张 H×W×3 的图压成 H/32×W/32×512 的特征块。解码器则是完全对称的 5 个 stage每一步先做上采样把长宽翻倍再堆卷积把通道数降回去最后接一个 1×1 卷积输出类别数通道softmax 之后就是每个像素的类别概率。关键差异在于上采样方式。很多分割网络用反卷积或者双线性插值SegNet 用的是“最大池化索引”。编码器每次做 2×2 最大池化时不光输出最大值还记录下最大值在 2×2 窗口里的位置0 到 3 的索引。解码器上采样时直接把值填回对应位置其余位置补零。这样做的好处是边界信息不会在插值里被抹平而且不需要学习上采样参数显存占用比反卷积小一截。常见做法是在编码器里用一个MaxPool2d的return_indicesTrue把索引一路存下来传给解码器。2.2 用 PyTorch 把编码器和解码器搭出来下面这段代码是 SegNet 编码器一个 stage 的典型写法我一般会把它封装成EncoderBlock方便复用。import torch import torch.nn as nn class EncoderBlock(nn.Module): def __init__(self, in_ch, out_ch, num_convs2): super().__init__() layers [] for i in range(num_convs): layers.append(nn.Conv2d(in_ch if i 0 else out_ch, out_ch, kernel_size3, padding1)) layers.append(nn.BatchNorm2d(out_ch)) layers.append(nn.ReLU(inplaceTrue)) self.conv nn.Sequential(*layers) # return_indicesTrue 是 SegNet 的灵魂必须开 self.pool nn.MaxPool2d(kernel_size2, stride2, return_indicesTrue) def forward(self, x): x self.conv(x) x, indices self.pool(x) return x, indices逻辑说明num_convs控制这个 stage 里堆几个卷积VGG16 的 5 个 stage 分别是 2、2、3、3、3。BatchNorm2d加在卷积和 ReLU 之间能明显稳住训练初期的 loss 震荡。return_indicesTrue让池化层多返回一个索引张量形状和输出特征图一致后面解码器要用它做MaxUnpool2d。参数说明in_ch是输入通道第一个 stage 是 3后面依次是 64、128、256、512。out_ch对应 64、128、256、512、512。kernel_size3, padding1保证卷积不改变长宽只有池化在降分辨率。解码器这边对应写一个DecoderBlock核心是MaxUnpool2d接收编码器传来的索引。class DecoderBlock(nn.Module): def __init__(self, in_ch, out_ch, num_convs2): super().__init__() self.unpool nn.MaxPool2d(kernel_size2, stride2, return_indicesTrue) # 占位实际用 MaxUnpool2d self.unpool nn.MaxUnpool2d(kernel_size2, stride2) layers [] for i in range(num_convs): layers.append(nn.Conv2d(in_ch if i 0 else out_ch, out_ch, kernel_size3, padding1)) layers.append(nn.BatchNorm2d(out_ch)) layers.append(nn.ReLU(inplaceTrue)) self.conv nn.Sequential(*layers) def forward(self, x, indices, output_size): x self.unpool(x, indices, output_sizeoutput_size) x self.conv(x) return x逻辑说明MaxUnpool2d的前向需要三个东西——当前特征图、编码器对应层的索引、以及上采样后的目标尺寸。目标尺寸在 PyTorch 里可以用output_size显式指定避免因为奇数尺寸导致形状对不上。output_size一般直接传编码器池化前的特征图尺寸可以在编码器 forward 里顺手存下来。参数说明in_ch和out_ch跟编码器反着来解码器第一个 stage 是 512 进 512 出最后是 64 进 64 出。num_convs同样对应 3、3、3、2、2。整个解码器最后接一个Conv2d(64, num_classes, kernel_size1)输出类别 logits。2.3 完整前向流程与输出尺寸对齐把编码器和解码器串起来的时候最容易翻车的地方就是尺寸对不上。假设输入是 360×480经过 5 次池化变成 12×15解码器第一次上采样要回到 24×30第二次 48×60第三次 96×120第四次 192×240第五次 384×480。如果输入尺寸不能被 32 整除最后一次上采样出来的尺寸就会和原图差几个像素算 loss 的时候直接报形状错误。我一般会在数据预处理里强制 resize 到 32 的倍数比如 352×480 或者 384×512。如果不想改数据也可以在解码器最后加一个F.interpolate把输出拉回原图尺寸但这样会引入额外的插值误差边界精度会掉一点。常见做法是训练时 resize 到固定尺寸推理时再插值回原图这样训练稳定、推理灵活。3. 数据管道与训练循环从文件夹到可收敛的模型3.1 数据集组织与 Dataset 类写法图像分割的数据集通常有两种组织方式一种是原图和掩码图分两个文件夹文件名一一对应另一种是原图和掩码图放在同一个文件夹用后缀区分。这份源码一般会采用第一种目录结构像这样dataset/ images/ 0001.png 0002.png masks/ 0001.png 0002.png掩码图是单通道的 PNG每个像素值就是类别 id背景是 0目标类别从 1 开始。写Dataset类的时候关键是把原图和掩码用相同的文件名读进来然后做同步的随机增强。同步增强是分割任务里最容易忽略的坑图像翻转了掩码没翻训练出来的模型直接学废。import os from PIL import Image import torch from torch.utils.data import Dataset import torchvision.transforms.functional as TF import random class SegDataset(Dataset): def __init__(self, root, splittrain, size(352, 480)): self.img_dir os.path.join(root, images) self.mask_dir os.path.join(root, masks) self.names sorted(os.listdir(self.img_dir)) self.size size def __len__(self): return len(self.names) def __getitem__(self, idx): name self.names[idx] img Image.open(os.path.join(self.img_dir, name)).convert(RGB) mask Image.open(os.path.join(self.mask_dir, name)).convert(L) # 同步 resize img TF.resize(img, self.size) mask TF.resize(mask, self.size, interpolationImage.NEAREST) # 同步随机水平翻转 if random.random() 0.5: img TF.hflip(img) mask TF.hflip(mask) img TF.to_tensor(img) img TF.normalize(img, mean[0.485, 0.456, 0.406], std[0.229, 0.224, 0.225]) mask torch.from_numpy( __import__(numpy).array(mask, dtypeint64)) return img, mask逻辑说明convert(L)把掩码转成单通道resize时掩码必须用NEAREST插值否则类别 id 会被插值成小数后面算交叉熵直接报错。水平翻转对图像和掩码同时做保证空间对应关系不变。归一化用 ImageNet 的均值和标准差因为编码器是 VGG 预训练权重输入分布最好对齐。参数说明size建议设成 32 的倍数(352, 480)是一个比较稳的选择显存占用和精度平衡得不错。如果显存只有 6GB可以降到(256, 352)。num_classes根据你的数据集来二分类就是 2多分类就改成实际类别数。3.2 损失函数与优化器配置分割任务最常用的损失是交叉熵PyTorch 里用nn.CrossEntropyLoss它内部会做 softmax所以模型输出直接给 logits 就行不要在模型里加 softmax。如果类别极度不平衡比如背景占 90% 以上可以给weight参数传一个按类别频率倒数算出来的权重张量或者换成 Dice Loss、Focal Loss。我一般会先用交叉熵跑一版 baseline看每个类别的 IoU再决定要不要换损失。优化器用 Adam 或者 SGD 都行。Adam 收敛快适合快速验证SGD 加 momentum 泛化稍好适合最终刷指标。学习率初始设 1e-3Adam或者 1e-2SGD配合StepLR或者CosineAnnealingLR衰减。batch size 在 8 到 16 之间取决于显存。import torch.optim as optim from torch.optim.lr_scheduler import CosineAnnealingLR model SegNet(num_classes2).cuda() criterion nn.CrossEntropyLoss(ignore_index255) # 255 是忽略像素 optimizer optim.Adam(model.parameters(), lr1e-3, weight_decay1e-4) scheduler CosineAnnealingLR(optimizer, T_max50)逻辑说明ignore_index255用来忽略掩码里标记为“不确定”或者“边界”的像素这些像素不参与 loss 计算能避免模型被噪声标签带偏。weight_decay加一点 L2 正则防止过拟合。CosineAnnealingLR的T_max设成总 epoch 数学习率会从 1e-3 平滑降到接近 0。参数说明ignore_index要和你的掩码标注约定一致如果掩码里没有 255 这个值可以去掉这个参数。weight_decay一般设 1e-4 到 1e-5太大模型欠拟合太小正则效果不明显。3.3 训练循环与验证指标训练循环的骨架很固定前向、算 loss、反向、更新。每个 epoch 结束后在验证集上算 mIoU 和像素准确率。mIoU 是分割任务最核心的指标计算方式是每个类别的交并比取平均。def train_one_epoch(model, loader, optimizer, criterion, device): model.train() total_loss 0 for imgs, masks in loader: imgs, masks imgs.to(device), masks.to(device) optimizer.zero_grad() outputs model(imgs) loss criterion(outputs, masks) loss.backward() optimizer.step() total_loss loss.item() return total_loss / len(loader) torch.no_grad() def evaluate(model, loader, num_classes, device): model.eval() hist torch.zeros(num_classes, num_classes) for imgs, masks in loader: imgs, masks imgs.to(device), masks.to(device) outputs model(imgs) preds outputs.argmax(dim1) # 混淆矩阵统计 for t, p in zip(masks.view(-1), preds.view(-1)): hist[t.long(), p.long()] 1 iou torch.diag(hist) / (hist.sum(1) hist.sum(0) - torch.diag(hist)) return iou.nanmean().item()逻辑说明argmax(dim1)把每个像素的类别概率转成类别 id。混淆矩阵hist的行是真实类别列是预测类别对角线是预测正确的像素数。IoU 用diag / (行和 列和 - 对角)算nanmean忽略没有出现的类别。参数说明num_classes要和模型输出通道一致。如果某个类别在验证集里一个像素都没有它的 IoU 会是 nannanmean会自动跳过。验证时记得model.eval()和torch.no_grad()省显存也省时间。4. 推理、可视化与模型导出把结果变成能交差的图4.1 单张图像推理与掩码上色训练完之后最直观的验证方式就是拿几张测试图跑一遍把预测掩码上色后和原图并排显示。上色可以用一个固定的颜色表每个类别对应一个 RGB 值。import numpy as np import matplotlib.pyplot as plt def colorize_mask(mask, palette): h, w mask.shape color np.zeros((h, w, 3), dtypenp.uint8) for cls_id, rgb in enumerate(palette): color[mask cls_id] rgb return color palette [(0, 0, 0), (255, 0, 0), (0, 255, 0), (0, 0, 255)] model.eval() img, _ dataset[0] with torch.no_grad(): pred model(img.unsqueeze(0).cuda()).argmax(1).squeeze().cpu().numpy() colored colorize_mask(pred, palette) plt.imshow(colored) plt.savefig(pred.png)逻辑说明palette的长度等于类别数索引就是类别 id。colorize_mask用布尔索引批量上色比逐像素循环快很多。推理时unsqueeze(0)加一个 batch 维度因为模型 forward 期望 4D 输入。参数说明palette的颜色可以自己定但建议背景用黑色目标类别用高对比度颜色方便肉眼检查。如果类别多可以用matplotlib的tab20色表生成。4.2 导出 ONNX 与 TorchScript如果作业要求部署或者跨平台推理可以把模型导出成 ONNX 或者 TorchScript。ONNX 的好处是可以用 ONNX Runtime 在 CPU 上跑不依赖 PyTorch 环境。dummy torch.randn(1, 3, 352, 480).cuda() torch.onnx.export(model, dummy, segnet.onnx, input_names[input], output_names[output], opset_version11, dynamic_axes{input: {0: batch}})逻辑说明dummy是一个示例输入用来追踪计算图。opset_version11兼容性比较好dynamic_axes把 batch 维度设成动态导出后的模型可以接受任意 batch size。参数说明如果模型里有MaxUnpool2dONNX 对它的支持在 opset 11 之后才完善所以不要用太低的版本。导出前确保模型在 eval 模式否则 BatchNorm 的统计量会不对。5. 避坑与排查SegNet 训练里最容易翻车的五个地方5.1 现象loss 一直是 nan训练几个 step 就崩原因学习率太大或者输入没有归一化导致梯度爆炸。SegNet 编码器是 VGG 结构对输入尺度很敏感如果图像像素值还是 0 到 255第一层卷积输出会非常大。解决确认ToTensor()把像素值压到 0 到 1再做 normalize。学习率从 1e-4 开始试如果还 nan加梯度裁剪torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm1.0)。5.2 现象mIoU 一直卡在 0.2 左右上不去原因掩码的类别 id 没对齐或者ignore_index设错了。比如掩码里背景是 0、目标是 255但ignore_index设成了 0结果背景全被忽略模型只学目标IoU 自然低。解决用numpy.unique打印掩码里所有出现的像素值确认类别 id 范围。ignore_index只设成真正需要忽略的值不要误伤背景。5.3 现象解码器上采样后尺寸和编码器对不上报形状错误原因输入图像尺寸不能被 32 整除五次池化后出现奇数尺寸MaxUnpool2d恢复出来的尺寸和编码器池化前差一个像素。解决在 Dataset 里强制 resize 到 32 的倍数比如 352×480、384×512。如果必须保持原尺寸在解码器最后加F.interpolate拉回原图但要注意这会影响边界精度。5.4 现象验证集 loss 比训练集低很多但 mIoU 也很低原因验证集的掩码里有很多ignore_index像素导致 loss 被低估但实际预测的像素很少IoU 自然低。这种情况常见于边界标注很粗的数据集。解决检查验证集掩码的ignore_index比例如果超过 20%说明标注质量有问题要么重新标注要么在 loss 里给有效像素加权。5.5 现象显存不够batch size 降到 2 还是 OOM原因SegNet 编码器最大通道 512解码器对称中间特征图在 352×480 输入下占用不小。如果还开了return_indices索引张量也要占显存。解决把输入尺寸降到 256×352或者把编码器前两个 stage 的通道数减半。也可以用混合精度训练torch.cuda.amp显存能省 30% 到 40%速度还快一点。from torch.cuda.amp import autocast, GradScaler scaler GradScaler() with autocast(): outputs model(imgs) loss criterion(outputs, masks) scaler.scale(loss).backward() scaler.step(optimizer) scaler.update()逻辑说明autocast自动把部分运算转成 float16GradScaler防止梯度下溢。这套组合在 6GB 显存的卡上能把 batch size 从 2 提到 6 左右。参数说明混合精度训练对MaxUnpool2d的支持没问题但CrossEntropyLoss在 autocast 下会自动转回 float32不用手动改。6. 把 SegNet 用到自己的数据上迁移学习与类别不平衡处理拿到这份源码之后最实际的用法不是从头训练而是加载 VGG16 预训练权重做迁移学习。SegNet 编码器结构和 VGG16 前 13 层完全一致可以直接把torchvision.models.vgg16(pretrainedTrue)的 features 部分权重拷过来。这样即使你的数据集只有几百张图也能在 30 个 epoch 内收敛到一个可用的 IoU。import torchvision.models as models vgg models.vgg16(pretrainedTrue) encoder_dict model.encoder.state_dict() vgg_features vgg.features.state_dict() # 只拷贝编码器里卷积层的权重 pretrained_dict {k: v for k, v in vgg_features.items() if k in encoder_dict and weight in k or bias in k} encoder_dict.update(pretrained_dict) model.encoder.load_state_dict(encoder_dict)逻辑说明vgg.features里包含卷积层和池化层但 SegNet 的编码器把池化单独拆出来了所以只拷贝卷积和 BN 的权重。strictFalse可以避免键名不匹配报错但这里手动过滤更稳。参数说明pretrainedTrue会下载 ImageNet 权重第一次运行需要联网。如果环境不能联网可以提前把权重文件下好放到~/.cache/torch/hub/checkpoints/。类别不平衡是分割任务里另一个绕不开的问题。医学图像分割里病灶往往只占几个像素道路分割里车道线也很细。除了给CrossEntropyLoss加权重还可以用 Dice Loss 或者 Tversky Loss。Dice Loss 直接优化预测和真实掩码的重叠度对小目标更友好。class DiceLoss(nn.Module): def __init__(self, smooth1.0): super().__init__() self.smooth smooth def forward(self, logits, targets): probs torch.softmax(logits, dim1) targets_onehot torch.nn.functional.one_hot( targets, num_classesprobs.shape[1]).permute(0, 3, 1, 2).float() intersection (probs * targets_onehot).sum(dim(0, 2, 3)) union probs.sum(dim(0, 2, 3)) targets_onehot.sum(dim(0, 2, 3)) dice (2 * intersection self.smooth) / (union self.smooth) return 1 - dice.mean()逻辑说明one_hot把掩码转成和 probs 同形状的 one-hot 张量permute把类别维挪到通道维。intersection和union按类别求和smooth防止除零。最终返回1 - dice作为 loss。参数说明smooth一般设 1.0太小对空类别没效果太大 loss 会被平滑掉。Dice Loss 可以和交叉熵按 0.5:0.5 加权组合收敛更稳。我自己的习惯是每次拿到一份新的分割源码先不急着改模型而是用一张图跑一遍前向把每一层的输出尺寸打印出来确认编码器和解码器能对上。然后再用 10 张图过拟合一遍如果 loss 能降到接近 0说明模型和损失函数没问题剩下的就是调数据和超参。这套流程帮我省掉了很多次盲目调参的时间。从那以后我每次跑新数据集都强制走一遍“单图前向 小样本过拟合”希望帮到你。本文还有配套的精品资源点击获取
网站建设高端定制企业官网
RELATED

相关资讯

更多精彩内容,欢迎继续阅读

较早相关资讯

最新相关资讯

文章02_终稿 2026/9/27 5:57:29

文章02_终稿

为什么 GitHub Copilot 对我们这行没用 AI发展神速,我也来试试AI能在我的工作中帮我做什么。我让AI帮我写一个串口打印调试信息的功能,AI直接插入到接收数据的中断中,结果可想而知,板子不断重启。20多年工业软件从业经历告诉我&am…

阅读更多 →
我用华为云码道 CodeArts 做了个秘钥体检器 2026/9/27 5:57:23

我用华为云码道 CodeArts 做了个秘钥体检器

提交代码前,先查一遍密钥:我用华为云码道 CodeArts 做了个查一下 一键开通华为云码道 CodeArts 代码智能体 一个前端项目做完,页面跑通以后,往往还要整理源码:提交到仓库,发给同事参考,或者作…

阅读更多 →
webpack 模块提取 2026/9/27 5:57:23

webpack 模块提取

保姆级教程:用 AST BFS 精准提取 webpack 模块依赖闭包 摘要:当你手里有一个被 webpack 打包、还做了代码混淆 / 拆 chunk 的前端产物,只想单独运行或逆向分析其中某个入口模块时,面对动辄几千上万个模块该怎么办?本文…

阅读更多 →
AgentENV部署指南:用Docker单机模式最快部署你的沙箱服务器 2026/9/27 5:57:16

AgentENV部署指南:用Docker单机模式最快部署你的沙箱服务器

AgentENV部署指南:用Docker单机模式最快部署你的沙箱服务器 【免费下载链接】AgentENV AgentENV (AENV) is a distributed platform for running agent environments at scale. 项目地址: https://gitcode.com/gh_mirrors/age/AgentENV AgentENV(…

阅读更多 →
5年建站老手揭秘:从零搭建wordpress目录层级避坑指南 2026/9/27 5:57:10

5年建站老手揭秘:从零搭建wordpress目录层级避坑指南

5年建站老手揭秘:从零搭建wordpress目录层级避坑指南 找建站公司怕被坑高价,这大概是每个准备上线官网或商城的老板最真实的焦虑。别急着骂街,我也被坑过。早期为了省钱,找个小工作室,结果上线三个月,网站打开速度像蜗牛,SEO排名直接掉到…

阅读更多 →
手机网页模板避坑指南:5个维度对比评测教你挑出高转化方案 2026/9/27 5:56:58

手机网页模板避坑指南:5个维度对比评测教你挑出高转化方案

手机网页模板避坑指南:5个维度对比评测教你挑出高转化方案 别再把“模板网站太丑不够用”挂在嘴边了,那是三年前的话。现在你挑的模板,如果首页加载超过2秒,用户手指还没滑完Banner,流量就漏光了。很多老板觉得模板只是换个皮,其实那是自欺欺人…

阅读更多 →

今日资讯

本周资讯

本月资讯

看完文章仍有疑问?

联系尧图顾问,获取一对一建站咨询

立即免费咨询 📞 400-888-8888
📞 ✉