简介这是一份基于深度学习方法的图像分割项目资源覆盖语义分割、实例分割与全景分割三大任务面向计算机、人工智能、通信工程等专业的学生与开发者可用于毕设、课设或进阶学习。压缩包共5个文件以3个Markdown说明文档为主包含1个Python脚本与1个.pro工程文件整体仅11KB轻量但核心实现完整。文档对项目思路与代码结构进行梳理Python脚本负责标签图转单通道等预处理工程文件则便于快速打开与调试。代码源自个人毕设经测试可正常运行并配套CCF遥感图像分割、RSSRAI 2019语义分割等场景的赛题方案适合作为图像分割方向入门与论文复现的参考。已有206人学习对需要快速理解深度分割网络落地流程的读者而言是一份高性价比的实操资料。1. 一套代码吃透图像分割的三种玩法语义、实例到全景的完整链路语义分割、实例分割、全景分割看起来是同一件事的三个难度档位实际上标签格式、损失函数、评测指标完全是三套体系。这份 Python 毕设源码包用深度学习方法把三种分割方式全部跑通覆盖了从遥感影像标注预处理、模型训练到推理可视化的完整流程还带上了 CCF 遥感影像赛道和 RSSRAI 2019 语义分割竞赛的工程目录属于那种理论框架和工程代码能对得上号的资源。适合正在做毕设、课程设计或者想找一个能直接改的工程骨架的从业者。代码是测试跑通后才打包的下完先看根目录 README.md里面写了运行顺序和环境依赖按顺序走不会卡在结构性问题上。2. 三种分割的边界标签粒度、网络结构与源码目录对照先把概念对齐后面看代码才不会晕。这三类任务经常被笼统叫作“图像分割”但工程上的差异远比名字差异大。语义分割只回答“这个像素是什么类别”实例分割还要回答“这是第几个该类别的对象”全景分割则要求两者同时输出而且每个像素只能有一个归属。下面按输出粒度和网络结构拆开讲。2.1 语义分割输出一张单通道类别图一个 CNN head 就能完成语义分割的输出是一张 H x W 的整数矩阵每个像素存的是类别 id。类别标签只有语义不区分个体。街景图里三辆车挨在一起它们在语义分割里的标签都是 class2不会拆成三个对象。训练 loss 用 CrossEntropyLoss逐像素算分类损失显存消耗相对可控。网络结构视角下最简做法是 encoder-decoder 结构。FCN 是鼻祖U-Net 通过跳跃连接把浅层细节接回来DeepLabV3 用空洞卷积扩大感受野这三代模型在遥感分割里都还很能打。这个包里对应的就是 rssrai2019_semantic_segmentation 目录数据是航拍和遥感影像的像素级分类工程里的数据加载、类别映射、训练脚本都是围绕单通道 int mask 设计的。值得注意的一点语义分割是最容易跑通、也最容易“假跑通”的任务。因为指标 mIoU 对类别数量不敏感背景占大头时数字会很好看但细看某个小类别可能完全没学出来。这也是后面避坑章要重点展开的地方。2.2 实例分割类别之外还要给每个目标编号网络必须分两阶段实例分割只对“可数的东西”thing 类比如车、人、房子做分割同一类别的每个独立目标都要单独输出一套 mask。它和语义分割的根本区别在于引入了分组机制——模型必须能判断“这辆车和那辆车是两辆车”。工程实现上最成熟的方案是两阶段检测器的思路。第一阶段用 RPN 或者检测头提出候选框第二阶段在每个候选框内做二值分割典型代表是 Mask R-CNNRPN 出框ROI Align 把框内特征对齐到固定尺寸再挂一个 mask head 输出逐像素的前景背景。这套流程的 loss 也不再是单个 CE而是分类 loss、框回归 loss、mask loss 的加权和评测指标也从 mIoU 换成 AP平均精度。为什么不能直接用语义分割的 head 改因为纯像素分类没有“编号”机制。一个像素知道自己是一辆车但不知道是哪辆车。要硬改就得给每个像素加 embedding 向量再做聚类工程稳定性远不如两阶段检测来得靠谱。这两年 YOLO 系列也能做实例分割但它走的是框加轮廓回归的路线原理依然和语义分割的像素分类完全不同。2.3 全景分割stuff 和 thing 同时覆盖后融合是关键全景分割把背景类stuff比如天空、草地、路面和前景类thing全部输出每个像素最终只有一个 (semantic_id, instance_id) 组合。对 stuff 部分直接用语义分割那套对 thing 部分用实例分割那套最后做融合排序。融合阶段的常见做法是按置信度和空间位置把两路结果合并冲突时优先保留实例分支再填充 stuff 分支的剩余像素。评测指标用 PQPanoptic Quality它同时计算识别质量和分割质量比单独看 mIoU 或 AP 都严格。从源码落地角度看全景分割不是一个独立 head而是语义分支和实例分支的叠加。所以常规工程顺序是先把前两类跑通全景分割只是改后处理逻辑的事。这也是这份资源把三样东西打包在一起的结构原因。2.4 资源包目录对照一段路径对应一类任务资源包目录对应任务主要内容rssrai2019_semantic_segmentation语义分割竞赛训练脚本、单通道标签、类别映射配置ccf-remote-image-segmentation遥感影像分割CCF 赛道的工程代码与数据组织贴近对象级分割rssrai2019_preproc.pro数据预处理QGIS 工程文件用于影像合成、查看与分幅label_to_singlechannel.py标签预处理RGB 标签转单通道 int mask 的关键脚本README.md说明文档运行顺序、环境依赖、目录解释从上往下捋预处理脚本在最底层数据文件在中间语义分割和遥感赛道在顶层这个顺序本身也对应了实际实验流程。第三节就从预处理脚本开始拆。3. 数据预处理第一关RGB 标签转单通道 Mask 与遥感分幅实战数据预处理决定训练能不能跑下去。这个包里最关键的第一步不是训练脚本而是 label_to_singlechannel.py。竞赛平台给的标注往往是一张可视化后的 RGB 图每种颜色代表一个类别但模型训练用的标签必须是单通道的整数图这一步不做对后面所有环节都会连锁出错而且报错信息通常很隐蔽。3.1 为什么必须从 RGB 标签转到单通道索引CrossEntropyLoss 不认 RGB 标签。它把 target 当作 category index期望输入维度是 [N, H, W]像素值范围是 0 到 C-1C 为类别数。直接把 RGB 标签喂进去常见现象是报 shape 错误因为你的 target 是 [N, H, W, 3]。更隐蔽的情况是某些封装好的代码会隐式把 RGB 压成一个维度的整数但出来的像素值可能是几十上百的乱序值比如纯红 (255, 0, 0) 被压成一个很大的数。模型会学到一套和语义完全无关的映射loss 还能降但预测结果一团糟。这类问题在医学图像分割、自制 VOC 风格数据集里同样常见——换数据集只需要换颜色映射表处理逻辑完全一致。3.2 label_to_singlechannel.py映射逻辑与参数说明import os import numpy as np from PIL import Image # 类别颜色映射表每种 RGB 颜色对应一个类别 id # 0 一定是背景其余类别按标注约定排列 CLASS_COLORS { (0, 0, 0): 0, # 背景 (255, 255, 255): 1, # 建筑物 (255, 0, 0): 2, # 道路 (0, 255, 0): 3, # 植被 (0, 0, 255): 4, # 水体 } def convert_label(img_path, out_path): # 以 RGB 模式读取确保通道排布稳定 img np.asarray(Image.open(img_path).convert(RGB)) h, w, _ img.shape # 初始化全零的类别图默认所有像素先归为背景 label np.zeros((h, w), dtypenp.uint8) for rgb, cls_id in CLASS_COLORS.items(): # 逐个匹配每种 RGB 颜色命中位置写入对应类别 id mask (img rgb).all(axis-1) label[mask] cls_id Image.fromarray(label).save(out_path)逻辑说明(img rgb).all(axis-1)这行是核心它要求 R、G、B 三个通道同时相等才算命中all(axis-1)缺一不可。如果用any纯蓝的像素会被红色误伤。执行完一轮后整张图里每个像素只会落在一个类别的桶里。参数说明img_path是原始 RGB 标签路径out_path是输出的单通道 mask 路径CLASS_COLORS字典必须和标注方给的颜色约定严格一致少写或多写一个键值对都会让部分像素掉进背景。转换完成后建议先打印np.unique(label)确认最大类别 id 等于类别数减一再进训练流程。3.3 QGIS 工程文件的用处预处理阶段的可视化帮手rssrai2019_preproc.pro 是可以用 QGIS 打开的工程文件后缀.pro暴露了它的 GIS 工具属性。遥感分割竞赛的原始数据经常是多光谱影像或者坐标未对齐的 TIF直接拿图看和对标注非常吃力这时候就需要桌面 GIS 工具做可视化检查和导出。常见的预处理流程是这三步先在 QGIS 里叠加多光谱波段做真彩色合成把近红外、红、绿波段映射到 R/G/B然后把标注图层叠在原图上肉眼检查地物边界和标注边缘是不是对得上最后用栅格导出的方式把合成结果切成 PNG 分幅供后续训练脚本读取。注意导出时固定坐标系和输出分辨率否则图像和 mask 会错位。如果不想打开图形界面也可以直接用 GDAL 的命令行工具做转换。像是用一条gdal_translate做波段合成和缩放gdal_translate -ot Byte -scale -outsize 1024 1024 input.tif output.png参数说明-ot Byte把输出类型压成 8 位整型-scale做自动拉伸增强对比度-outsize控制输出尺寸。注意这条命令的-scale只是显示层面的拉伸不要把它当成数据增强手段。处理完一定要随机抽几张图叠 label 肉眼确认这一步省不了。3.4 分幅裁剪大图直接进网络会 OOM遥感影像动辄几千乘几千直接塞进 ResNet 骨干必爆显存所以分幅是遥感分割绕不开的步骤。裁剪的原则很简单原图和标签用同一个裁剪框像素严格对齐。from PIL import Image # 遥感原图往往 6000x6000 以上直接送进 ResNet 必爆显存 src Image.open(scene_001.tif).convert(RGB) mask Image.open(scene_001_label.png) patch_size 512 step 512 # 无重叠滑窗想要重叠样本就让 step patch_size idx 0 for top in range(0, src.height - patch_size 1, step): for left in range(0, src.width - patch_size 1, step): box (left, top, left patch_size, top patch_size) src_patch src.crop(box) mask_patch mask.crop(box) # 原图和标签共用一个 box保证像素严格对齐 src_patch.save(fpatches/img_{idx:05d}.png) mask_patch.save(fpatches/label_{idx:05d}.png) idx 1逻辑说明crop(box)的 box 是 (left, top, right, bottom)原图和标签的 box 必须完全一致这个细节决定了标签会不会歪。step patch_size时是无重叠滑窗step小于patch_size会产出重叠样本增加数据量但也会让模型重复看到同一区域。参数说明patch_size一般取 512 或 256越大感受野越好但显存开销按平方增长输出文件名用 0 填充保证按字典序排序时顺序和空间位置一致。4. 训练与推理配置从 Dataset 到可视化验证的一站式脚本数据准备好之后进入训练环节。这一节给出一套跑通语义分割的最小配置按这个顺序改参数比直接拿大模型开跑要稳得多。重点放在三个位置数据读取的 dtype 和通道顺序、loss 和 lr 调度的取舍、推理脚本里反查颜色的逻辑。4.1 数据读取与同步增强image 和 mask 必须用同一套随机变换import os import numpy as np import torch from torch.utils.data import Dataset from PIL import Image import albumentations as A class SegDataset(Dataset): def __init__(self, img_dir, mask_dir, transformNone): self.img_dir img_dir self.mask_dir mask_dir self.names sorted(os.listdir(img_dir)) self.transform transform def __len__(self): return len(self.names) def __getitem__(self, i): img Image.open(os.path.join(self.img_dir, self.names[i])) # 标签文件名与原图一一对应这里假设命名规则一致 mask Image.open(os.path.join(self.mask_dir, self.names[i].replace(img, label))) if self.transform: aug self.transform(imagenp.array(img), masknp.array(mask)) img, mask aug[image], aug[mask] # 图像转 CHW float标签保持单通道 int img torch.from_numpy(img).permute(2, 0, 1).float() / 255.0 mask torch.from_numpy(mask).long() return img, mask # 同步增强image 和 mask 配对使用几何变换必须同时作用在两者上 transform A.Compose([ A.RandomCrop(512, 512), A.HorizontalFlip(p0.5), A.RandomBrightnessContrast(p0.2), ])逻辑说明permute(2, 0, 1)是把 HWC 转成 CHW这是 PyTorch 卷积层的默认输入格式float() / 255.0把像素值压到 0~1mask.long()是 CrossEntropyLoss 的硬性要求它需要 int64 类型的 target。增强部分用的是 albumentations 的双参数接口传image和mask两个关键字它才会同步执行同一个几何变换否则裁剪和翻转只作用在图上、标签错位训练时 loss 会莫名奇妙地震荡。参数说明RandomCrop(512, 512)是和前面分幅尺寸配套的如果你的训练显存吃紧先缩到 256 而不是直接降 batch分割任务对分辨率敏感HorizontalFlip对遥感语义分割基本是白嫖涨点RandomBrightnessContrast只改图像亮度标签不受影响但写进 Compose 里它仍然会自动同步。使用预训练 backbone 时注意归一化方式要和预训练统计一致有些实现要在除法之后再减均值、除标准差具体看权重说明。4.2 训练配置loss 主导收敛方向lr 调度决定上限配置项常用值说明网络DeepLabV3 / U-Net毕设一般用 ResNet50 主干的 DeepLabV3显存不够就换 MobileNetlossCrossEntropy Dice两类 loss 加权Dice 对类别不平衡更友好优化器SGDmomentum0.9Adam 也能用但分割场景里 SGD 收敛更稳初始学习率1e-3 量级必须配合 batch size 调整见下方说明学习率调度poly 衰减lr 乘 (1 - iter/total)^0.9经验上比 step 衰减稳定epoch60~100验证集指标不再涨就停不要盲目加长batch size4~16尽量不低于 4否则 BN 统计不稳定Dice loss 的公式可以写成 1 - (2|X∩Y|)/(|X||Y|)它按类别逐个计算两片区域的重叠程度对小类别更宽容。遥感分割里背景占比经常超过 80%纯 CrossEntropy 会让模型疯狂学背景叠加 Dice loss 是常见解法。初始化学习率的经验法则是跟着 batch 走batch 减半lr 也要减半否则小 batch 下梯度噪声大同样的 lr 会明显震荡。lr 调度这一段经常被忽略。分割任务不是前几十轮出效果而是靠后段的精细微调拉指标。poly 衰减的做法是让 lr 随训练进度平滑趋向 0而不是每隔固定 epoch 掉一截能显著减少后期在局部最小值附近的来回跳动。4.3 推理与可视化验证先数类别再调颜色最后才看 mIoUmodel.eval() with torch.no_grad(): logits model(img_tensor) # (1, num_classes, H, W) pred logits.argmax(dim1).squeeze() # 取概率最大的类别作为预测 pred pred.cpu().numpy().astype(np.uint8) # 单通道 pred 直接保存是一张黑图必须先反查颜色表 color_map {v: k for k, v in CLASS_COLORS.items()} rgb np.zeros((*pred.shape, 3), dtypenp.uint8) for cls_id, color in color_map.items(): rgb[pred cls_id] color Image.fromarray(rgb).save(pred.png)逻辑说明argmax(dim1)在类别维度上取最大概率对应的索引输出形状从 [N, C, H, W] 变成 [N, H, W]squeeze()再去掉 batch 维度。注意不能对logits直接argmax()那样会把通道维当空间维一起卷进去出来的图全是乱码。预测结果的类别 id 是 0 到 C-1 的整数直接保存成 PNG 只会看到全黑必须先反查CLASS_COLORS恢复成 RGB 才能肉眼验证。参数说明color_map是第三节里CLASS_COLORS的键值反转两次用到同一个字典务必保持定义一致。mIoU 指标的计算发生在颜色反查之前拿 RGB 图去算 IoU 属于典型的指标误用。推理阶段要同时打印np.unique(pred)确认预测覆盖了所有类别再谈指标高低。5. 避坑指南图像分割毕设最常踩的五个坑分割项目的坑和普通分类不太一样多数不是模型不收敛而是数据格式、指标误读、显存管理这类看起来很小、排查起来要命的问题。下面五条是这个资源场景里最容易翻车的点按现象、原因、解决三个步骤写。5.1 现象CrossEntropyLoss 报 target out of bounds训练刚启动就抛错提示 target 里的值超出了类别数范围或者报维度不匹配。用一条命令检查标签图python -c from PIL import Image; import numpy as np; mnp.array(Image.open(label.png)); print(np.unique(m))原因np.unique(m)打印出来的最大值是 255说明喂进去的根本不是单通道 int label而是 RGB 可视化标签。RGB 图的通道维还在CrossEntropyLoss 把每个通道当成了一个样本去算维度对不上就报错。解决重跑 label_to_singlechannel.py转完再执行一次上面的检查命令确认最大值等于类别数减一再进 DataLoader。5.2 现象mIoU 涨到 90%但关键类别全丢训练曲线很漂亮验证集 mIoU 也很高但可视化结果里目标类别几乎没有全是背景。翻车点在于遥感数据里背景占比极大mIoU 按类别取平均可模型优化的是逐像素损失背景像素多梯度也大模型自然优先学背景。解决给 CrossEntropyLoss 加 class weight按各类别像素频率的倒数设置权重或者直接叠加 Dice loss 分支。判断标准很简单看每个类别的 IoU而不是看整体 mIoU。占比最小的那个类别通常就是预测最差的。5.3 现象验证阶段显存溢出而训练没爆训练能跑一到验证就 OOM。这是最容易忽略的一处验证阶段忘记关梯度计算模型依然在前向传播的同时构建计算图显存占用和训练时一样大。解决验证循环套上with torch.no_grad():同时写上model.eval()。前者关掉梯度图后者切换 BN 和 Dropout 的运行模式。只写 eval 不写 no_grad照样爆显存。5.4 现象pred 存出来是一张黑图或者颜色和标签对不上推理脚本跑完保存的 prediction 全黑。原因很单一预测结果是单通道整数图图像查看器默认把它当成全黑图像显示因为像素值只有 0、1、2 这种小数字。另一种情况是颜色对不上比如草地和水体反了那是CLASS_COLORS字典里的 RGB 值和原始标注定义不一致。解决保存前先np.unique(pred)确认类别 id 覆盖完整再用反查颜色表恢复 RGB。检查颜色表时重点核对背景 (0,0,0) 和纯白 (255,255,255) 这两项它们最容易写反。5.5 现象batch 调小后同样的学习率模型不收敛显存不够把 batch size 从 16 降到 4其他参数照旧结果 loss 开始震荡或者收敛极慢。原因学习率是按 batch 匹配的batch 减小意味着梯度估计噪声变大同样的 lr 会让参数更新步子过大而且 BN 层的统计量在小 batch 下也变得更不稳定。解决batch 降到 4 的话学习率同步降到原来的四分之一到二分之一如果还想维持原有 batch 效果可以用梯度累积每几步累加梯度再统一更新等效于变相放大 batch。分割任务里 batch 至少保持在 4 以上太低的话连 BN 都会开始拖后腿。6. 进阶实验从语义分割迁移到实例与全景分割的改动清单跑通语义分割之后往另外两种分割迁移的价格没有想象中高因为 backbone 和数据增强那套基础代码是可以直接复用的。最大的改动量集中在两处标签体系从单通道 mask 换成对象级标注以及 head 从纯分类头换成检测加分割的复合结构。6.1 迁移改动点与验证方法迁移方向需要改动的模块保持不变的部分语义 → 实例分割加检测头或直接换 Mask R-CNN 类模型数据增强、backbone、lr 调度策略语义 → 全景分割并行挂一个实例分支后处理做融合排序语义分支原样不动共享 backbone 特征评测指标mIoU 换成 AP 或 PQ可视化脚本只改反查表实例分割的 label 体系和语义分割完全不同单通道 int mask 不再适用要换成对象级的标注格式比如 COCO 风格的 JSON 或者每个实例一张二值 mask。如果只是做实验验证最快的路径是直接换一个 Mask R-CNN 的现成实现把 backbone 替换成你已经在用的 ResNet数据加载部分重写增强和 lr 调度原封不动搬过去。全景分割的落地方式更讨巧。语义分支跑出来的 stuff 结果保持不变实例分支用 Mask R-CNN 出 thing 结果后处理时按置信度排序先放实例掩膜、再填语义掩膜重叠区域以实例为准。这个融合逻辑看起来简单但它是全景分割最核心的一步PQ 指标好不好一半取决于这个冲突处理顺序。我最早跑这个包的时候习惯不验证直接上大模型结果后面花了一周排查一个明明是标签没转干净的错。从那以后我每次换数据集都强制走一遍“先np.unique标签、再对齐类别数、最后才调学习率”的流程。数据预处理不干净的实验再好的网络也白搭。希望帮到你。本文还有配套的精品资源点击获取