尧图网络科技YAOTU DIGITAL 获取报价
获取报价
首页 / 资讯中心 / 文章详情

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

发布时间:2026/9/28 1:23:36

资讯中心
01
ARTICLE

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

7000张宠物图像语义分割数据集:从数据探查到模型训练全流程
简介这份大型宠物图像语义分割数据集面向计算机视觉学习者与图像分割研究者尤其适合正在实践UNet、SwinUnet、TransUNet等分割网络、需要真实数据验证模型效果的人群。数据集共约7000张图像及对应标签已按训练集5100张左右、验证集2200张左右划分完毕每部分均包含images图片目录与masks掩膜目录标签分为背景、宠物、边缘三类像素值定义可参考classes文件。资源包共2000个文件以1998个png图像与掩膜为主另含1个txt类别说明和1个py可视化脚本压缩包约765.67MB。其中可视化脚本可随机抽取一张图片展示原图、GT图像及GT在原图上的蒙板效果并保存到当前目录便于快速检查标注质量。目前已有48人学习适合作为分割模型训练、调参与对比实验的完整数据基础。1. 从 7000 张宠物图说起语义分割数据集到底该怎么用手上拿到一个约 7000 张规模的宠物图像语义分割数据集第一反应往往不是兴奋而是犯嘀咕这点量够不够训一个能用的语义分割模型标签是掩码还是多边形猫狗边界、毛发边缘这种细碎区域标得准不准我见过太多人把数据集下载下来随手按 8:2 切了训练验证跑完发现 mIoU 卡在 0.6 上不去回头一查是类别映射错了、背景类没对齐、或者验证集里混进了训练图。宠物图像语义分割这件事难点从来不在模型结构而在数据这一侧前景主体毛发蓬松、姿态多变、背景杂乱沙发、草地、地板同一张图里可能同时出现猫、狗、人、玩具类别定义稍微含糊一点标注就崩了。这篇笔记就围绕这个约 7000 张的宠物语义分割数据集把「它是什么、怎么读、怎么切、怎么训、坑在哪」一条线讲透适合刚拿到数据集准备做语义分割算法落地的人也适合想把自定义数据集制作流程跑通的人。2. 宠物语义分割数据集的结构与标签格式解析2.1 先搞清楚语义分割和实例分割的边界拿到数据集第一件事是确认任务类型。语义分割给每个像素分配一个类别同一类别的两只狗在掩码里是连成一片的实例分割要把每只狗单独区分开。宠物数据集常见的是语义分割标注类别通常是 background、cat、dog有的还会细分到品种或者加上 person。这个区别直接决定你后面用哪个损失函数、哪个评价指标。如果你拿语义分割的标签去训实例分割模型或者反过来结果一定是玄学级别的差。判断方法很简单打开一张掩码图看两只挨在一起的同类目标是不是同一个像素值是就是语义分割。2.2 目录结构与标签编码的常见形态约 7000 张这个量级数据集一般长这样一个 images 目录放原图一个 masks 或 annotations 目录放标签标签可能是单通道 PNG像素值即类别 id、也可能是彩色 PNG需要按调色板映射回类别、还可能是 COCO 格式的 JSON 多边形。三种形态处理方式完全不同先确认再动手。# 先摸清数据集长什么样别急着写训练脚本 find ./pet_dataset -maxdepth 2 -type d | head -20 # 统计图片数量确认是不是接近 7000 find ./pet_dataset/images -type f \( -name *.jpg -o -name *.png \) | wc -l # 看一张掩码的通道数和像素值分布 python -c import numpy as np from PIL import Image m np.array(Image.open(./pet_dataset/masks/0001.png)) print(shape:, m.shape, dtype:, m.dtype) print(unique values:, np.unique(m)[:20]) 这段脚本先看目录层级再数图片总数最后打印掩码的维度和唯一像素值。如果 unique values 是 [0,1,2] 这种小整数说明是单通道类别 id 编码直接能用如果是 [0, 128, 255] 或者一堆彩色值说明是调色板或 RGB 编码需要建映射表。shape 如果是 (H, W, 3)那就是三通道彩色掩码必须转换。这一步不做后面训练 loss 不降你都不知道为什么。2.3 类别映射表必须先固定下来宠物数据集最容易翻车的地方是类别顺序。有的标注把 background 放 0cat 放 1dog 放 2有的把 cat 放 0。你训练时按自己的顺序读验证时按另一个顺序算指标mIoU 直接对不上。我一般会在项目根目录建一个固定的类别定义文件所有脚本都从这里读。# classes.py —— 全项目唯一的类别定义谁都不许改 CLASSES [background, cat, dog] NUM_CLASSES len(CLASSES) # 如果原始掩码用的是别的编码在这里写映射 # 例如原始 0cat, 1dog, 255background RAW_TO_TRAIN {0: 1, 1: 2, 255: 0} def remap_mask(mask): import numpy as np out np.zeros_like(mask, dtypenp.uint8) for raw, train in RAW_TO_TRAIN.items(): out[mask raw] train return out参数说明CLASSES 的顺序一旦定下训练、验证、可视化、部署全部沿用不要在任何脚本里硬编码数字。RAW_TO_TRAIN 是原始编码到训练编码的映射如果你的数据集已经是标准编码这个字典就写成恒等映射。remap_mask 用逐值替换而不是查表是因为类别数少可读性优先类别多的时候换成 np.take 查表更快。3. 把 7000 张宠物图切成能训的 train/val划分与预处理实操3.1 划分比例不是拍脑袋要看类别分布7000 张听起来不少但如果猫狗比例是 7:3而且有些图里猫狗同框随机切分很容易让验证集里某一类几乎消失。我一般先统计每张图包含哪些类别再做分层抽样。宠物数据集还有个坑同一只宠物可能有多张连拍图如果这些图被分到训练和验证两边验证指标会虚高这就是数据泄漏。常见做法是按宠物 id 或拍摄批次分组切分没有 id 信息时至少按文件名前缀去重。import os, random from collections import defaultdict import numpy as np from PIL import Image def scan_classes(mask_path, raw_to_train): m np.array(Image.open(mask_path)) present set() for raw, train in raw_to_train.items(): if (m raw).any(): present.add(train) return present def stratified_split(img_dir, mask_dir, raw_to_train, val_ratio0.15, seed42): random.seed(seed) groups defaultdict(list) for name in os.listdir(img_dir): stem os.path.splitext(name)[0] mask_path os.path.join(mask_dir, stem .png) if not os.path.exists(mask_path): continue key tuple(sorted(scan_classes(mask_path, raw_to_train))) groups[key].append(stem) train, val [], [] for key, stems in groups.items(): random.shuffle(stems) n_val max(1, int(len(stems) * val_ratio)) val.extend(stems[:n_val]) train.extend(stems[n_val:]) return train, val逻辑说明scan_classes 读掩码返回这张图里出现了哪些训练类别作为分层依据。stratified_split 按「类别组合」分组每组内按比例切保证验证集里猫、狗、背景都有覆盖。val_ratio 设 0.15 是 7000 张量级的常用值验证集约 1000 张够算稳定的 mIoU。seed 固定住保证每次切分一致方便复现。如果你的数据集有宠物 id把 key 换成 id 做分组切分能进一步避免泄漏。3.2 预处理尺寸、归一化和增强的取舍宠物图像分辨率往往不统一直接 resize 到 512×512 会拉伸变形尤其是长条形的猫。常见做法是短边缩放到目标尺寸再中心裁剪或者直接 padding 到统一尺寸。归一化用 ImageNet 均值方差就行因为主干网络基本都是 ImageNet 预训练的。增强方面水平翻转、随机裁剪、颜色抖动是安全牌垂直翻转对宠物要谨慎猫狗倒过来不符合真实分布可能反而掉点。import torch from torch.utils.data import Dataset from torchvision import transforms import numpy as np from PIL import Image class PetSegDataset(Dataset): def __init__(self, img_dir, mask_dir, stems, raw_to_train, size512, trainTrue): self.img_dir, self.mask_dir, self.stems img_dir, mask_dir, stems self.raw_to_train raw_to_train self.size size self.train train self.normalize transforms.Normalize( mean[0.485, 0.456, 0.406], std[0.229, 0.224, 0.225]) def __len__(self): return len(self.stems) def __getitem__(self, idx): stem self.stems[idx] img Image.open(f{self.img_dir}/{stem}.jpg).convert(RGB) mask np.array(Image.open(f{self.mask_dir}/{stem}.png)) # 短边缩放 中心裁剪保持长宽比 w, h img.size scale self.size / min(w, h) img img.resize((int(w*scale), int(h*scale)), Image.BILINEAR) mask Image.fromarray(mask).resize((int(w*scale), int(h*scale)), Image.NEAREST) # 中心裁剪 left (img.size[0] - self.size) // 2 top (img.size[1] - self.size) // 2 img img.crop((left, top, leftself.size, topself.size)) mask np.array(mask.crop((left, top, leftself.size, topself.size))) if self.train and np.random.rand() 0.5: img img.transpose(Image.FLIP_LEFT_RIGHT) mask np.fliplr(mask).copy() img self.normalize(torch.from_numpy(np.array(img)).permute(2,0,1).float()/255) mask torch.from_numpy(mask).long() return img, mask参数说明size512 是宠物分割的常用输入显存吃紧可以降到 384 或 320。mask 的 resize 必须用 NEAREST用 BILINEAR 会插值出 1.5 这种不存在的类别 id训练直接报错或学出脏标签。翻转时 mask 用 np.fliplr 后要 .copy()否则负步长数组转 tensor 会出问题这是血泪经验。归一化放在最后输入模型的是标准化后的 float tensor。3.3 用 DataLoader 跑通一个 batch 再谈训练在写完整训练循环之前先确认数据管道能出正确的 batch。这一步能省掉后面大量排查时间。from torch.utils.data import DataLoader train_stems, val_stems stratified_split(./images, ./masks, RAW_TO_TRAIN) train_ds PetSegDataset(./images, ./masks, train_stems, RAW_TO_TRAIN, trainTrue) val_ds PetSegDataset(./images, ./masks, val_stems, RAW_TO_TRAIN, trainFalse) train_loader DataLoader(train_ds, batch_size8, shuffleTrue, num_workers4, drop_lastTrue) imgs, masks next(iter(train_loader)) print(img:, imgs.shape, imgs.dtype) # [8, 3, 512, 512] float32 print(mask:, masks.shape, masks.dtype) # [8, 512, 512] int64 print(mask unique:, masks.unique()) # 应该只有 0,1,2如果 mask unique 里出现了 255 或别的值说明 remap 没生效如果 img 的 dtype 是 uint8说明归一化漏了。batch_size 从 8 起步512 输入下 8G 显存大概能跑不够就降到 4 并配合梯度累积。num_workers 在 Linux 上设 4 到 8Windows 上设 0 避免多进程报错。4. 用宠物数据集训一个能看的语义分割模型损失、指标与训练循环4.1 损失函数选型交叉熵够不够宠物分割类别少且不平衡背景像素远多于猫狗纯交叉熵容易被背景主导。常见做法是交叉熵加 Dice 或 Focal 的组合。我一般先用交叉熵跑一版基线看每类的 IoU如果猫狗 IoU 明显低于背景再引入 Dice 损失。类别权重也可以按像素频率的倒数来设但别设太极端否则训练不稳定。import torch.nn as nn import torch.nn.functional as F class ComboLoss(nn.Module): def __init__(self, num_classes, dice_weight0.5): super().__init__() self.ce nn.CrossEntropyLoss(ignore_index255) self.dice_weight dice_weight self.num_classes num_classes def dice_loss(self, logits, target): probs F.softmax(logits, dim1) loss 0.0 for c in range(self.num_classes): p probs[:, c] t (target c).float() inter (p * t).sum() union p.sum() t.sum() loss 1 - (2 * inter 1e-6) / (union 1e-6) return loss / self.num_classes def forward(self, logits, target): return self.ce(logits, target) self.dice_weight * self.dice_loss(logits, target)参数说明ignore_index255 用来忽略边界未标注像素如果你的数据集没有这类像素可以去掉。dice_weight 从 0.5 起步猫狗 IoU 低就加到 1.0训练震荡就降到 0.3。dice_loss 里加 1e-6 是防止某类在当前 batch 完全没出现时除零。这个组合在宠物数据集上通常比纯 CE 高 3 到 5 个点 mIoU。4.2 mIoU 怎么算才不骗自己mIoU 是语义分割的标准指标但实现细节很容易算错。正确做法是维护一个混淆矩阵累加整个验证集最后一次性算。逐 batch 算再平均会被小 batch 里的类别分布带偏。def update_confusion(conf, pred, target, num_classes): pred pred.flatten() target target.flatten() valid target ! 255 idx target[valid] * num_classes pred[valid] conf np.bincount(idx, minlengthnum_classes**2).reshape(num_classes, num_classes) def compute_miou(conf): iou [] for c in range(conf.shape[0]): inter conf[c, c] union conf[c, :].sum() conf[:, c].sum() - inter if union 0: iou.append(inter / union) return np.mean(iou), iou逻辑说明conf 是 num_classes × num_classes 的矩阵行是真实类别列是预测类别。update_confusion 把预测和标签展平后过滤掉 ignore 像素用 bincount 一次性累加比循环快很多。compute_miou 对每个出现过的类别算 IoU 再平均没出现过的类别跳过避免除零。验证时记得 model.eval() 加 torch.no_grad()否则 BN 统计和显存都会出问题。4.3 训练循环与学习率调度主干网络用预训练的 ResNet 或轻量的 MobileNet 都行宠物数据集 7000 张不算大从头训容易过拟合预训练权重基本是必须的。优化器 AdamW 配 cosine 调度是稳妥选择初始学习率 1e-4 到 3e-4。import torch from torch.optim import AdamW from torch.optim.lr_scheduler import CosineAnnealingLR device cuda if torch.cuda.is_available() else cpu model build_model(num_classesNUM_CLASSES).to(device) # 你的分割网络 criterion ComboLoss(NUM_CLASSES).to(device) optimizer AdamW(model.parameters(), lr2e-4, weight_decay1e-4) scheduler CosineAnnealingLR(optimizer, T_max40) best_miou 0.0 for epoch in range(40): model.train() for imgs, masks in train_loader: imgs, masks imgs.to(device), masks.to(device) logits model(imgs) loss criterion(logits, masks) optimizer.zero_grad() loss.backward() optimizer.step() scheduler.step() # 验证 model.eval() conf np.zeros((NUM_CLASSES, NUM_CLASSES), dtypenp.int64) with torch.no_grad(): for imgs, masks in val_loader: imgs imgs.to(device) pred model(imgs).argmax(1).cpu().numpy() update_confusion(conf, pred, masks.numpy(), NUM_CLASSES) miou, per_class compute_miou(conf) print(fepoch {epoch} mIoU {miou:.4f} per-class {per_class}) if miou best_miou: best_miou miou torch.save(model.state_dict(), best.pth)参数说明lr2e-4 是预训练主干微调的常用起点如果 loss 前几个 epoch 就爆了降到 1e-4。weight_decay1e-4 抑制过拟合。T_max40 和总 epoch 数一致cosine 才会完整退火。每轮验证后按 mIoU 存最优权重别只看最后一轮宠物数据集后期容易过拟合最优往往在中间。7000 张、512 输入、单卡 3090 大概每 epoch 几分钟40 轮几小时能跑完。5. 宠物语义分割数据集制作与训练中的避坑清单5.1 掩码边缘出现 255 或异常值现象训练时 loss 突然变 NaN或者打印 mask unique 发现有 255、254 这类值。原因标注工具在目标边界留了 ignore 区域或者彩色掩码转单通道时映射表没覆盖全。解决先统计全数据集掩码的唯一值建完整映射表确实要忽略的边界像素在损失函数里设 ignore_index并在算 mIoU 时过滤掉别让它们进混淆矩阵。5.2 验证集 mIoU 虚高上线就崩现象验证 mIoU 0.85换一批真实宠物图预测惨不忍睹。原因训练验证切分时同一只宠物的连拍图被分到两边模型记住了这只宠物的纹理而不是学会了分割。解决按宠物 id 或文件名前缀分组切分确保同一主体的图只出现在一边。没有 id 信息时用图像相似度做去重分组宁可训练集少一点。5.3 猫狗边界糊成一团毛发区域 IoU 特别低现象整体 mIoU 还行但 cat 和 dog 的 IoU 明显低于 background可视化看边界像蒙了一层雾。原因输入分辨率太低毛发细节在 resize 时丢了或者损失函数对边界像素权重不够。解决把输入从 256 提到 512 甚至 768掩码 resize 坚持用 NEAREST损失里加边界加权或者用 Dice 损失强化前景。别指望后处理能救回丢失的细节。5.4 类别极度不平衡导致小类直接消失现象训练几个 epoch 后模型对所有像素都预测 backgroundmIoU 看着不低但猫狗全丢。原因背景像素占比过高交叉熵被背景主导。解决用 ComboLoss 里的 Dice 分量或者给交叉熵设类别权重按像素频率倒数但上限截断到 10 以内。也可以对含猫狗的图做 oversampling让前景在 batch 里占比提上来。5.5 数据增强把标签增强坏了现象训练 loss 正常下降但验证时某些图预测出奇怪的条带或错位。原因几何增强时图像和掩码用了不同的插值方式或不同的随机参数导致两者不对齐。解决所有几何变换必须同时作用于图像和掩码且掩码只用 NEAREST随机裁剪、翻转的随机种子要共享。写 Dataset 时把 img 和 mask 当成一个整体处理别分开写两套增强逻辑。6. 把 7000 张用到极致小数据集上的进阶技巧与验证习惯7000 张在语义分割里属于中小规模想榨出更高精度靠的不是换更大的模型而是把数据用透。我常用的几个手段第一预训练主干一定要用ImageNet 权重能省掉大量标注需求如果找得到宠物相关的预训练权重更好第二伪标签半监督先用现有数据训一版模型对无标注宠物图预测挑高置信区域加入训练集迭代两三轮通常能涨 2 到 4 个点第三测试时增强TTA推理时对原图做水平翻转和多尺度预测结果平均mIoU 能再涨 1 到 2 个点代价是推理变慢。验证习惯上我坚持两件事。一是固定一个 hold-out 验证集从切分那天起就不动它所有调参看这个集避免过拟合验证集二是每次改完数据管道或损失函数先跑 5 个 epoch 看 loss 曲线和 mask 可视化别等 40 轮跑完才发现标签映射错了。可视化这一步很多人偷懒但宠物分割的很多问题——边界糊、类别错、掩码错位——看图一眼就能发现看指标反而绕远。技巧预期增益代价适用条件ImageNet 预训练5~10 mIoU无几乎总是伪标签半监督2~4 mIoU多轮训练有无标注宠物图测试时增强 TTA1~2 mIoU推理变慢对延迟不敏感输入分辨率 512→7682~3 mIoU显存翻倍显存充足Dice CE 组合损失3~5 mIoU无类别不平衡最后说个我自己的习惯每拿到一个新数据集先花半天只做数据探查——数图片、看掩码、统计类别分布、可视化几十张——再动手写训练代码。这个半天能省掉后面好几天的排查。宠物语义分割数据集看着简单但毛发、姿态、背景这些变量叠在一起数据侧的坑远比模型侧多。希望帮到你。本文还有配套的精品资源点击获取
02
RELATED NEWS

相关资讯

更多网站建设与数字化升级内容

03
WHY YAOTU

想打造同款高转化官网?

懂行业、懂生意,从建站到增长一站式陪跑

◈

场景化定制

不做模板站,围绕你的业务场景量身设计,小众不撞款。

◐

营销型架构

以转化目标组织内容与路径,让官网真正带来询盘。

▲

全周期服务

设计、开发、运营、运维一体,上线只是开始。

免费获取你的建站方案

留下需求,专属顾问 24 小时内为你输出方案建议。