☰
牙科影像分割数据集实战:labelme转mask与6类病变训练避坑指南
2026/11/3 4:04:59 网站建设 项目流程

简介:面向牙齿影像分析与口腔病变检测方向的算法开发者与研究者,这份资源提供了一套可直接用labelme打开编辑的牙齿分割标注数据集,重点服务于龋齿、龋洞、裂纹、牙结石与炎症等病变区域的识别任务,适合具备一定图像分割基础、需要自建训练数据的中高级学习者。压缩包为单个docx文档,约2.22MB,其中整理了2616张jpg图像与对应json标注文件的完整说明,标注由labelme 5.5.0以多边形方式完成,涵盖Tooth、Caries、Cavity、Crack、calculus、inflamation共6类,各类标注数量从139到2913不等,并明确提示部分牙齿未标注不影响病变检测。已有122人学习关注。读者可据此了解数据规模与类别分布,自行将json转换为mask、YOLO或COCO格式,用于语义分割或实例分割实验,同时需注意发布方不对训练模型精度作保证。

1. 牙科影像分割数据集:2616 张 labelme 标注到底能干什么

拿到一份牙齿病变分割数据集,第一反应往往不是"太好了有数据了",而是"这标注能不能直接喂给模型"。这份资源给的是 2616 张 jpg 配 2616 个 labelme json,6 个类别全是多边形标注,没有现成 mask。它解决的是牙科影像分割里最耗时的环节——从零标注。适合做医学图像分割验证、牙科辅助诊断原型、labelme 转 mask 流程练手的人。不适合想要开箱即训、直接跑出高精度模型的人,因为 json 到 mask 的转换、部分牙齿漏标的处理,都得自己动手。下面按"数据长什么样 → 怎么转 → 怎么训 → 坑在哪"拆开讲。

2. 先看清数据底子:6 类标注的分布与选型判断

2.1 类别分布决定了这份数据能训什么

把 6 个类别的标注数拉出来看,分布极不均衡:

类别英文名标注数占比特征
牙齿Tooth1709主体结构,但明显少于图片数
龋齿Caries2913数量最多,病变检测主力
龋洞Cavity1099中等
裂纹Crack139极稀疏
牙结石calculus1207中等偏多
炎症inflamation620偏少

2616 张图里 Tooth 只有 1709 个标注,说明约三分之一的图里牙齿没被完整标出。项目正文已经点明:分割目的是查病变,部分牙齿没标不影响病变检测。这句话是理解整份数据的关键——它不是一份"完整牙齿实例分割"数据集,而是一份"病变区域优先"的数据集。如果你的任务是数牙齿、做全口牙位识别,这份数据会让你翻车;如果任务是龋齿、牙结石、炎症的病灶分割,它是对口的。

Crack 只有 139 个标注,是典型的稀疏类别。直接按像素级交叉熵训,模型大概率把这个类学成背景。血泪经验是:稀疏类别要么在采样时过采样含 Crack 的图,要么在 loss 里给类别权重,别指望默认配置能救回来。

2.2 为什么是 labelme 而不是现成 mask

labelme 存的是 json,里面记录每个多边形的顶点坐标和 label 名,不存像素 mask。好处是标注可编辑、可回溯、顶点精度高;代价是训练前必须自己栅格化成 mask。这份数据用 labelme 5.5.0 标注,polygon 规则,意味着每个标注是一串点而不是矩形框。对语义分割来说这是对的——牙齿和病变边界本来就不规则,矩形框会引入大量背景像素。

选型上要清楚:语义分割(每个像素归一类)和实例分割(区分同一类的不同个体)对这份数据的用法不同。语义分割直接把 6 类栅格化成 6 通道或单通道索引图即可;实例分割需要把每个 polygon 当独立实例,同一张图里多个 Tooth 要分开,但数据里部分牙齿漏标会让实例数不稳定,做实例分割要谨慎。

3. 把 json 转成可训练的 mask:脚本与参数

3.1 环境准备与目录约定

先固定目录结构,后面所有脚本都基于它:

dataset/ ├── images/ # 2616 张 jpg ├── jsons/ # 2616 个同名 json └── masks/ # 输出,脚本生成

装依赖,labelme 自带转换工具但版本要对上:

pip install labelme==5.5.0 pip install opencv-python numpy pillow

