ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

深度学习图像分割实战:语义、实例与全景一网打尽

深度学习图像分割实战:语义、实例与全景一网打尽 简介这是一份基于深度学习方法的图像分割专题资料包围绕语义分割、实例分割与全景分割三大任务组织面向计算机视觉方向的在校学生、研究者及开发者也可作为相关课程设计、毕业设计或项目初期的参考实现。资源内含完整项目源码与设计资料代码均经过运行验证目录涉及遥感图像分割、语义分割竞赛及数据预处理等典型场景适合在此基础上进行二次扩展。压缩包共5个文件以Markdown文档为主用于项目说明与使用指引另含Python脚本实现标签图像单通道转换等预处理功能以及用于工程配置的pro文件压缩包仅11KB轻量且结构完整。目前已有206人学习下载整体内容虽精简但覆盖了从数据准备到模型实验的主要环节。可为入门图像分割的同学提供清晰的实践路径。1. 基于深度学习的图像分割语义、实例与全景一包打尽搞计算机视觉的都知道图像分割是比目标检测更高一级的活儿——目标检测给框分割给像素级掩码。基于深度学习的图像分割这套 Python 源码里同时整理了语义分割、实例分割、全景分割三类任务的实现还带了一个遥感影像语义分割的比赛级子项目不是只有玩具 demo。它解决的是「拿到一张图怎么让模型逐像素回答哪里是什么、哪个物体属于同一实例」的问题适合毕设选图像分割方向、课设需要完整流程、或者刚入门想找全链路项目跑通的同学。源码能跑关键是把目录结构、数据预处理和训练参数三者之间的关系捋清楚再动手改自己的数据集。2. 源码包解剖三个分割任务和遥感数据集在哪儿2.1 目录结构与文件职责解压之后先别急着跑这包的目录结构是有讲究的。核心目录是deeplearning-image-segmentation-master里面按「比赛项目 工具脚本」的方式组织我拆包第一件事就是画文件地图弄清谁依赖谁否则后面换数据时连改哪里都不知道。路径职责rssrai2019_semantic_segmentation/RSSRAI 2019 遥感语义分割训练与推理主体含模型定义、训练脚本rssrai2019_preproc.pro预处理工程文件定义了遥感影像的波段组合、裁剪窗口、归一化等参数label_to_singlechannel.py标签格式转换脚本RGB 编码的多通道标签 → 单通道类别索引ccf-remote-image-segmentation/CCF 遥感图像分割赛道的读取与适配代码处理该比赛的数据格式README.md顶层两份一份是总览运行说明一份是工具脚本的单独说明项目里放了两份 README容易被扫一眼就略过。总览那份会写清整体训练流程工具脚本那份只说明label_to_singlechannel.py的用法。我的习惯是先读 README再打开label_to_singlechannel.py看标签怎么转最后才进模型目录。原因很现实——分割的标签一旦是(H, W, 3)的 RGB 编码格式而模型输入要(H, W)的整数索引这一步错了后面所有训练指标都是假的。rssrai2019_preproc.pro这个文件最容易被当成无关配置跳过去但它恰恰是遥感分割和普通分割的最大区别。遥感影像不是随手拍的照片原始数据可能有十几个波段、几万乘几万像素直接训练既不现实也没必要。预处理工程里通常会写清取哪些波段、按什么窗口裁剪、是否做归一化。如果你拿到的是完全不熟悉的 .pro 文件先别猜用文本编辑器打开看里面的路径和参数基本能推断出数据目录在哪。README 里如果写了环境依赖先照着装没写就按第 3 章我给的稳妥组合来。2.2 三个分割任务的界定与代码落点图像分割在项目里被拆成三条线对应不同用法。语义分割给每个像素打一个类别标签不管同类物体有几个实例比如街景图里所有「汽车」像素都标为类别 6两辆挨着的车是同一个标签遥感场景就是典型建筑、道路、水体、植被逐像素分类。实例分割在语义基础上区分「这是第几辆车」输出每个物体的独立掩码加编号典型结构是检测分支 分割分支模型侧通常是 Mask R-CNN。全景分割把语义和实例统一成一个任务用 stuff背景类和 thing前景实例两套逻辑同时预测代码实现上一般是 Panoptic FPN 或 UPSNet 的思路推理时做合并后处理。这三种任务在代码里的落点往往是同一套骨干网络ResNet 之类区别在 head 和损失函数。语义分割的 head 可以简单理解为「每个像素过 softmax 分类」输出一个(B, C, H, W)的概率图C 是类别数实例分割的 head 要多接 mask 分支和 bounding box 回归全景分割再加 thing/stuff 合并逻辑最终合成一张(H, W)的标签图加实例 id 记录。从源码阅读角度我建议先只盯语义分割这一条线跑通。实例分割和全景分割的代码复杂度是语义分割的两倍以上涉及 anchor、NMS、mask 缩放这些前置知识毕设时间有限的情况下先跑语义分割拿指标再按需要扩展更务实。项目既然同时给了三条线的素材最合理用法是把语义分割当主线另外两个当进阶扩展而不是一上来三条线同时调。2.3 RSSRAI 2019 与 CCF 遥感的预处理工程RSSRAI 2019 是遥感图像语义分割的知名比赛数据特点是高分辨率遥感影像幅面很大直接整图丢进 GPU 显存必然爆所以预处理工程要解决三件事。第一是裁剪窗口把大影像切成 512×512 或 1024×1024 的 patch同时保证标签图做同样的裁剪坐标严格对齐。第二是通道选择遥感影像通常是多波段RGB 加近红外等预处理里要指定取哪几个波段、是否做波段组合。第三是数据增强翻转、旋转、尺度缩放要同时作用于影像和标签避免标签失真。ccf-remote-image-segmentation/目录则是另一套数据读取适配因为 CCF 赛道的标注格式和 RSSRAI 不完全一样里面多半是数据 loader 的差异。拿到这个项目时不要指望两边数据格式一致先看数据集内部存的是 PNG 还是 TIFF标签是 RGB 编码还是索引图再决定复用哪个目录的加载逻辑。CCF 遥感影像的标注通常更细碎类目划分更贴近实际地物改类别映射表时要特别小心多读一遍再动手。回头看这包源码的核心价值不在某一两个模型的实现而是把「遥感分割比赛的数据处理 标签转换 训练」这条完整链路放在了一起这正是很多开源项目缺的部分。想快速跑通毕设流程把这条链路的每一环吃透比单独钻研一个 SOTA 模型更有实际收益。3. 环境配置与标签预处理先把数据喂对3.1 环境依赖与版本建议环境装不对后面每一步都像在打地鼠。图像分割的老项目最容易踩的坑是 PyTorch 版本太新。这个包对应的代码年代集中在 2019 到 2020 年当时主流写法是torch.nn.functional.interpolate和torchvision.models.segmentation里的固定接口。直接装最新的 torch 2.x某些旧版算子的默认行为变了训练时会出现数值不匹配或者ATen底层报错。常见做法是装一套兼容性好的组合Python 3.7 ~ 3.9 torch 1.7 ~ 1.9 torchvision 0.8 ~ 0.10 opencv-python numpy 2.0 pillow tqdmnumpy 要特别小心1.24 之后的版本把旧代码里np.float、np.int这类别名删了跑老脚本会报AttributeError: module numpy has no attribute float。不想降级 numpy 的话就全局搜索替换成np.float64。安装命令pip install torch1.9.0 torchvision0.10.0 --index-url https://download.pytorch.org/whl/cu111 pip install opencv-python numpy2.0 pillow tqdm提示cu111表示 CUDA 11.1。先运行nvidia-smi查看驱动支持的 CUDA 版本再选对应 whl 参数不确定就装 CPU 版先跑通代码再补 GPU 加速。装完环境别急着训。先做一次导入冒烟测试import torch import torchvision import cv2 import numpy as np print(torch.__version__, torchvision.__version__, cv2.__version__, np.__version__)能正常打印版本号再进下一步。如果import cv2报 libGL 缺失Ubuntu 上执行apt-get install -y libgl1-mesa-glx或者libgl1这类系统依赖书里一般不写卡住不少新手。3.2 label_to_singlechannel.py 的转换逻辑这个脚本是整个数据链路里最关键的一环。遥感分割的标签图为了可视化方便常保存为 RGB 三通道颜色图每个颜色代表一个类别而模型训练时CrossEntropy 损失只接受单通道类别索引0, 1, 2, ...所以必须转换。常见实现逻辑如下import numpy as np from PIL import Image # 类别到 RGB 颜色的映射需根据数据集的 label 定义修改 CLASS_COLORS { 0: (0, 0, 0), # 背景 1: (255, 0, 0), # 建筑 2: (0, 255, 0), # 道路 3: (0, 0, 255), # 水体 } def rgb_to_singlechannel(rgb_path, out_path): img np.array(Image.open(rgb_path).convert(RGB)) h, w img.shape[:2] label np.zeros((h, w), dtypenp.uint8) color_to_label {v: k for k, v in CLASS_COLORS.items()} for color, cls_id in color_to_label.items(): mask (img np.array(color)).all(axis2) label[mask] cls_id Image.fromarray(label).save(out_path) import glob for rgb_path in glob.glob(train_labels/*.png): out rgb_path.replace(train_labels, train_labels_single) rgb_to_singlechannel(rgb_path, out)这段代码的核心思想是「颜色字典反转」把原标签里等于某一 RGB 值的像素统一写成对应的类别整数。几个细节值得注意反查表写法直接把CLASS_COLORS反转成color - cls_id避免一层层 if/elif。但前提是数据集里没有两个类别共用同一 RGB。dtype 用 uint8类别数一般不超过 255省显存也兼容 PNG 压缩。未匹配像素默认归 0标签图里存在调色板之外的像素时会被静默当成背景这是最常见的数据 bug。转换完记得做一次可视化抽查把单通道标签再映射回颜色图与原 RGB 标签并排对比确认类别没串。3.3 影像裁剪与训练样本的生成把大图切成 patch同时要保证标签不偏移。常见做法是程序化裁剪import cv2 import numpy as np def crop_pair(image, label, size512, stride256): h, w image.shape[:2] patches [] for y in range(0, h - size 1, stride): for x in range(0, w - size 1, stride): img_patch image[y:ysize, x:xsize] lab_patch label[y:ysize, x:xsize] patches.append((img_patch, lab_patch)) return patches参数size512是裁剪窗口stride256是滑动步长。步长小于窗口会产生重叠增加样本量边界上下文更丰富缺点是数据膨胀。遥感语义分割我一般让 stride 取 size 的一半到三分之二显存允许就开 size1024感受野大对小目标更友好。裁剪完做一次格式校验python -c import glob import cv2 imgs sorted(glob.glob(patches/img/*.png)) labs sorted(glob.glob(patches/lab/*.png)) assert len(imgs) len(labs), (len(imgs), len(labs)) for a, b in zip(imgs, labs): ia cv2.imread(a) lb cv2.imread(b) assert ia.shape[:2] lb.shape[:2], (a, b) print(all ok) 这个校验值得养成习惯。图对上了但尺寸不一致训练时torch.stack会直接报坐标越界而且错误信息很难一眼定位到数据。数据增强也是预处理的一部分遥感场景下常用随机水平翻转、90 度旋转倍数、随机亮度扰动和少量高斯噪声。增强时图与标签必须执行完全相同的随机变换常见做法是用一个随机种子同时控制两个变换对象避免图转了标签没转。4. 训练实战模型选择、损失函数与关键参数4.1 模型脚本里常见的三种结构数据链路确定没问题之后才轮到训练脚本上场。在rssrai2019_semantic_segmentation目录下模型代码一般围绕 FCN、U-Net、DeepLab 这三类展开。FCN全卷积网络把 VGG/ResNet 最后的全连接换成卷积上采样用转置卷积。作为理解的起点代码量小适合快速跑通。U-Net编码器-解码器 跳跃连接对边缘细节保留好在遥感小样本场景下非常实用unet 图像分割的代码结构可以参考它来理解。DeepLabV3空洞卷积 ASPP 模块多尺度信息聚合强是比赛 baseline 里效果和速度比较均衡的选择。模型脚本里通常有一个build_model()或者get_model()入口返回nn.Module。从使用角度确认三件事backbone 是 ResNet-50 还是 ResNet-101、输出类别数是否等于数据集类别数、最后的nn.Conv2d的 out_channels 有没有改对。如果项目里只有语义分割的模型定义而你想补实例分割模块常见做法是直接用 torchvision 里带 COCO 预训练权重的 Mask R-CNNimport torchvision def get_mask_rcnn(num_classes): model torchvision.models.detection.maskrcnn_resnet50_fpn( weightsCOCO_V1 ) in_features model.roi_heads.box_predictor.cls_score.in_features model.roi_heads.box_predictor ( torchvision.models.detection.faster_rcnn.FastRCNNPredictor( in_features, num_classes ) ) return modelweightsCOCO_V1会加载 COCO 预训练权重迁移学习时能省不少收敛时间注意num_classes要包含背景类比如你的数据集有 4 个前景类别这里要传 5。如果你发现项目里只有 FCN 或 DeepLab 的单模型脚本想对比 U-Net 效果不必重写训练流程——很多训练脚本把模型名作为参数在build_model里按名字返回实例保留一份 U-Net 实现把--model参数切过去就行。这种以参数切换模型的设计在比赛中很常见方便对比不同结构的 mIoU。4.2 损失函数与评价指标语义分割的默认损失是 CrossEntropyLoss但遥感数据往往类别不平衡——背景面积远大于建筑和道路所以项目里很可能加了权重import torch.nn as nn weights torch.tensor([0.1, 1.0, 2.0, 1.5]) # 按类别像素占比反比设置 criterion nn.CrossEntropyLoss(weightweights)权重怎么设先统计训练集每个类别的像素占比 p_i取w_i median(p) / p_i的近似值或者直接用经验值样本少的类别给大权重。别设得太极端比如背景 0.01、前景 100会导致训练早期梯度震荡loss 曲线来回跳。评价指标用 mIoUMean Intersection over Union比像素准确率可靠得多。计算方式def compute_miou(pred, label, num_classes): ious [] for cls in range(num_classes): p (pred cls) t (label cls) inter (p t).sum().float() union (p | t).sum().float() ious.append((inter 1e-6) / (union 1e-6)) return sum(ious) / len(ious)注意1e-6是平滑项防止某个类别在某一 batch 里完全没出现导致除零。评测时按像素统计而不是按图平均数字更接近真实效果类别不平衡严重时按图平均会被小块图带偏。4.3 训练脚本的关键参数与调优方向训练入口脚本通常长这样python train.py \ --data_root ./data/rssrai2019 \ --backbone resnet50 \ --model deeplabv3 \ --batch_size 8 \ --lr 0.001 \ --epochs 60 \ --num_classes 4参数说明batch_size显存决定。8GB 显存 512 分辨率batch_size 取 4~8训练不稳定时先减半。lr0.001 是常见起点配 poly 学习率衰减lr base_lr * (1 - iter/total_iter)^0.9效果比固定 lr 好。epochs遥感分割训练集 patch 数量大60 epoch 已经能看出模型是否收敛验证 mIoU 到后期反复横跳就做早停。训练到一半怀疑损失不降时不要急着换模型先做两步检查加载一两个训练样本手动前向看输出 shape 和数值范围再对同一个 batch 用 eval 模式试跑一次确认 loss 能正常计算。每次训练完把验证集预测结果可视化叠加回去看边缘是否贴合原图。分割模型的靠谱程度不是只看 loss 曲线而是看预测掩码叠加在原图上是否「长在该长的位置」。5. 避坑手册图像分割项目最容易翻车的五个地方图像分割的坑跟目标检测不一样检测的坑多在 anchor 和后处理分割的坑集中在数据标签和内存管理。下面五条按出现频率排每一条都有真实翻车场景。5.1 标签转换后类别丢失现象转换完的单通道标签图几乎全黑用np.unique(label)统计只剩 0 和 1模型只会输出背景。原因CLASS_COLORS配置的 RGB 值和标签图实际颜色不一致。比如标签实际是(255, 255, 0)代码里写的是(255, 0, 0)匹配不上就默认归 0。这种情况在别人分享的代码里非常常见每个数据集对同一类别的颜色定义都可能不同。解决先打印标签图真实的颜色集合再以结果为准修改字典import numpy as np from PIL import Image img np.array(Image.open(任意一张标签.png).convert(RGB)) colors np.unique(img.reshape(-1, 3), axis0) print(colors)改完重跑转换把单通道标签回渲成彩色图与原标签并排对比确认类别没有串。5.2 裁剪后影像和标签错位现象训练时 loss 正常下降但可视化预测里建筑物边缘整体偏移几个像素像套了一层错位描边指标可能还不低。原因影像和标签裁剪时边界处理不一致比如影像做了 padding 而标签没做或者读取时 OpenCV 的 BGR 与 PIL 的 RGB 混用导致通道顺序错乱还有一种是裁剪边界差一y:ysize写成y:ysize-1。解决裁剪循环里加断言assert img_patch.shape[:2] lab_patch.shape[:2]全流程统一用同一种读取方式不要在一条链路里混用 cv2.imread 和 PIL.Image进模型前强制cv2.cvtColor(img, cv2.COLOR_BGR2RGB)。5.3 显存溢出 OOM现象第一个 epoch 还没跑完就抛CUDA out of memory有时 step 0 就崩。原因patch 尺寸太大、batch_size 太大或验证阶段没关梯度。遥感影像的 patch 经常是 1024×1024 甚至四通道输入显存占用比想象中大得多。解决先torch.cuda.empty_cache()把 batch_size 降到 2patch 降到 256×256跑通一次 forward/backward 再逐步加大。验证阶段记得包with torch.no_grad():。8GB 单卡跑 1024 patchbatch_size 只能取 1~2同时关掉中间特征的保留。5.4 mIoU 低但像素准确率高现象验证集像素准确率 90% 以上mIoU 只有 0.3提交结果看起来也全是背景。原因类别不平衡。背景像素占绝大多数模型把所有像素都预测成背景准确率自然高但每个目标类别的 IoU 都是 0mIoU 直接被拉垮。解决以 mIoU 为准不要看准确率训模型。给损失函数加类别权重w_i median(p) / p_i或者用加权采样器让每个 batch 都见到小类别样本。5.5 多波段输入被当成 3 通道现象模型输入是 4 个或更多波段代码里in_channels3前向直接 shape 报错或者不报错但训练 loss 异常波动。原因nn.Conv2d的 in_channels 没改数据读取时多波段影像被直接堆成了(H, W, 4)甚至(H, W, 8)。解决改模型输入头nn.Conv2d(in_channelsN, ...)只想用三通道就在预处理固定取 RGB 三个波段确认标签图与取波段后的影像分辨率一致改完先用print(x.shape)打一次输入张量再训练。这五个坑按出现频率排数据标签问题占了三个。遇到诡异现象先怀疑预处理而不是先怀疑模型——这条经验在分割项目里尤其适用。6. 进阶技巧把项目迁移到自己的分割数据集6.1 四步移植流程项目里的遥感数据集你不可能永远用同一套。等换上自己的数据时四步移植就够了。第一步统一标签格式。自己的标签可能是 JSON 多边形、COCO 格式或 RGB 颜色图通过脚本转成单通道索引 PNG这一步直接复用项目里的label_to_singlechannel.py只需改CLASS_COLORS或加一个 JSON 转 PNG 的解析。第二步调整类别数。找到模型定义里的num_classes把损失函数权重长度改成自己的类别数。第三步改数据加载路径。项目默认读 RSSRAI 的目录结构改成images/和labels/两个文件夹文件名一一对应即可。第四步验证。拿 10 张图跑通一次训练迭代确认 loss 能下降且不为 NaN。6.2 一个常被忽视的验证技巧训练结束后不要只依赖测试指标做一个「叠加可视化的交叉验证」把预测掩码以半透明方式叠加回原图肉眼检查边缘是否贴合、小目标是否被吞。import cv2 import numpy as np def overlay_prediction(image, pred, alpha0.5): color_map np.array([ [0, 0, 0], [0, 0, 255], [0, 255, 0], [255, 0, 0] ], dtypenp.uint8) vis color_map[pred] out cv2.addWeighted(image, 1 - alpha, vis, alpha, 0) cv2.imwrite(overlay.png, out)alpha0.5是掩码透明度调到 0.3 便于观察原图细节调到 0.8 便于检查掩码边缘是否整齐。我一般生成三张图并排对比原图、单通道标签上色图、预测叠加图一眼就看明白模型在哪类目标上崩了。说实话图像分割项目我拆过不少最深的教训是别急着调模型先花一天把数据链路验明白。自从有一次因为标签颜色写错白训了三天我每次接手分割项目都强制先跑一遍「标签转换 → 裁剪 → 单 batch 前向 → 可视化叠加」四步检查再进训练这个习惯能省掉大量调参时间。项目里label_to_singlechannel.py和预处理工程文件都在照着这个流程走很快就能换上自己的数据跑起来希望帮到你。本文还有配套的精品资源点击获取
返回列表