ARTICLE DETAIL

资讯详情

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

猫狗图像分割数据集详解:从目录结构到UNet训练实践

猫狗图像分割数据集详解:从目录结构到UNet训练实践 简介面向深度学习与计算机视觉学习者的猫狗图像分割数据集已划分好训练集与测试集文件以文件夹格式存放直接解压即可用于分割网络训练省去数据清洗和格式转换步骤。训练集包含5912张原始图像及对应的掩码标注测试集包含1478张图像及对应掩码共计7390对样本前景标注完整准确、区域细节丰富非常适合训练常见的分割网络。压缩包共2000个文件其中1999个为jpeg格式图像另含1个Python可视化脚本可随机抽取图片将原始图、GT掩码、GT叠加图一并展示并保存便于快速检查标注效果压缩包大小约230.81MB解压后约247MB。内容涵盖埃及猫、阿比西尼亚猫、英国短毛猫、孟买猫等多个品种样本多样性较好目前已有236人学习使用适合图像分割入门、课程设计或算法对比实验。1. 猫狗图像分割数据集为什么说它是“开箱即用”的那一类训练深度学习图像分割模型最耗时间的往往不是写网络而是找一份能直接喂给模型的数据集。很多公开数据要自己转格式、自己清洗标注、自己划分训练测试集一折腾就是两三天。这个猫狗图像分割数据集正好绕开了这些247MB训练集5912张原图加5912张mask测试集1478张加1478张mask目录结构已经是标准的images和masks两两对应拿过来不改路径就能训练。它的定位很明确——给需要快速验证分割网络、熟悉UNet等模型训练流程的人用。从文件名能看到埃及猫、阿比西尼亚猫、英短、斯芬克斯、孟买猫等品种编号图像内容干净标注集中在前景区域适合做二分类语义分割的入门和调参。2. 目录结构与标注特点5912/1478 划分背后的建模价值2.1 images 与 masks 的对应关系拿到数据集先看目录这一点比什么都重要。常见的错误是拿到压缩包直接解压扔进训练脚本结果发现文件名对不上。这个数据集的结构是这样的dataset/ ├── train/ │ ├── images/ │ │ ├── Abyssinian_29.jpeg │ │ ├── Bombay_41.jpeg │ │ ├── Egyptian_Mau_165.jpeg │ │ └── ... │ └── masks/ │ ├── Abyssinian_29.png │ ├── Bombay_41.png │ ├── Egyptian_Mau_165.png │ └── ... └── test/ ├── images/ └── masks/train下的images有5912张图片masks有5912个对应文件test下各1478张。这里的对应规则是文件名前缀相同只是扩展名不同——原图是jpegmask是png。为什么mask要用png而不是jpeg因为jpeg是有损压缩会在标注边缘产生块状噪点对分割训练来说等于给标签引入了噪声png无损0和255这样的灰阶值能原样保留。如果你自己造数据集mask一律存png别用jpg。2.2 mask 的像素语义前景区域与二值标注数据集的摘要描述里说“前景值区域丰富标注效果极好”。翻译成像素语言就是mask不是简单的一整块白色涂满物体而是把猫的边缘、耳朵、胡须这些细节都标出来了前景区域内部灰度层次分明边缘过渡也比较干净。这类宠物分割的mask一般是二值标注背景为0前景为255。不过这里要注意即使视觉上看起来是纯白区域经过标注工具导出后前景值未必统一等于255。有些标注软件会用灰度级区分物体实例同一个mask里可能出现128、200这类中间值。训练前拿脚本扫一遍实际取值分布非常重要from PIL import Image import numpy as np mask Image.open(train/masks/Bombay_41.png).convert(L) arr np.array(mask) print(shape:, arr.shape) print(unique values:, np.unique(arr)) print(fg ratio:, (arr 0).sum() / arr.size)逻辑说明先转成单通道灰度图再用np.unique统计所有像素值。如果输出的unique只有[0, 255]说明是标准二值标注如果出现别的值训练时就要决定是直接阈值化还是保留多级标签。fg ratio算的是前景像素占比用来判断类别平衡程度猫在图里占比太小的话模型很容易学成“全部预测为背景”。2.3 训练集/测试集划分的合理性5912和1478大约是4:1的划分比例这个比例对分割任务来说中规中矩。有人觉得训练集应该更大但分割任务看重的是标注质量而不是单纯堆数量。一个更需要注意的点是宠物品种在训练集和测试集里是否均匀分布。从文件名看图像是按品种加编号组织的如果训练集全是英短而测试集全是斯芬克斯模型会严重退化到“只认识见过的猫”。常见的做法是拿到数据后先按品种做一次分组统计确认训练集和测试集的品种重叠度。没有现成工具的话写一个简单的counter就行ls train/images | awk -F_ {print $1} | sort | uniq -c逻辑说明文件名格式是品种_编号.jpeg按下划线切分后取第一个字段就是品种名排序后统计每种数量。如果某个品种只出现在测试集没有出现在训练集这就是一个明显的分布缺口后续评估指标会失真。3. 把数据集跑进 UNetDataset 封装与 mask 预处理要点3.1 读取 mask 时要避免的三个默认行为把图像分割数据集跑起来第一步不是调整网络结构而是把数据加载写对。对新手来说PIL和OpenCV在读取mask时的默认行为是最容易翻车的三个地方第一PIL的Image.open默认不转灰度mask如果以RGB方式读进来就会变成三通道标签从(H, W)变成(H, W, 3)直接导致和模型的输出通道对不上。第二OpenCV的cv2.imread默认按BGR三通道读入并且会把png的透明通道丢掉mask的灰度语义被破坏。第三mask读进来之后如果直接除以255转float再拿去算交叉熵损失模型输出的概率和标签的数值范围确实是对上了但如果loss里用了ignore_index二值标签里残留的中间灰度值会干扰计算。我的习惯是原图走PIL转RGBmask单独走一条路——Image.open(...).convert(L)转灰度后直接用np.array并且在Dataset里就完成归一化不让训练循环操心预处理。3.2 Dataset 代码与参数说明下面这个是配合UNet最常见的Dataset写法直接用PyTorch的Dataset抽象类实现import torch from torch.utils.data import Dataset from PIL import Image import numpy as np import os class CatDogSegDataset(Dataset): def __init__(self, image_dir, mask_dir, image_size(256, 256), transformNone, mask_transformNone): self.image_dir image_dir self.mask_dir mask_dir self.image_size image_size self.transform transform self.mask_transform mask_transform # 文件名前缀一致按stem对齐 self.image_names sorted(os.listdir(image_dir)) self.mask_names sorted(os.listdir(mask_dir)) assert len(self.image_names) len(self.mask_names), \ image and mask count mismatch def __len__(self): return len(self.image_names) def __getitem__(self, idx): img_name self.image_names[idx] mask_name self.mask_names[idx] assert img_name.split(.)[0] mask_name.split(.)[0], \ fname mismatch: {img_name} vs {mask_name} image Image.open(os.path.join(self.image_dir, img_name)).convert(RGB) mask Image.open(os.path.join(self.mask_dir, mask_name)).convert(L) image image.resize(self.image_size, Image.BILINEAR) mask mask.resize(self.image_size, Image.NEAREST) image_np np.array(image, dtypenp.float32) / 255.0 mask_np np.array(mask, dtypenp.int64) # 将前景统一归一化成1 mask_np (mask_np 0).astype(np.int64) if self.transform: image_np self.transform(image_np) if self.mask_transform: mask_np self.mask_transform(mask_np) image_tensor torch.from_numpy(image_np).permute(2, 0, 1) mask_tensor torch.from_numpy(mask_np) return image_tensor, mask_tensor逻辑说明__getitem__每次返回一对图像和mask两者通过文件名前缀绑定。图像用双线性插值缩放mask用最近邻插值——这个区别很关键变换mask时千万不能用线性插值它会在0和255之间插入灰色过渡像素导致标签出现原本不存在的类别值。最后把mask大于0的区域全部置为1统一成前景背景二分类标签这一步把这个数据集“前景值区域丰富”的灰度标注收敛到模型训练需要的闭区间。参数说明image_size建议先统一成256×256UNet在这个分辨率上能取得训练速度和精度的平衡如果显存够大可以上512×512但首轮跑通建议用小尺寸。transform接收的是已经归一化到0-1的numpy数组可以直接接PyTorch的RandomHorizontalFlip这类几何变换但注意这类变换作用在numpy数组上需要配ToTensor之类的转换器我习惯在这里手动完成归一化和HWC转CHW减少外部依赖。3.3 数据增强几何变换必须同步作用于 mask训练分割网络增强是必要的。但要牢牢记住一条铁律凡是几何变换必须同时作用到图像和mask上凡是颜色/亮度变换只能作用到图像上。很多人把RandomHorizontalFlip只加在图像上训练几个epoch后loss不降看预测结果发现猫的位置全是镜像错位的——这就是图像左右翻转了而mask没翻。import random def sync_transform(image_np, mask_np, p0.5): # image_np: (H, W, 3) float32 0-1 # mask_np: (H, W) int64 0/1 if random.random() p: image_np image_np[:, ::-1, :] mask_np mask_np[:, ::-1] return image_np, mask_np逻辑说明同步变换的核心是让图像和mask使用同一个随机种子也就是同一次判断决定是否翻转。这里直接切片的::-1做水平翻转对mask的标签语义没有影响因为前景背景的类别值不会因为左右镜像而改变。竖直翻转对宠物图像来说不太自然训练效果未必好我一般只用水平翻转和轻微缩放。3.4 训练参数参考划分、batch 与 loss把数据集喂给UNet时下面这组参数是我在这个数据集上反复试过比较稳的起点参数推荐值说明输入尺寸256×256显存不足时降到224Batch Size168G显存可跑不够就用8优化器Adam初始学习率1e-4损失函数BCEWithLogitsLoss二分类分割推荐训练轮数50-80前20轮观察loss下降趋势验证指标mIoU / Dice不能只看loss这个组合在大多数二值分割场景下都能快速收敛。注意不要用SGD大学习率入门分割任务的类别极度不平衡SGD容易震荡Adam的逐参数自适应学习率对新手更友好。等整套流程跑通、确定模型没问题后再换成SGD精调也不迟。4. 可视化脚本的用法与判读一图看穿标注质量4.1 脚本输出的三种图资源里附带了一个图像分割可视化脚本作用是随机抽取一张图把原始图片、GT分割图、GT叠加在原图上的蒙版效果横向排在一起并保存到当前目录。这个脚本的价值在于训练前快速抽查标注是否对齐训练后快速看模型效果。import matplotlib.pyplot as plt from PIL import Image import numpy as np import os import random def visualize_sample(image_dir, mask_dir, output_namesample_vis.png): img_files os.listdir(image_dir) chosen random.choice(img_files) stem chosen.split(.)[0] image_path os.path.join(image_dir, chosen) mask_path os.path.join(mask_dir, stem .png) assert os.path.exists(mask_path), fmask missing: {mask_path} image np.array(Image.open(image_path).convert(RGB)) mask np.array(Image.open(mask_path).convert(L)) # 蒙版叠加红色半透明区域表示前景 overlay image.copy() mask_bool mask 0 overlay[mask_bool] (overlay[mask_bool] * 0.4 np.array([255, 0, 0]) * 0.6).astype(np.uint8) fig, axes plt.subplots(1, 3, figsize(18, 6)) axes[0].imshow(image); axes[0].set_title(Original) axes[1].imshow(mask, cmapgray); axes[1].set_title(GT Mask) axes[2].imshow(overlay); axes[2].set_title(Overlay) for ax in axes: ax.axis(off) plt.tight_layout() plt.savefig(output_name, dpi150, bbox_inchestight) print(fsaved to {output_name})逻辑说明叠加图不是简单把mask用白色贴在原图上而是用红色半透明蒙版标记出前景区域。mask_bool是二值索引overlay[mask_bool]取出所有前景像素把它们往红色方向混合原图占40%纯红占60%。这样即使在深色毛发区域蒙版边界也能看清。参数说明dpi150适合快速检查如果要写论文插图可以调到300。cmapgray保证GT按灰度显示不会因为matplotlib默认的colormap把0和255映射成奇怪的颜色。运行脚本后重点看第三张图的边缘——如果轮廓和猫的边缘贴合度高、没有大面积溢出或空洞就说明标注质量过关。4.2 用可视化结果核对 mask 对齐可视化脚本最大的用途是检验文件名对齐。实际操作中我见过数据集的mask文件比图像多一个sorted之后索引整体错位模型训练了一个epoch后loss不降反而升高。这种问题靠肉眼扫描文件名是看不出来的但可视化叠加图一眼就能暴露猫在左边蒙版却在右边。另一个检查点是mask的覆盖范围。前景区域占比过小或者过大说明标注质量有问题或者图像裁剪不合适。如果mask在物体边缘有一圈规则的白色边框那多半是标注工具导出时的膨胀操作遇到这种情况需要腐蚀处理。5. 避坑与排查训练猫狗分割时最容易翻车的四件事5.1 mask 被 PIL 读成三通道现象训练时报错size mismatch模型输出是(B, 2, H, W)target是(B, H, W, 3)维度对不上。原因Image.open(mask_path)没有指定模式黑白png被PIL按RGB方式读入变成了(H, W, 3)。解决读取mask统一走Image.open(mask_path).convert(L)确保拿到的是单通道灰度。这个坑几乎每个新手都会踩一次踩完就长记性了。5.2 标签翻转255 被当成背景现象loss一直在0.7左右徘徊训练20轮没明显下降验证集Dice不到0.1。原因mask里背景是0、前景是255但有人习惯在预处理里写1 - mask/255直接把前景背景翻转了。网络学到的是“把所有像素预测为某个固定类”就能拿到很低loss——因为背景占比高全预测背景的Dice也可能有80%。这里血泪经验是二分类分割的loss会欺骗你必须配合Dice或IoU一起看。解决用前面第2.2节的检查脚本先打印np.unique(arr)确认数值语义再决定是否翻转。不要靠猜要靠统计。5.3 图像尺寸不一致导致 dataloader 报错现象单张预测没问题一进DataLoader就报stack expects each tensor to be equal size。原因数据集中原始图片不全是正方形有的宽屏有的竖图batch内堆叠时尺寸不一致。解决在Dataset里对图像和mask同时做resize让它们统一到(256, 256)。如果不想破坏长宽比可以padding成正方形再resize但mask的padding部分要填0背景不能填255否则等于给测试集添了灰色标签。5.4 前景占比失衡猫在画面里只有几个像素现象模型收敛后预测图全是黑的一个前景像素都画不出来。原因部分图像中猫很小mask前景像素占比低于1%。分割网络在这种样本上学不到有效特征反向传播的梯度几乎全来自背景区域。解决训练前按第2.2节的fg ratio统计所有mask的占比将占比过低的图像过滤掉或者加入Weighted BCE Loss给前景像素更高的权重。另一种做法是把小目标图像的裁剪区域放大再训练这比调loss更直接有效。实际踩下来一份数据里如果有10%以上的图前景占比低于5%直接过滤掉重采样的性价比最高。6. 进阶验证用训练指标和可视化结果反推数据集质量数据集的真正价值不在于“能跑通”而在于“能帮你判断模型和数据的边界”。我习惯做这样一轮快速验证用第3.2节的Dataset封装训练20个epoch只看三个数——训练loss、验证mIoU、前景Dice。如果20轮内mIoU能上0.85说明这个数据集标注干净、分布稳定适合做后续模型结构实验的基准如果mIoU在0.5-0.6之间上不去先别怀疑网络去可视化几张预测图看是边缘溢出还是整体偏色。这个数据集还能做一件事把二分类任务的mask推广到多类别。数据集的mask虽然只有前景背景两层语义但文件名包含了品种信息你可以利用这个信息做一层弱监督——比如把埃及猫、孟买猫、英短各自归为一类手动给每个mask按品种重标标签。这样就从一份二分类数据集扩展成多类分割数据集用于测试UNet或DeepLabV3的多类别分割能力。更进一步这个数据集也适合做迁移学习实验。先用这份猫狗数据做一个分割预训练模型然后把骨干网络的权重迁移到医学图像分割或者工业质检场景对比随机初始化的效果差异。这类开放领域的宠物分割数据标注质量高作为预训练来源比直接用ImageNet分类权重更接近分割任务本身的特征分布。我第一次做这个实验时迁移后Dice比随机初始化高了接近7个百分点效果出人意料。从那以后我每拿到一份新数据集都会强制走一遍“统计像素分布 → 可视化对齐 → 训练20轮基线 → 迁移验证”这四步把数据集的底细摸清楚再往深了做。这个流程浪费不了几个小时但能帮你避掉后面好几周的无效调试。希望帮到你。本文还有配套的精品资源点击获取
返回列表