注意 labelme 版本尽量和标注时一致(5.5.0),高版本 json 字段偶有差异,低版本可能读不了新字段。转换脚本我一般自己写,比labelme_json_to_dataset可控,因为官方脚本对多类别、自定义颜色映射不够灵活。

3.2 单张 json 转 mask 的核心逻辑

import json import numpy as np import cv2 from pathlib import Path # 类别到索引的映射,顺序固定,训练和推理必须一致 CLASS_MAP = { "Tooth": 1, "Caries": 2, "Cavity": 3, "Crack": 4, "calculus": 5, "inflamation": 6, } def json_to_mask(json_path, out_path): with open(json_path, "r", encoding="utf-8") as f: data = json.load(f) h, w = data["imageHeight"], data["imageWidth"] mask = np.zeros((h, w), dtype=np.uint8) # 0 为背景 for shape in data["shapes"]: label = shape["label"] if label not in CLASS_MAP: continue pts = np.array(shape["points"], dtype=np.int32) # polygon 填充,同一类别重叠时后画的覆盖先画的 cv2.fillPoly(mask, [pts], CLASS_MAP[label]) cv2.imwrite(str(out_path), mask) if __name__ == "__main__": img_dir = Path("dataset/images") json_dir = Path("dataset/jsons") mask_dir = Path("dataset/masks") mask_dir.mkdir(exist_ok=True) for jp in json_dir.glob("*.json"): json_to_mask(jp, mask_dir / (jp.stem + ".png"))

逻辑说明:imageHeight/imageWidth从 json 里读,保证 mask 和原图同尺寸;fillPoly按顶点填充,CLASS_MAP把字符串 label 映射成 0-6 的整数索引,0 留给背景。参数上最关键的是CLASS_MAP的顺序——训练时的类别索引、推理时的颜色表、评估时的混淆矩阵都依赖它,一旦改动必须全链路同步,否则会出现"训练是龋齿、推理显示牙结石"的玄学问题。

3.3 批量转换与完整性校验

批量跑完后必须校验,不能假设 2616 个都成功:

import os from pathlib import Path json_files = list(Path("dataset/jsons").glob("*.json")) mask_files = list(Path("dataset/masks").glob("*.png")) print("json:", len(json_files), "mask:", len(mask_files)) # 检查空 mask(全背景),这类图要么是漏标要么是纯背景 empty = [] for mp in mask_files: m = cv2.imread(str(mp), cv2.IMREAD_GRAYSCALE) if m.max() == 0: empty.append(mp.name) print("空 mask 数量:", len(empty))

空 mask 是这份数据的高频现象,因为部分牙齿没标、有些图可能只有背景。训练时全背景图对语义分割是有效负样本,但如果比例过高会拉低正样本学习效率,建议统计后决定是否剔除。校验这一步别省,我见过直接开训、跑了半天发现 mask 全是黑的,回头查是 json 里shapes为空。

4. 训练配置:类别不平衡与漏标怎么处理

4.1 损失函数与采样策略

6 类里 Crack 只有 139 个标注,Tooth 1709、Caries 2913,像素级不平衡更严重(病变区域通常只占图的一小块)。常见做法是组合损失:

import torch import torch.nn as nn import torch.nn.functional as F class CombinedLoss(nn.Module): def __init__(self, num_classes=7, weights=None): super().__init__() # weights 按类别频率的倒数设置,Crack 给高权重 self.ce = nn.CrossEntropyLoss(weight=weights) def forward(self, logits, target): # logits: [B, C, H, W], target: [B, H, W] ce_loss = self.ce(logits, target) # Dice 缓解前景背景不平衡 probs = F.softmax(logits, dim=1) dice = 0.0 for c in range(1, logits.shape[1]): p = probs[:, c] t = (target == c).float() inter = (p * t).sum() dice += (2 * inter + 1e-6) / (p.sum() + t.sum() + 1e-6) dice_loss = 1 - dice / (logits.shape[1] - 1) return ce_loss + dice_loss

参数说明:weights建议按总像素 / (类别数 * 该类像素)估算,Crack 的权重会明显高于其他类;Dice 部分跳过背景类(从 1 开始循环),避免背景主导。这套组合在医学小目标分割里是常规操作,比单用交叉熵稳。

4.2 漏标牙齿的处理边界

