ARTICLE DETAIL

资讯详情

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

宠物语义分割数据集实战:7000张图从数据检查到模型训练全流程

宠物语义分割数据集实战:7000张图从数据检查到模型训练全流程 简介这份大型宠物图像语义分割数据集面向计算机视觉学习者与图像分割研究者尤其适合正在实践UNet、SwinUnet、TransUnet等分割网络、需要真实多类别数据做训练与验证的人群。数据集已按训练集与验证集划分完毕训练集约5100张图片及对应mask验证集约2200张共约7000张样本涵盖背景、宠物、边缘三类像素标签标签定义可参考classes文件。资源包共2000个文件以1998个png图像与掩膜为主另含1个txt类别说明和1个py可视化脚本压缩包约765.67MB采用7z格式。配套脚本可随机抽取一张图片展示原图、GT图像及GT在原图上的蒙板效果并保存到当前目录便于快速检查标注质量与数据分布。目前已有48人学习适合用于分割模型训练、对比实验与数据预处理练习。1. 宠物语义分割数据集7000 张图能撑起一个可用的分割模型吗拿到「约 7000 张宠物图像语义分割数据集」这个标题多数人第一反应是数据量够不够能不能直接训出能用的模型我先把结论摆出来——7000 张带像素级标签的宠物图像在语义分割这个任务里属于「小而精」的规模单类目标比如只分猫、狗、宠物本体完全够用多类细粒度区分品种、区分身体部位就偏紧。它真正适合的人群是想跑通语义分割全流程的算法工程师、做宠物相关产品智能喂食器、宠物摄像头、宠物社交 App 抠图的开发者以及拿它当教学或预训练底座的研究者。宠物图像有个天然难点毛发边缘极其琐碎猫狗的轮廓在像素级别上本身就是模糊的。这跟建筑、道路那种硬边界目标完全不同标注时人和动物的边界怎么算、胡须算不算前景、半透明毛发怎么处理都会直接影响标签质量。所以这个数据集的价值不在于「大」而在于它把语义分割里最烦人的一类边界问题集中暴露出来了。你拿它练手踩过的坑在医学图像分割、抠图、软标签这些场景里会反复遇到。7000 张的体量单卡 24G 显存跑主流分割网络一两天能出第一版结果迭代成本低这才是它最实际的意义。2. 语义分割数据集到底长什么样从标签格式到类别体系2.1 语义分割和实例分割的区别先别搞混热词里有人问「yolo26 中实例分割与语义分割的区别」这个问题必须先说清否则你拿到数据集会不知道怎么用。语义分割给每个像素分配一个类别标签同一类别的所有个体在标签图里是同一个值——三只猫全是「猫」这个类像素值一样。实例分割则要给每只猫单独编号猫 A 和猫 B 是不同实例。宠物数据集如果标签是单通道灰度图或调色板图每个像素只有一个类别 ID那就是语义分割如果还附带每个个体的 mask 或 JSON 里的多边形实例列表那才可能支持实例分割。判断方法很直接打开一张标签图看同一类别的两只宠物是不是连成一片、值相同。是就是语义分割。常见做法是标签存成 PNG 单通道背景为 0宠物为 1或者用调色板映射多类。你拿到数据集第一件事就是确认这一点不然训练时 loss 怎么算都是错的。2.2 标签格式与目录结构拿到手先做这三步检查一个规范的语义分割数据集目录通常长这样pet_seg/ ├── images/ │ ├── 0001.jpg │ ├── 0002.jpg │ └── ... ├── masks/ │ ├── 0001.png │ ├── 0002.png │ └── ... └── classes.txtimages 存原图masks 存同名的标签图classes.txt 写类别名和对应像素值。拿到数据集后我一般先跑一段检查脚本确认三件事图像和标签是否一一对应、标签像素值是否在预期类别范围内、图像尺寸和标签尺寸是否一致。import os import numpy as np from PIL import Image img_dir pet_seg/images mask_dir pet_seg/masks img_files sorted(os.listdir(img_dir)) mask_files sorted(os.listdir(mask_dir)) # 检查1文件名是否一一对应 assert [os.path.splitext(f)[0] for f in img_files] \ [os.path.splitext(f)[0] for f in mask_files], 图像与标签不匹配 # 检查2标签像素值分布 all_vals set() for f in mask_files[:200]: # 抽样200张足够看出类别体系 m np.array(Image.open(os.path.join(mask_dir, f))) all_vals.update(np.unique(m).tolist()) print(标签像素值集合:, sorted(all_vals)) # 检查3尺寸一致性 for f in img_files[:50]: stem os.path.splitext(f)[0] img Image.open(os.path.join(img_dir, f)) mask Image.open(os.path.join(mask_dir, stem .png)) if img.size ! mask.size: print(尺寸不一致:, f, img.size, mask.size)这段脚本的逻辑很直白第一步用文件名集合比对防止有图没标签或有标签没图第二步抽样统计标签里出现过的所有像素值你就能反推出类别体系——如果只有 0 和 1是二分类背景/宠物如果有 0、1、2、3可能是背景加多个宠物类别或身体部位。第三步查尺寸语义分割要求图像和标签严格对齐尺寸不一致的样本必须剔除或同步 resize否则训练时对齐就错了。参数上抽样数量 200 和 50 是经验值数据量小就全查。如果标签是调色板模式P 模式np.array读出来是调色板索引不是 RGB这点要注意别拿 RGB 值去比对类别。2.3 类别体系设计二分类还是多分类决定了后面所有工作宠物数据集常见的类别体系有三种。第一种是二分类背景 0、宠物 1最简单适合抠图、宠物检测前置。第二种是物种级多分类背景、猫、狗、其他宠物适合宠物摄像头区分物种。第三种是部位级背景、头部、躯干、四肢、尾巴适合姿态相关分析。7000 张的体量我建议二分类或物种级部位级标注成本高、样本摊薄后每类可能只剩一两千张训不充分。类别体系一旦定了后续的 loss 函数、评价指标、可视化配色都跟着定。多分类要用交叉熵或带 softmax 的 dice二分类用 BCE 或 sigmoid dice。评价指标上多分类看 mIoU二分类看 IoU 和 Dice。这些不是可选项是必须对齐的。我见过有人拿二分类标签去跑多分类的 softmax训练 loss 一直不降查了半天才发现类别数设错了这种坑纯属没做前面的检查。3. 用 7000 张宠物图跑通第一个分割模型从划分到训练3.1 训练集验证集划分别让同一只宠物同时出现在两边数据划分看着简单宠物数据集有个隐蔽的坑同一只宠物可能被拍了多张如果随机划分同一只猫的照片可能既在训练集又在验证集验证指标会虚高模型其实没学到泛化。常见做法是按宠物个体或按拍摄场景分组划分同一组只进一个集合。如果数据集没提供个体 ID退而求其次按图像相似度聚类后再划分或者至少保证验证集占 15% 到 20%。import os import random import shutil random.seed(42) img_dir pet_seg/images mask_dir pet_seg/masks out_root pet_seg_split files sorted(os.listdir(img_dir)) random.shuffle(files) split int(len(files) * 0.85) train_files files[:split] val_files files[split:] for subset, flist in [(train, train_files), (val, val_files)]: os.makedirs(f{out_root}/{subset}/images, exist_okTrue) os.makedirs(f{out_root}/{subset}/masks, exist_okTrue) for f in flist: stem os.path.splitext(f)[0] shutil.copy(os.path.join(img_dir, f), f{out_root}/{subset}/images/{f}) shutil.copy(os.path.join(mask_dir, stem .png), f{out_root}/{subset}/masks/{stem}.png) print(train:, len(train_files), val:, len(val_files))固定随机种子 42 是为了可复现团队协作时大家划分一致。85/15 的切分在 7000 张规模下验证集约 1050 张足够稳定评估。如果你的数据里有明显的个体聚集把random.shuffle换成按组打乱逻辑一样只是打乱单位从单张图变成组。3.2 数据增强宠物毛发场景下哪些增强有效、哪些是负优化语义分割的数据增强必须图像和标签同步变换这是铁律。宠物图像里水平翻转、随机裁剪、亮度对比度扰动是安全且有效的。垂直翻转要谨慎猫狗倒过来的样本在真实场景里极少加了可能引入噪声。旋转超过 30 度同理。颜色抖动幅度别太大毛发颜色是重要特征抖过头模型学不到稳定表征。import albumentations as A import cv2 train_transform A.Compose([ A.HorizontalFlip(p0.5), A.RandomResizedCrop(size(512, 512), scale(0.7, 1.0), p1.0), A.RandomBrightnessContrast(brightness_limit0.2, contrast_limit0.2, p0.5), A.Normalize(mean(0.485, 0.456, 0.406), std(0.229, 0.224, 0.225)), ]) val_transform A.Compose([ A.Resize(height512, width512), A.Normalize(mean(0.485, 0.456, 0.406), std(0.229, 0.224, 0.225)), ]) # 使用示例 img cv2.imread(pet_seg/images/0001.jpg) img cv2.cvtColor(img, cv2.COLOR_BGR2RGB) mask cv2.imread(pet_seg/masks/0001.png, cv2.IMREAD_GRAYSCALE) aug train_transform(imageimg, maskmask)albumentations 的 Compose 会自动把几何变换同步应用到 image 和 mask 上这是它比手写增强靠谱的地方。RandomResizedCrop 的 scale 下限设 0.7保证裁出来的区域还有足够宠物像素裁太狠可能整张图只剩背景标签全 0这种样本对训练是干扰。Normalize 用的 ImageNet 均值方差如果你用 ImageNet 预训练权重就必须保持一致从头训可以换成数据集自身统计值。3.3 选哪个分割网络U-Net、DeepLabV3 还是 SegFormer7000 张的规模我一般推荐两条路线。想快速出结果、显存有限用 U-Net 加 ResNet34 或 EfficientNet-B0 编码器结构简单512 分辨率下单卡 8G 就能跑。追求精度、有预训练权重加持用 SegFormer 的 B0 或 B1 版本Transformer 架构对毛发这种长程依赖边界处理得更好但显存和训练时间都要多一档。DeepLabV3 介于两者之间空洞卷积对多尺度目标友好宠物大小差异大时表现稳。选型不是越新越好。SegFormer 在小数据集上如果没加载预训练很容易过拟合U-Net 从头训反而更稳。我的习惯是先用 U-Net 跑一个 baseline记录 mIoU 和 Dice再换 SegFormer 对比看提升值不值得多花的算力。7000 张这个量级两者差距通常在 2 到 5 个 mIoU 点如果业务对边界精度要求不高U-Net 就够了。3.4 训练配置与关键参数学习率、损失函数、batch size 怎么定训练配置里最容易翻车的是学习率和损失函数。用预训练编码器时编码器学习率要设小通常是解码器的十分之一否则预训练权重被冲垮。损失函数上宠物分割边界像素占比小纯交叉熵会被背景主导常见做法是交叉熵加 Dice 的加权和Dice 负责拉边界交叉熵负责整体稳定。import torch import torch.nn as nn class CombinedLoss(nn.Module): def __init__(self, ce_weight0.5, dice_weight0.5): super().__init__() self.ce nn.CrossEntropyLoss() self.ce_weight ce_weight self.dice_weight dice_weight def dice_loss(self, pred, target, eps1e-6): # pred: [B, C, H, W] logits, target: [B, H, W] prob torch.softmax(pred, dim1) target_onehot torch.nn.functional.one_hot( target, num_classespred.shape[1]).permute(0, 3, 1, 2).float() inter (prob * target_onehot).sum(dim(0, 2, 3)) union prob.sum(dim(0, 2, 3)) target_onehot.sum(dim(0, 2, 3)) dice (2 * inter eps) / (union eps) return 1 - dice.mean() def forward(self, pred, target): return self.ce_weight * self.ce(pred, target) \ self.dice_weight * self.dice_loss(pred, target) # 优化器编码器和解码器分组学习率 encoder_params list(model.encoder.parameters()) decoder_params list(model.decoder.parameters()) optimizer torch.optim.AdamW([ {params: encoder_params, lr: 1e-5}, {params: decoder_params, lr: 1e-4}, ], weight_decay1e-4)CombinedLoss 里 ce_weight 和 dice_weight 各 0.5 是起点如果发现边界糊把 dice 权重提到 0.6 到 0.7如果整体类别预测乱提交叉熵权重。dice_loss 里对每个类别单独算再平均这样小类别不会被大类别淹没。优化器用 AdamW编码器 1e-5、解码器 1e-4 是预训练微调的常规配置从头训可以统一用 1e-3 配余弦退火。batch size 在 512 分辨率下8G 显存大概能放 4 到 8用梯度累积凑等效大 batch 也行。4. 训练不收敛、边界糊、指标虚高宠物分割的排查清单4.1 现象loss 震荡不下降验证集 mIoU 卡在 0.3 以下原因通常有三个。一是标签像素值和模型输出类别数不匹配比如标签里有 0、1、2 但模型输出只设了 2 类第 2 类永远学不到。二是学习率太大预训练权重被冲垮表现为前几个 epoch loss 先降后猛涨。三是数据增强把标签也做了插值mask 出现非整数像素值交叉熵直接报错或算出垃圾。解决先跑 2.2 的检查脚本确认类别体系再打印一个 batch 的标签唯一值和模型输出通道数对齐。学习率从 1e-5 起试观察前 5 个 epoch 的 loss 曲线。mask 的增强只允许最近邻插值albumentations 里 mask 默认就是最近邻但如果你手动 resize 用了双线性就会出问题。4.2 现象整体 mIoU 还行但宠物边缘像狗啃的这是宠物分割最典型的翻车现场。原因一是损失函数里 Dice 权重太低边界像素被交叉熵忽略。原因二是下采样倍率太高编码器把细节丢光了解码器补不回来。原因三是标签本身边界就粗糙标注时毛发区域被草草涂了个大概。解决把 dice_weight 提到 0.6 以上或者加边界感知损失。网络侧用输出 stride 为 4 而不是 8 的解码器保留更多空间细节。如果标签质量本身差先抽样看几十张标签边界糊得离谱的样本考虑剔除或重标别指望模型学会连人都标不准的边界。4.3 现象验证集指标高得离谱上线后效果崩了几乎可以断定是数据泄漏。同一只宠物的多张照片被分到了训练和验证两边模型记住了这只宠物的特征验证集等于开卷考试。另一种可能是验证集太小或分布和训练集太像比如全是同一背景、同一光照。解决回到 3.1按个体或场景分组划分。检查方法很简单算一下训练集和验证集的图像相似度如果大量验证图和训练图高度相似就是泄漏。验证集要尽量覆盖不同品种、不同光照、不同背景7000 张里至少留出几百张有明显差异的。4.4 现象训练时显存爆了或者 batch size 只能设 1原因通常是分辨率设太高或者模型解码器用了全分辨率特征拼接。512 分辨率下 U-Net 加 ResNet34 大概占 4 到 6G如果你设了 1024 又用 SegFormer24G 也扛不住。解决优先降分辨率到 512 或 384宠物分割对分辨率没那么敏感384 通常够用。其次用混合精度训练显存能省三到四成。再不行就梯度累积batch size 设 2累积 8 步等效 16。别硬扛大分辨率先跑通再优化。4.5 现象某些类别 IoU 一直是 0小类别样本太少或者类别像素值在标签里根本没出现。7000 张里如果某个宠物品种只有几十张模型学不到。也可能是标签里该类用了别的像素值和你 classes.txt 对不上。解决统计每个类别的像素占比和图像数占比低于 1% 的类别考虑合并或过采样。确认标签像素值和类别定义一致用 2.2 的脚本打印唯一值集合逐个核对。类别不平衡严重时交叉熵里加类别权重权重和频率成反比。5. 把 7000 张用到极致预训练、软标签与边界后处理数据集规模有限时榨干它的方式不是反复调网络结构而是换训练策略。我常用的第一招是自监督或大规模预训练权重打底ImageNet 预训练是标配如果能拿到 COCO 或 ADE20K 上训过的分割权重在宠物数据上微调mIoU 通常能比从头训高 5 到 10 个点。第二招是软标签宠物毛发边界本身是渐变的硬标签把边界像素非 0 即 1模型学得很痛苦。把边界区域用高斯模糊生成软标签让模型输出概率而不是硬分类边界过渡会自然很多这也是热词里「软标签」在分割任务里的实际用法。import numpy as np import cv2 def soft_boundary(mask, kernel_size5, sigma1.5): # mask: 硬标签 [H, W]返回软标签 [H, W] float fg (mask 0).astype(np.float32) soft cv2.GaussianBlur(fg, (kernel_size, kernel_size), sigma) # 内部区域保持硬标签只软化边界 eroded cv2.erode(fg, np.ones((kernel_size, kernel_size), np.uint8)) soft np.where(eroded 0, 1.0, soft) return soft这段逻辑是先二值化前景高斯模糊得到边界渐变再用腐蚀把目标内部还原成硬 1只保留边界带的软过渡。kernel_size 和 sigma 控制软化宽度5 和 1.5 适合 512 分辨率边界带大概两三个像素。软标签训练时损失函数换成 BCE 或带 soft target 的交叉熵模型输出用 sigmoid 而不是 softmax。第三招是边界后处理。推理完得到概率图用条件随机场或简单的形态学操作修边界能再捞回一两个 mIoU 点。CRF 对毛发这种细碎边界效果明显但速度慢实时场景慎用。轻量替代是引导滤波用原图当引导把概率图边缘对齐到图像真实边缘上。验证这套流程是否有效别只看 mIoU。我习惯额外看三个东西边界带的 IoU把标签腐蚀掉内部只留边界算指标、可视化叠加图的目视效果、以及在不同品种宠物上的分组指标。整体 mIoU 涨了但某个品种掉了说明模型在偏科得回去查数据分布。最后说个我自己的习惯每次拿到新数据集先花半天做数据体检别急着开训。7000 张宠物图体检能查出标签错误、类别体系混乱、数据泄漏这些致命问题省下的调试时间远超这半天。训崩了再回头查数据成本高得多。希望帮到你。本文还有配套的精品资源点击获取
返回列表