ARTICLE DETAIL

资讯详情

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

约7000张宠物语义分割数据集:三类别标签映射与UNet训练避坑指南

约7000张宠物语义分割数据集:三类别标签映射与UNet训练避坑指南 简介本资源为面向图像分割学习者的宠物语义分割数据集约7000张图像及对应标签适合入门与进阶的深度学习实践者用于训练、验证分割模型。数据集已按训练集与验证集划分训练集约5100张图片及mask验证集约2200张图片及mask标签涵盖背景、宠物、边缘三类像素值定义可参考classes文件。压缩包共2000个文件以1998个png图像与掩膜为主另含1个txt类别说明和1个py可视化脚本整体约765.67MB采用7z格式打包。配套脚本可随机抽取一张图片展示原图、GT图像及GT在原图上的蒙板效果并保存至当前目录便于快速检查标注质量与数据分布。目前已有48人学习适合需要现成数据开展UNet、SwinUnet、TransUnet等分割网络实验的读者。1. 约 7000 张宠物语义分割数据集拿到手先别急着喂给 UNet如果你正在复现 DeepLabV3、UNet 或者 Swin-Unet却卡在「找不到一份干净、已经划分好、mask 还是像素级标签」的数据集上这份约 7000 张的宠物图像语义分割数据集值得先看一眼。它把训练集和验证集都拆好了训练集约 5100 张、验证集约 2200 张每张原图都配一张同名 mask类别只有三类背景、宠物主体、边缘。文件名像Egyptian_Mau_182.png、Bombay_140.png、Abyssinian_32.png这种一眼能看出是猫的品种命名说明它大概率是从 Oxford-IIIT Pet 那套数据整理出来的语义分割版本。它解决的核心问题不是「数据量不够」而是「省掉你自己写划分脚本、对齐文件名、处理 mask 像素值映射」这几步脏活。适合两类人一类是刚入门语义分割、想跑通一个三类别 baseline 的新手另一类是手里有医学图像分割网络UNet、TransUNet、Swin-Unet 改进版想换个数据集验证泛化性的熟手。下面按「这份资源是什么 → 怎么读标签 → 怎么接进训练 → 坑在哪 → 怎么验证」的顺序拆开讲每一步都能直接抄。2. 读懂 mask 像素值与 classes 映射三类标签到底怎么存的2.1 为什么先看 classes 文件而不是先写 DataLoader很多人拿到分割数据集第一反应是torchvision.datasets套一下就开始训结果 loss 一直不降最后发现 mask 里存的是1/2/3而不是0/1/2或者边缘类被当成了独立前景。这份数据集的类别定义是「背景、宠物、边缘」三类但像素值标签的具体数值必须参考随包的 classes 文件不能凭经验假设。语义分割里 mask 的像素值就是类别索引差一个偏移量交叉熵就会把背景学成宠物。常见做法是先用一段脚本把 mask 的唯一值统计出来和 classes 文件对照确认映射关系再动手。这一步花两分钟能省掉后面几小时的玄学调参。import numpy as np from PIL import Image import os mask_dir train/masks # 换成你的 mask 目录 classes_file classes.txt # 随包的类别定义文件 # 1. 读 classes 文件看官方给的类别顺序 with open(classes_file, r, encodingutf-8) as f: class_names [line.strip() for line in f if line.strip()] print(类别顺序:, class_names) # 2. 统计所有 mask 的像素唯一值确认实际存储的标签值 uniq set() for name in os.listdir(mask_dir)[:200]: # 抽样 200 张足够 m np.array(Image.open(os.path.join(mask_dir, name))) uniq.update(np.unique(m).tolist()) print(mask 实际像素值:, sorted(uniq))逻辑说明第一段读 classes 文件拿到官方类别顺序第二段抽样统计 mask 的真实像素值。参数上[:200]是抽样数量7000 张全扫也行只是慢np.unique返回的是该 mask 里出现过的所有类别值。如果打印出来是[1, 2, 3]而 classes 是[background, pet, edge]那训练时要么在 Dataset 里减 1要么把ignore_index和权重重新配。2.2 边缘类要不要单独算 loss三类里「边缘」这一类是最容易被忽略的。它本质上是宠物轮廓附近的过渡像素占比很小如果直接和背景、宠物一起做普通交叉熵边缘类几乎学不到因为梯度被大面积的背景和宠物主体淹没了。常见做法有两种一是给边缘类更高的 class weight二是把边缘当作辅助任务主 loss 只算背景和宠物边缘单独算一个二值 loss。我一般会先跑一版不加权重的 baseline看验证集上边缘类的 IoU。如果边缘 IoU 长期低于 0.1就说明它被淹没了这时候再上权重。权重不要拍脑袋设用1 / sqrt(freq)这种按频率倒数开方的经验公式先试import numpy as np # 假设统计出的三类像素占比按你的实际数据替换 freq np.array([0.85, 0.13, 0.02]) # 背景、宠物、边缘 weight 1.0 / np.sqrt(freq) weight weight / weight.sum() * 3 # 归一化到均值为 1 附近 print(建议 class weight:, weight)参数说明freq是三类在全体 mask 里的像素占比边缘类通常只有百分之几甚至更低1/sqrt比直接1/freq温和避免边缘类权重过大导致训练震荡。算出来的 weight 直接传给nn.CrossEntropyLoss(weight...)。注意这只是起点最终还要看验证集指标微调。2.3 文件名对齐是隐形的坑数据集里原图和 mask 是同名不同目录比如images/Egyptian_Mau_182.png对应masks/Egyptian_Mau_182.png。写 Dataset 时最稳的做法是遍历 images 目录用os.path.splitext换目录去拼 mask 路径而不是分别遍历两个目录再排序配对——后者一旦有一张图缺 mask整个索引就错位了而且不会报错只会让模型学出莫名其妙的输出。import os from PIL import Image from torch.utils.data import Dataset class PetSegDataset(Dataset): def __init__(self, root, splittrain, transformNone): self.img_dir os.path.join(root, split, images) self.mask_dir os.path.join(root, split, masks) self.names sorted(os.listdir(self.img_dir)) self.transform transform 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_name os.path.splitext(name)[0] .png # 强制同名 mask Image.open(os.path.join(self.mask_dir, mask_name)) if self.transform: img, mask self.transform(img, mask) return img, mask逻辑说明以 images 目录的文件名为准用splitext拼出 mask 名保证一一对应。参数上split控制读 train 还是 valtransform需要同时处理 img 和 mask几何变换要同步颜色变换只对 img。如果某张 mask 缺失这里会直接抛FileNotFoundError比静默错位好排查得多。3. 接进 UNet / Swin-Unet 训练dataloader、归一化与可视化脚本3.1 训练集与验证集的目录结构确认这份数据集已经划分好训练集约 5100 张、验证集约 2200 张结构是每个 split 下各有images和masks两个目录。动手前先tree一下确认层级别把train/images和val/images搞混。常见做法是写一个常量字典管理路径避免到处硬编码字符串。# 确认目录结构只看两层 find . -maxdepth 3 -type d | sort # 期望输出类似 # ./train # ./train/images # ./train/masks # ./val # ./val/images # ./val/masks如果发现 mask 和 image 不在同一层级或者多套了一层先手动整理成上面这种结构再往下走。分割任务对路径结构很敏感路径错了往往不报错只是读到空数据。3.2 归一化参数别照搬 ImageNet宠物图像是自然图像用 ImageNet 的 mean/std 做归一化通常没问题但如果你要做迁移学习、加载的是在 ImageNet 上预训练的 backbone那就必须用 ImageNet 的统计量否则预训练权重等于白搭。反过来如果你从零训一个小 UNet用数据集自己的 mean/std 会更稳。常见做法是先算一遍训练集的均值和标准差import numpy as np from PIL import Image import os img_dir train/images pixel_sum np.zeros(3) pixel_sq_sum np.zeros(3) n 0 for name in os.listdir(img_dir): arr np.array(Image.open(os.path.join(img_dir, name)).convert(RGB)) / 255.0 pixel_sum arr.reshape(-1, 3).sum(axis0) pixel_sq_sum (arr.reshape(-1, 3) ** 2).sum(axis0) n arr.shape[0] * arr.shape[1] mean pixel_sum / n std np.sqrt(pixel_sq_sum / n - mean ** 2) print(mean:, mean, std:, std)参数说明/255.0把像素压到 0~1 再统计n是总像素数。算出来的 mean/std 直接填进transforms.Normalize。注意验证集要用训练集的统计量不能各算各的否则分布不一致。3.3 可视化脚本怎么用、输出在哪数据集自带一个可视化脚本随机抽一张图把原始图、GT 图、GT 在原图上的蒙板三张展示出来并保存在当前目录。这个脚本的价值在于快速验证「mask 和原图是否对齐」——如果蒙板盖错了位置说明文件名配对或 resize 出了问题。跑之前确认脚本里的路径指向你的数据根目录跑完在当前目录找输出的 png。# 常见调用方式具体参数看脚本内的 argparse python visualize.py --root ./ --split val --num 3如果脚本没有 argparse、路径写死就手动改root和split两个变量。输出一般是vis_xxx.png这种三合一对比图。重点看第三张蒙板图宠物轮廓应该被准确覆盖边缘类如果被可视化出来应该是一圈细线。如果蒙板整体偏移或缩放不对八成是原图和 mask 尺寸不一致或者 transform 里对 mask 用了插值。提示可视化脚本跑通再开始训练。很多人跳过这步训了半天发现 mask 根本没对上血泪经验。3.4 训练循环里必须盯的三个指标接进 UNet 或 Swin-Unet 后训练循环里别只看 loss。语义分割至少盯三个整体像素准确率容易被背景刷高参考价值有限、mIoU真正反映分割质量、以及每一类的 IoU尤其边缘类。常见做法是每个 epoch 在验证集上算一次混淆矩阵再从中导出各类 IoU。import torch import numpy as np def compute_iou(pred, target, num_classes3): # pred, target: [B, H, W] 的整数标签 ious [] pred pred.flatten() target target.flatten() for c in range(num_classes): inter ((pred c) (target c)).sum().item() union ((pred c) | (target c)).sum().item() ious.append(inter / union if union 0 else float(nan)) return ious # 训练循环里 model.eval() all_pred, all_gt [], [] with torch.no_grad(): for img, mask in val_loader: img img.cuda() out model(img) pred out.argmax(dim1).cpu() all_pred.append(pred) all_gt.append(mask) pred torch.cat(all_pred) gt torch.cat(all_gt) ious compute_iou(pred, gt, num_classes3) print(各类 IoU:, ious, mIoU:, np.nanmean(ious))逻辑说明argmax(dim1)把网络输出的 logits 转成类别索引混淆矩阵按类累加 inter 和 union。参数num_classes3对应背景、宠物、边缘。注意union0时返回 nan最后用nanmean忽略。如果边缘类 IoU 一直是 nan说明验证集里边缘像素太少或根本没预测出来要回头查标签映射。4. 避坑与排查这份数据集最容易翻车的五个地方4.1 现象loss 正常下降但 mIoU 卡在 0.3 上不去原因mask 像素值和类别索引差了一个偏移量模型学的是「背景1、宠物2」但评估时按「背景0、宠物1」算导致预测和 GT 系统性错位。解决回到 2.1 的统计脚本确认 mask 唯一值在 Dataset 里统一做mask mask - 1或按 classes 文件重映射然后重新评估。4.2 现象边缘类 IoU 长期为 0原因边缘像素占比极低普通交叉熵下梯度被淹没或者可视化时发现边缘根本没被标注出来。解决先确认 mask 里确实存在边缘类像素值用 2.1 的脚本看唯一值是否包含边缘那一类再上 class weight 或把边缘拆成辅助二值任务。如果数据本身边缘标注就稀疏别强求把它当正则项用。4.3 现象训练时显存爆掉batch size 只能设 2原因原图分辨率偏高UNet 下采样再上采样中间特征图很吃显存。解决常见做法是训练时随机裁剪到 256×256 或 320×320验证时再整图推理或者用混合精度torch.cuda.amp。注意裁剪要 img 和 mask 同步别只裁 img。4.4 现象验证集指标比训练集高很多原因验证集只有 2200 张且可能和训练集同分布加上验证时用了model.eval()关掉 dropout指标虚高很正常。解决别被验证集骗了留一小部分训练数据做 holdout或者做 K 折。另外检查验证集有没有和训练集重复的图同名不同 split 的情况要排掉。4.5 现象可视化脚本输出的蒙板整体偏移原因原图和 mask 尺寸不一致transform 里对 mask 用了双线性插值导致标签值被插成小数。解决mask 的 resize 必须用最近邻InterpolationMode.NEAREST几何变换旋转、裁剪要和 img 用同一组随机参数。检查transforms里 mask 那条链路别让它走默认插值。5. 进阶验证用混淆矩阵和单类 IoU 判断数据集值不值得长期用跑通 baseline 之后真正决定要不要把这份数据集长期用下去的不是 mIoU 那一个数而是每一类的表现和混淆矩阵的结构。我一般会做两件事一是把验证集的混淆矩阵画出来用表格也行看背景和宠物之间有没有大量互混二是单独看边缘类的召回判断它到底是「学不会」还是「标注本身就不一致」。先算混淆矩阵。下面这段直接输出 3×3 的计数表行是 GT、列是预测import torch import numpy as np def confusion_matrix(pred, target, num_classes3): # pred, target: 一维整数张量 idx target * num_classes pred cm torch.bincount(idx, minlengthnum_classes ** 2) return cm.reshape(num_classes, num_classes).numpy() # 假设 pred, gt 已经 concat 好并 flatten cm confusion_matrix(pred.flatten(), gt.flatten(), num_classes3) print(混淆矩阵(行GT, 列Pred):\n, cm) # 从混淆矩阵导出每类 IoU for c in range(3): tp cm[c, c] fp cm[:, c].sum() - tp fn cm[c, :].sum() - tp iou tp / (tp fp fn) if (tp fp fn) 0 else float(nan) print(f类 {c} IoU: {iou:.4f})逻辑说明target * num_classes pred把二维的 (GT, Pred) 组合压成一维索引bincount一次统计完所有组合再 reshape 回矩阵。参数num_classes3对应三类。看矩阵时重点看对角线对角线越大越好如果cm[0,1]背景被预测成宠物很大说明模型把大片背景误判成宠物通常是类别不平衡或归一化有问题。拿到每类 IoU 后判断标准可以这样定背景 IoU 通常最高宠物主体 IoU 在 0.7 以上算这份数据可用边缘类如果低于 0.2 且召回也低就把它降级成辅助任务别让它拖累主任务。如果宠物主体 IoU 都上不去 0.6先别怀疑网络回头查标签映射和可视化对齐——这份数据集本身质量不差问题多半出在读取环节。还有一个容易被忽略的验证点把训练集和验证集的类别像素占比分别统计一遍对比两者分布。如果验证集的边缘占比和训练集差一个数量级说明划分可能不是随机的这时候要么重新划分要么在评估时对边缘类单独加权。我一般会写个小函数把两个 split 的三类占比打出来一眼就能看出分布是否一致。def class_ratio(mask_dir, num_classes3, sample300): import os from PIL import Image counts np.zeros(num_classes) names os.listdir(mask_dir)[:sample] for name in names: m np.array(Image.open(os.path.join(mask_dir, name))) for c in range(num_classes): counts[c] (m c).sum() return counts / counts.sum() print(train 占比:, class_ratio(train/masks)) print(val 占比:, class_ratio(val/masks))参数sample300是抽样数量想更准就全量。两个占比向量越接近说明划分越均衡。如果 val 的边缘占比明显偏高或偏低评估指标的可比性就要打折扣。从那以后我每次拿到新的分割数据集都强制先跑一遍「唯一值统计 可视化对齐 双 split 占比对比」这三步再动网络。这份约 7000 张的宠物语义分割数据集结构清晰、划分现成、还带可视化脚本作为三类别分割的练手或验证集是够格的前提是你把标签映射和 mask 对齐这两件事做扎实。希望帮到你。本文还有配套的精品资源点击获取
返回列表