项目正文明确说部分牙齿没标注。训练时这会带来一个隐蔽问题:模型把未标注的牙齿预测成 Tooth,loss 却按背景惩罚,等于在教模型"别认这颗牙"。处理方式有两种:一是把未标注区域当 ignore index,loss 里跳过;二是接受噪声,靠数据量摊平。我一般选前者,在 mask 生成时把"确定是牙齿但没标"的区域标成 255,CrossEntropyLoss(ignore_index=255)跳过。但这份数据没提供"哪些没标"的信息,所以实操上更现实的做法是:训练目标聚焦病变类,Tooth 类权重调低,别让模型在牙齿完整性上死磕。

提示:如果你的下游任务是病灶检测而非全牙分割,评估指标只看 Caries/Cavity/calculus/inflamation 的 Dice,Tooth 的漏标就不会污染结论。

5. 避坑排查:这份数据最容易翻车的五个地方

5.1 现象:转出来的 mask 和原图对不上

原因:json 里的imageWidth/imageHeight和实际 jpg 尺寸不一致,常见于图片被二次压缩或裁剪过。解决:转换前用cv2.imread读原图尺寸,和 json 字段比对,不一致的图单独列出来人工确认,别直接resize硬转,否则多边形坐标全错位。

5.2 现象:训练 loss 正常但验证 Dice 极低

原因:验证集里 Crack 类样本太少,或者验证集划分时没做分层采样,导致某折里根本没有 Crack。解决:按类别出现情况分层划分 train/val,保证每个类在验证集里有足够样本;Crack 这种稀疏类可以单独统计召回,别只看总体 Dice。

5.3 现象:模型把牙结石预测成龋齿

原因:calculus 和 Caries 在颜色、纹理上接近,标注边界本身有主观性,且两类像素数都不少。解决:检查混淆矩阵确认是哪两类混淆;在 loss 里对这两类加区分性权重,或引入边界损失(boundary loss)强化边缘;数据层面复查标注,确认没有同一区域被标成两类。

5.4 现象:空 mask 图导致训练不稳定

原因:部分图只有背景或漏标严重,全零 mask 在 Dice loss 里分母接近零,梯度异常。解决:转换后统计空 mask 比例,超过阈值(比如 5%)就剔除或降采样;Dice 计算里已经加了1e-6平滑,但仍建议过滤纯空图。

5.5 现象:labelme 打开 json 报错或类别显示异常

原因:labelme 版本不匹配,或 json 里 label 名有前后空格、大小写不一致(比如Tooth和tooth)。解决:统一用 5.5.0 打开;转换脚本里对 label 做strip()并建立大小写归一映射,遇到未知 label 打印出来人工核对,别静默跳过。

6. 进阶验证:用混淆矩阵反推标注质量

数据到手别急着堆模型,先花半小时做一次标注质量体检,比调参收益大。做法是:随机抽 100 张图,把生成的 mask 叠加回原图,用半透明颜色显示,人工扫一遍。重点看三类问题——病变区域是否被完整包住、同一病变是否被拆成多个 polygon、边界是否明显超出病灶。这一步能提前发现系统性标注偏差,避免模型学了一堆噪声还以为是数据不够。

更量化的验证是跑一个轻量模型(比如 UNet 小通道版)训 10 个 epoch,输出混淆矩阵:

from sklearn.metrics import confusion_matrix import numpy as np # pred, target 为展平后的像素级标签 cm = confusion_matrix(target.flatten(), pred.flatten(), labels=list(range(7))) print(cm) # 重点看对角线外的非零项,尤其是 Caries<->calculus、Cavity<->Caries

如果 Caries 和 calculus 之间大量互错,大概率是标注边界模糊,而不是模型不行。这时候回头复查标注比换 backbone 有用。我一般会把这个矩阵存成图,和 mask 叠加图放一起,作为这份数据能不能用的判断依据。

从那以后我每次拿到 labelme 格式的医学数据集,都强制先走一遍"转 mask → 空图统计 → 叠加目检 → 小模型混淆矩阵"四步,再决定要不要投入正式训练。这份牙齿病变数据集的价值不在"2616 张"这个数字,而在于它把牙科病灶的 6 类边界摆在了你面前,剩下的转换和清洗,才是真正决定成败的部分。希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询