舌头分割数据集实战:2类掩码从数据校验到边界优化
发布时间:2026/9/30 1:39:10来源:尧图网络
简介这份资源面向计算机视觉方向的开发者与图像分割学习者提供一套完整的舌头分割数据集可用于训练和验证二类语义分割模型。数据图像分辨率统一为640×640原图为jpg格式mask标签为png格式采用0背景、1舌头的阈值图像具体类别可在classes文本中查看。压缩包共约2000个文件以1998个png掩膜、1个txt类别说明和1个py可视化脚本为主整体约101.53MB。数据集划分为训练集与测试集训练集含2127张图片及2127个对应mask测试集含537张图片及537个对应mask目录结构清晰便于直接接入分割网络训练。随包附带的可视化脚本无需修改即可运行随机抽取一张图片同时展示原始图像、GT图像以及GT在原图上的蒙板效果并保存到当前目录方便快速检查标注质量。目前已有229人学习适合需要现成舌头分割数据做实验、复现或课程实践的用户。1. 舌头分割数据集到底能解决什么问题从中医齿痕识别到 2 类掩码落地拿到「舌头分割2类」这个数据集标题时很多人第一反应是舌头分割能有多大市场但如果你做过中医舌诊仪、口腔内窥镜辅助诊断、或者舌象健康 App就会知道舌体区域的像素级掩码几乎是所有下游任务的前置依赖——舌质颜色统计、齿痕计数、舌苔厚薄分级全都建立在「先把舌头从背景里抠出来」这一步上。这个数据集的核心价值在于把问题简化成 2 类语义分割背景与舌体标签文件是单通道掩码图配套数据可视化代码让你在训练前就能肉眼确认标注质量。它适合三类人想入门医学图像分割的算法工程师、需要快速验证舌象分析原型的在校学生、以及要给现有 pipeline 补一个稳定 ROI 提取模块的开发者。2 类设定意味着你不用纠结多标签冲突交叉熵和 Dice 就能跑通但边界细粒度才是真正的难点。2. 2 类舌头分割的数据组织与标签格式先看懂再动手2.1 图像与掩码的目录约定和命名对齐绝大多数舌头分割数据集采用「图像目录 掩码目录」平行结构文件名一一对应。常见做法是图像存为 JPG 或 PNG掩码存为 PNG 单通道 8 位灰度图像素值 0 表示背景、1 或 255 表示舌体。这里有个血泪经验如果掩码用 JPG 存压缩会引入中间灰度值你的 2 类标签就变成了事实上的多类噪声。所以拿到数据集第一件事不是写模型而是写一个校验脚本确认掩码唯一值集合。import os import numpy as np from PIL import Image img_dir data/images mask_dir data/masks def check_mask_values(mask_dir): bad [] for name in os.listdir(mask_dir): m np.array(Image.open(os.path.join(mask_dir, name))) uniq np.unique(m) # 2类掩码唯一值应只含 0 和 1或 0 和 255 if not set(uniq.tolist()).issubset({0, 1, 255}): bad.append((name, uniq.tolist())) return bad bad_list check_mask_values(mask_dir) print(f异常掩码数量: {len(bad_list)}) for item in bad_list[:5]: print(item)这段脚本的逻辑很直接遍历掩码目录读取每个 PNG 的唯一像素值只要出现 0/1/255 之外的值就记录。参数上issubset里的集合要根据你数据集的真实编码调整——有的数据集用 0/1有的用 0/255还有的用 0/128。如果发现大量异常说明掩码被 JPG 污染过需要重新找原始标注或做阈值化修复。2.2 标签文件里容易忽略的三个属性除了像素值标签文件还有三个属性决定你后面会不会翻车位深、调色板、尺寸。位深必须是 8 位16 位掩码在 PyTorch 里读进来会变成 int32直接送进损失函数会报类型错误。调色板模式P 模式的 PNG 看起来是彩色实际索引值才是标签用convert(L)转灰度时可能发生映射偏移。尺寸必须和原图严格一致有些数据集图像是 512×512掩码却是 256×256训练时广播会静默出错。属性正确值错误后果检查方法位深8 位损失函数类型报错Image.open().mode模式L 或 P标签值偏移np.unique校验尺寸与原图一致广播静默错误img.size mask.size编码0/1 或 0/255类别数误判统计唯一值我一般会在数据加载类里加一个断言尺寸不一致直接抛异常而不是等到 loss 变成 NaN 才回头查。这个习惯帮我省过至少两次通宵排查。3. 用可视化代码在训练前验数据把黑匣子打开3.1 叠加可视化三行代码看出标注偏移数据可视化代码的价值不是好看而是让你在花几小时训练前发现标注问题。最有效的可视化是原图与掩码半透明叠加舌体区域用红色覆盖边界是否贴合一眼就能看出来。import numpy as np from PIL import Image import matplotlib.pyplot as plt def overlay_visualize(img_path, mask_path, alpha0.5): img np.array(Image.open(img_path).convert(RGB)) mask np.array(Image.open(mask_path).convert(L)) # 将掩码二值化兼容 0/1 和 0/255 两种编码 binary (mask 0).astype(np.uint8) color_mask np.zeros_like(img) color_mask[binary 1] [255, 0, 0] overlay (img * (1 - alpha) color_mask * alpha).astype(np.uint8) plt.figure(figsize(12, 4)) plt.subplot(1, 3, 1); plt.imshow(img); plt.title(原图) plt.subplot(1, 3, 2); plt.imshow(binary, cmapgray); plt.title(掩码) plt.subplot(1, 3, 3); plt.imshow(overlay); plt.title(叠加) plt.show() overlay_visualize(data/images/001.jpg, data/masks/001.png)逻辑说明先统一把掩码二值化避免 0/1 和 0/255 两种编码导致可视化全黑或全红。alpha控制叠加透明度0.5 是通用值如果舌体颜色本身偏红可以降到 0.3 让边界更清楚。参数上唯一要注意的是convert(L)如果原掩码是 P 模式这一步会按调色板转灰度可能不是你想要的索引值稳妥做法是先convert(RGB)再取单通道。3.2 批量抽检与边界统计单张看不够我一般会随机抽 20 张拼成网格同时统计每张掩码的舌体像素占比。占比异常小低于 1%或异常大高于 80%的样本大概率是标注错误或图像本身有问题。import random def batch_inspect(img_dir, mask_dir, n20): names os.listdir(mask_dir) sample random.sample(names, min(n, len(names))) ratios [] for name in sample: m np.array(Image.open(os.path.join(mask_dir, name)).convert(L)) ratio (m 0).sum() / m.size ratios.append((name, round(ratio, 4))) ratios.sort(keylambda x: x[1]) print(占比最低:, ratios[:3]) print(占比最高:, ratios[-3:]) return ratios batch_inspect(data/images, data/masks)这段代码输出占比排序最低和最高的几个就是重点复查对象。舌体在正常拍摄下占比通常在 15% 到 50% 之间低于 5% 可能是拍摄距离过远或标注漏标高于 70% 可能是把嘴唇、下巴一起标进去了。这一步做完你对数据集的信任度才有底。4. 训练 2 类舌头分割模型的参数怎么设从损失函数到边界优化4.1 损失函数选择Dice 与 BCE 的组合逻辑2 类分割最常见的坑是类别极度不平衡——舌体占比 20%背景 80%纯 BCE 会让模型倾向于全预测背景也能拿到 80% 准确率。我一般用 BCE Dice 组合BCE 稳定梯度Dice 直接优化重叠度。import torch import torch.nn as nn class BCEDiceLoss(nn.Module): def __init__(self, bce_weight0.5): super().__init__() self.bce nn.BCEWithLogitsLoss() self.bce_weight bce_weight def forward(self, logits, targets): # logits: (B,1,H,W) targets: (B,1,H,W) float bce_loss self.bce(logits, targets) probs torch.sigmoid(logits) intersection (probs * targets).sum(dim(2, 3)) union probs.sum(dim(2, 3)) targets.sum(dim(2, 3)) dice (2 * intersection 1e-6) / (union 1e-6) dice_loss 1 - dice.mean() return self.bce_weight * bce_loss (1 - self.bce_weight) * dice_loss参数说明bce_weight默认 0.5如果训练初期 loss 震荡大可以调到 0.7 让 BCE 主导如果最终 Dice 上不去调到 0.3 让 Dice 主导。1e-6是平滑项防止空掩码时除零。注意 targets 必须是 float 且值域 0/1如果掩码是 0/255要在 Dataset 里除以 255。4.2 输入尺寸与增强策略舌头边界的细粒度保护舌头分割的难点在边界——齿痕、舌苔边缘、反光区域都会让边界模糊。输入尺寸我一般用 512×512再小边界信息丢失严重再大显存吃不消。增强策略里随机旋转和水平翻转是安全的但垂直翻转要慎用舌头上下颠倒不符合解剖先验可能让模型学到错误特征。颜色抖动幅度要小因为舌质颜色本身就是下游任务要分析的特征抖太狠会破坏颜色分布。增强方式推荐参数理由随机旋转±30 度拍摄角度变化水平翻转p0.5左右对称合理垂直翻转不建议解剖先验不符颜色抖动亮度±0.1保护舌质颜色随机裁剪缩放 0.8-1.2模拟距离变化4.3 评价指标Dice 之外还要看边界Dice 和 IoU 是常规指标但舌头分割我还会看边界 F1Boundary F1。因为 Dice 对内部像素不敏感边界错几个像素 Dice 可能只掉 0.01但下游齿痕计数就废了。常见做法是用scipy.ndimage做形态学腐蚀得到边界带再算边界区域的 F1。from scipy.ndimage import binary_erosion def boundary_f1(pred, target, width3): # pred/target: (H,W) 二值 kernel np.ones((width, width), bool) pred_bound pred ^ binary_erosion(pred, kernel) target_bound target ^ binary_erosion(target, kernel) tp (pred_bound target_bound).sum() fp (pred_bound ~target_bound).sum() fn (~pred_bound target_bound).sum() precision tp / (tp fp 1e-6) recall tp / (tp fn 1e-6) return 2 * precision * recall / (precision recall 1e-6)width控制边界带宽度3 像素适合 512 输入如果输入是 256 就调到 2。这个指标能暴露 Dice 掩盖的边界问题我一般要求边界 F1 至少 0.7 才认为模型可用。5. 舌头分割数据集常见踩坑与排查标注、编码、过拟合5.1 掩码全黑或全白编码与阈值问题现象可视化时掩码要么全黑要么全白但用图片查看器打开又正常。原因通常是掩码是 P 模式调色板图convert(L)后索引值被映射到 0 或 255 之外或者掩码实际是 0/255 编码但代码按 0/1 处理。解决先用np.unique打印真实唯一值再根据结果调整二值化阈值不要假设编码。5.2 训练 loss 不降标签尺寸与类型不匹配现象训练几个 epoch loss 几乎不动或者直接 NaN。原因多半是掩码尺寸和图像不一致广播后标签错位或者掩码是 int64BCE 要求 float。解决在 Dataset 的__getitem__里加断言assert img.size mask.size并强制mask mask.float() / 255.0。5.3 验证集 Dice 高但可视化边界差过拟合与增强过强现象验证 Dice 0.95但叠加可视化发现边界锯齿严重。原因是训练集和验证集同分布Dice 对边界不敏感加上颜色抖动过强让模型学到了颜色捷径。解决引入边界 F1 指标降低颜色抖动幅度增加边界区域的损失权重。5.4 舌体与嘴唇粘连标注规范不一致现象部分样本把嘴唇标进舌体部分没有模型学出模糊边界。原因标注人员对舌体边界定义不一致。解决抽检占比最高的样本统一标注规范必要时重新标注或剔除异常样本。这个坑最耗时间但跳过它后面所有指标都不可信。5.5 显存溢出输入尺寸与 batch size 不匹配现象512×512 输入配 batch size 16 直接 OOM。原因2 类分割虽然输出通道少但高分辨率特征图显存占用大。解决用混合精度训练或者把 batch size 降到 4 配合梯度累积。from torch.cuda.amp import autocast, GradScaler scaler GradScaler() for imgs, masks in loader: optimizer.zero_grad() with autocast(): logits model(imgs) loss criterion(logits, masks) / accum_steps scaler.scale(loss).backward() if step % accum_steps 0: scaler.step(optimizer) scaler.update()accum_steps设为 4 时等效 batch size 是实际 batch 的 4 倍显存只增加少量。这个技巧在单卡 8G 上跑 512 输入很实用。6. 把 2 类掩码用到位从 ROI 裁剪到齿痕计数的进阶技巧训练出舌头分割模型只是开始真正让这个数据集产生价值的是下游用法。我一般会把分割掩码做成 ROI 裁剪器用掩码的 bounding box 把舌体裁出来再送进颜色分析或齿痕检测模块。这样下游模型不用处理背景干扰输入尺寸统一准确率通常能涨 5 到 10 个点。def crop_by_mask(img, mask, pad10): ys, xs np.where(mask 0) if len(ys) 0: return img y1, y2 max(0, ys.min()-pad), min(img.shape[0], ys.max()pad) x1, x2 max(0, xs.min()-pad), min(img.shape[1], xs.max()pad) return img[y1:y2, x1:x2] # 批量裁剪并保存 for name in os.listdir(mask_dir): img np.array(Image.open(fdata/images/{name.replace(.png,.jpg)})) mask np.array(Image.open(fdata/masks/{name}).convert(L)) cropped crop_by_mask(img, mask) Image.fromarray(cropped).save(fdata/crops/{name.replace(.png,.jpg)})pad参数留 10 像素边距防止边界信息被裁掉。这个裁剪步骤在齿痕计数场景特别有用因为齿痕就在舌体边缘裁太紧会丢特征。另一个进阶用法是把 2 类掩码转成边界带专门训练一个边界细化模型。做法是对掩码做形态学梯度得到 3 到 5 像素宽的边界带然后在这个窄带上做二分类。这样模型容量集中在最难的地方边界 F1 通常能再涨 3 到 5 个点。我试过在 512 输入下把边界 F1 从 0.72 推到 0.78代价是训练时间增加约 40%。还有一个容易被忽略的技巧用分割掩码做数据增强的引导。比如只对舌体区域做颜色抖动背景保持不变这样既增加了样本多样性又不会破坏背景的分布。实现上就是生成一个随机颜色偏移图用掩码做加权融合。def masked_color_jitter(img, mask, strength0.1): jitter np.random.uniform(1-strength, 1strength, sizeimg.shape) jittered np.clip(img * jitter, 0, 255).astype(np.uint8) m (mask 0)[..., None] return np.where(m, jittered, img).astype(np.uint8)strength控制抖动幅度0.1 对应 ±10%舌质颜色分析场景建议不超过 0.15。这个函数在 Dataset 里调用能让模型对光照变化更鲁棒同时不破坏背景一致性。最后说一个我自己的习惯每次拿到新的舌头分割数据集先跑一遍可视化抽检再跑一遍边界 F1 基线最后才动模型结构。顺序反了后面全是后悔药。这个 2 类数据集看起来简单但边界质量和标注一致性决定了它的天花板。希望帮到你。本文还有配套的精品资源点击获取
网站建设高端定制企业官网