
简介这份资源是一套基于改进YOLOv8的龋齿牙齿病变图像分割系统源码主要面向医学影像处理研究者和深度学习开发者。龋齿发病率高传统诊断依赖人工视觉与X光影像容易因主观因素造成漏诊误诊而本资源针对该场景提供了从数据处理、模型训练到结果预测的一体化工程。资源内不仅包含具有5100张图像、23个类别标注的龋齿分割数据集还提供了改进YOLOv8的完整实现——训练脚本负责模型优化验证脚本评估分割效果预测脚本可直接作用于新图像配合交互式UI界面用户无需繁琐配置即可一键训练并查看结果。压缩包共25个文件主要由图像样本、Python源码、Markdown说明与Word文档构成整体大小约4.99MB目录结构清晰便于按模块查阅。已有410人学习下载适合具备一定深度学习基础、想快速复现医学图像分割项目的开发者与研究者参考。1. 龋齿分割不是分类像素级病变检测的数据与模型起点牙科影像中龋齿病灶往往是邻面的一点阴影边界不清晰用分类网络只能回答“这颗牙有没有龋坏”给不出位置和形状。而这个项目采用的分割思路是让模型对每颗牙、每个病变区域做像素级归类拍摄一张口内像模型输出的是与图像同分辨率的掩膜哪一块是龋坏、哪一块是正常釉质、哪一块是修复体边界清清楚楚。项目基于 YOLOv8-seg 的改进版本配套 5100 张已标注图像、23 个类别以及 train.py、val.py、predict.py、ui.py 四个入口从零训练到图形界面推理一条线串下来。对于正在做医学图像分割落地又想用 YOLO 系列快速建基准的工程师这套源码值得拆开看看。2. 从5100张标注到YOLOv8-seg训练数据集结构、标签格式与增强策略拿到tooth-segmentation44-main.zip第一件事不是直接跑 train.py而是先确认数据集的目录结构是否符合 YOLOv8-seg 的读取逻辑。解压后通常会把数据集整理成 images 与 labels 两个平行目录训练和验证分别放一份。这样 train.py 才能通过数据配置文件的 train 和 val 路径直接读到图像和对应标签。2.1 YOLOv8 分割标签格式与目录组织YOLOv8-seg 的标签不是 PNG 掩膜而是纯文本 txt。每个图像对应一个同名 txt文件名相同、扩展名不同。每行代表一个物体的分割实例格式是3 0.3504 0.4211 0.3621 0.4332 0.3712 0.4471 0.3605 0.4583 ... 7 0.5213 0.3112 0.5354 0.3220 0.5481 0.3314 ...第一个数字是类别 ID范围 0~22后续每两个数字一组表示多边形一个顶点的归一化 x、y 坐标。归一化用像素坐标除以图像宽高所以坐标系在 [0,1] 区间。这里有个容易踩的坑轮廓点需要按顺时针或逆时针连接YOLOv8 训练时会对真实 mask 重采样成固定数量的点所以点数多少不是关键关键是顶点顺序不能乱。如果直接从标注软件的 JSON 转换需要用np.array的 reshape 和后来的 contour 提取保证顺序正确。项目文件里能看到1.png、2.png这样的样本说明数据集中既有原图也可能有掩膜可视化图。实际训练目录我一般这样组织tooth-segmentation44-main/ ├── train.py ├── val.py ├── predict.py ├── ui.py ├── dataset/ │ ├── images/ │ │ ├── train/ # 约一张一张的牙科影像 │ │ └── val/ │ ├── labels/ │ │ ├── train/ # 与 images/train 同名的 .txt │ │ └── val/ │ └── classes.txt # 23个类别名每行一个 └── runs/classes.txt每行一个类别名顺序和类别 ID 对应比如第 4 行对应 ID 3。如果类别名顺序错了训练过程不会报错但预测时显示的标签会错位这是很多初学者最容易忽略的细节。2.2 从掩膜图像转成 YOLO 分割标签如果你的初始标注是单通道 PNG 掩膜每个像素值对应该像素的类别那么需要先转换成 YOLO txt 格式。我一般用下面这个脚本批量转换import cv2 import numpy as np from pathlib import Path def mask_to_yolo(mask_path, label_path, image_w, image_h): mask cv2.imread(str(mask_path), cv2.IMREAD_GRAYSCALE) lines [] for class_id in range(23): binary (mask class_id).astype(np.uint8) * 255 contours, _ cv2.findContours(binary, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) for cnt in contours: cnt cnt.reshape(-1, 2).astype(np.float32) if len(cnt) 3: continue points [] for x, y in cnt: points.append(f{x / image_w:.6f} {y / image_h:.6f}) lines.append(f{class_id} .join(points)) Path(label_path).write_text(\n.join(lines), encodingutf-8) # 示例对 dataset/masks/19.png 生成 dataset/labels/19.txt mask_to_yolo(dataset/masks/19.png, dataset/labels/19.txt, 640, 640)这段脚本的核心是用findContours提取每个类别的外边界再归一化坐标。注意RETR_EXTERNAL只取外轮廓如果病变内部有空洞不会被单独建模在牙科龋齿场景下龋坏区域通常是连通块外部轮廓足够表达。如果遇到牙齿遮挡导致的凹陷边界建议改用RETR_LIST并把小面积轮廓过滤掉避免标签里混入噪声。2.3 数据增强参数与数据集划分策略train.py内部一般通过超参数配置来控制数据增强。YOLOv8 的增强在hyp.yaml中调整关键参数建议如下参数建议值作用与风险mosaic0.8四图拼接提升小目标召回但牙科影像过度拼接会破坏解剖结构建议不超过 0.8hsv_h0.015轻微色相扰动模拟不同光源下的口内像色调hsv_s / hsv_v0.3饱和度与明度扰动防止模型过度依赖亮度degrees5允许轻微旋转角度过大会改变咬合关系fliplr0.5左右翻转牙齿左右对称可安全使用scale0.3缩放模拟不同拍摄距离训练集和验证集的划分不只是随机抽样。这里有一个专业坑同一个人的多张口腔照片如果同时出现在训练集和验证集模型会“记住”那个人测试指标虚高。建议按患者 ID 分组划分比如 5100 张来自多个患者就按患者聚类后 9:1 切分。如果数据集里没有患者字段至少保证同一时间段拍摄的图像不会跨集合。3. 改进YOLOv8的关键点注意力模块、损失函数与train.py一键训练参数YOLOv8-seg 本身就是检测与分割一体的架构但直接用在龋齿病变上会面临两个问题小目标多、类别像素极度不平衡。邻面龋可能只占整张图的千分之一普通 BCE 损失很容易被正常牙齿区域主导。这一章从模型结构和训练配置两个层面拆解改进思路。3.1 为什么选 YOLOv8-seg 而不是 U-NetU-Net 在医学图像分割里是经典编码器-解码器结构对像素级语义分割很稳。但它本质是一个“纯分割”网络不输出物体级别信息。牙科临床应用不仅要告诉医生“这里有龋坏”最好还要给出是哪颗牙、病变区域有多大。YOLOv8-seg 保留了检测头同时新增分割头一个前向过程同时输出检测框和掩膜。对龋齿这种病灶边界不规则、又和牙齿位置强相关的任务检测框提供位置先验分割掩膜细化边界比单独用 U-Net 更容易工程化。另外U-Net 推理一张 640×640 图像需要完整全卷积计算YOLOv8 的多尺度特征金字塔结构天然支持 batch 推理显存利用更高效。3.2 在模型 yaml 中加入注意力模块与多尺度融合常见的改进做法是在 backbone 的最后一层或 neck 的融合层插入注意力模块。以坐标注意力CoordAtt为例它能同时编码通道注意力和方向位置信息特别适合牙齿这种长条形、方向性强的目标。修改后的模型结构文件tooth_seg_yolov8s.yaml片段如下# 基于 YOLOv8s-seg 的改进示意 nc: 23 backbone: - [-1, 1, Conv, [64, 3, 2]] - [-1, 1, Conv, [128, 3, 2]] - [-1, 1, C2f, [128, True]] - [-1, 1, Conv, [256, 3, 2]] - [-1, 1, C2f, [256, True]] - [-1, 1, Conv, [512, 3, 2]] - [-1, 1, C2f, [512, True]] - [-1, 1, Conv, [1024, 3, 2]] - [-1, 1, C2f, [1024, True]] - [-1, 1, SPPF, [1024, 5]] - [-1, 1, CoordAtt, [1024]] # 改进点在 SPPF 后加注意力 head: # head 部分与 YOLOv8s-seg 原始定义保持一致 - [-1, 1, nn.Upsample, [None, 2, nearest]] ...CoordAtt类需要注册到 ultralytics 的模块目录下常见做法是在ultralytics/nn/modules/attention.py里实现并在__init__.py中导出。模块代码如下import torch import torch.nn as nn class CoordAtt(nn.Module): def __init__(self, inp, oup, reduction32): super().__init__() self.pool_h nn.AdaptiveAvgPool2d((None, 1)) self.pool_w nn.AdaptiveAvgPool2d((1, None)) mip max(8, inp // reduction) self.conv1 nn.Conv2d(inp, mip, kernel_size1, stride1, padding0) self.bn1 nn.BatchNorm2d(mip) self.act nn.Hardswish() self.conv_h nn.Conv2d(mip, oup, kernel_size1, stride1, padding0) self.conv_w nn.Conv2d(mip, oup, kernel_size1, stride1, padding0) def forward(self, x): identity x n, c, h, w x.size() x_h self.pool_h(x).permute(0, 1, 3, 2).contiguous() x_w self.pool_w(x).permute(0, 1, 2, 3).contiguous() y torch.cat([x_h, x_w], dim2) y self.act(self.bn1(self.conv1(y))) x_h, x_w torch.split(y, [h, w], dim2) x_w x_w.permute(0, 1, 3, 2).contiguous() a_h self.conv_h(x_h).sigmoid() a_w self.conv_w(x_w).sigmoid() return identity * a_w * a_h这段代码先在高度和宽度方向分别做池化再通过 1×1 卷积生成注意力权重最后对原特征图按通道和空间位置加权。与 SE 注意力相比CoordAtt 保留了坐标位置信息对邻面龋这种局部小区域更敏感。注意permute的使用高度和宽度池化后张量维度不同拼接时要保证维度一致。3.3 train.py 一键训练的参数拆解这个项目的核心卖点是“一键训练”train.py已经封装好了数据集加载、配置合并和训练循环。实际运行时只需要指定数据集配置和模型结构例如python train.py --data configs/tooth_seg.yaml \ --model tooth_seg_yolov8s.yaml \ --epochs 300 \ --batch-size 16 \ --imgsz 640 \ --optimizer AdamW \ --device 0关键参数说明如下参数示例含义与建议--datatooth_seg.yaml数据集配置必须包含 train/val 路径、nc 类别数和 classes 名称--modeltooth_seg_yolov8s.yaml网络结构文件改进注意力时指向自定义 yaml--epochs300训练轮数搭配早停后一般 200 轮左右收敛--batch-size16单卡训练24G 显存可设 32显存不足先降 imgsz--imgsz640输入尺寸邻面龋小目标建议提升到 960代价是训练变慢--optimizerAdamW分割任务上 AdamW 比 SGD 稳定尤其小 batch 时--patience50验证集 mAP50 连续 50 轮不涨就停止防止过拟合训练时的分割损失默认是 BCE但 23 个类别中龋坏区域像素占比极低直接训练会偏向背景。改进做法是在train.py中把 seg loss 换成 Focal Dice 的组合class FocalDiceLoss(nn.Module): def __init__(self, alpha0.25, gamma2.0): super().__init__() self.alpha alpha self.gamma gamma self.bce nn.BCEWithLogitsLoss(reductionnone) def forward(self, pred, target): # pred: [B, 1, H, W], target: [B, 1, H, W] bce self.bce(pred, target) pt torch.exp(-bce) focal self.alpha * (1 - pt) ** self.gamma * bce pred_sig torch.sigmoid(pred) inter (pred_sig * target).sum(dim(-2, -1)) union pred_sig.sum(dim(-2, -1)) target.sum(dim(-2, -1)) dice 1 - (2 * inter 1e-6) / (union 1e-6) return (focal.mean() dice.mean()) / 2Focal 损失让模型关注难分样本Dice 损失缓解前景背景不平衡。实际训练中如果发现损失下降缓慢可以把 alpha 调到 0.5因为龋齿病变相比一般检测任务的难分负样本更多alpha 太低会压住正样本梯度。4. val.py验证与predict.py推理mAP指标、置信度阈值和掩膜后处理训练完成后项目里的val.py和predict.py是两个独立入口。前者用来在验证集上算指标后者用来对单张或批量图像做推理。很多人训练完直接跑预测跳过验证环节导致模型过拟合都没发现。正确的顺序是先用 val.py 确认模型泛化能力再去做预测。4.1 用 val.py 看 Mask mAP 而不是只看 Box mAP运行验证命令python val.py --weights runs/segment/train/exp/weights/best.pt \ --data configs/tooth_seg.yaml \ --imgsz 640 \ --batch-size 8输出会分成 Box 指标和 Mask 指标两部分。Box 基于检测框计算Mask 基于像素掩膜计算。龋齿分割系统的关键指标是 Mask mAP因为最终交付的是掩膜区域。需要重点看这几个指标含义诊断价值mAP50IoU 阈值 0.5 时的平均精度判断病变区域是否大致定位准确mAP50-95从 0.5 到 0.95 按 0.05 步长取平均对边界精度的综合评价越高越贴近真实轮廓Mask Precision预测掩膜中真正属于病变的像素比例避免把正常釉质误判为龋坏Mask Recall真值掩膜中有多少被预测出来防止漏诊尤其邻面龋如果 mAP50 很高但 mAP50-95 低说明模型能找到大致区域但边缘锯齿明显。这时优先在 predict 阶段增加掩膜平滑后处理而不是重新训练。4.2 用 predict.py 做单图和批量推理预测入口常见用法python predict.py --weights runs/segment/train/exp/weights/best.pt \ --source ./test_images/19.png \ --conf 0.25 \ --iou 0.5 \ --save-dir runs/predict--conf是置信度阈值--iou是 NMS 的 IoU 阈值。分割结果会保存为原图叠加掩膜的图片同时可能生成一个 txt 文件里面是每个对象的类别和掩膜坐标。预测时建议把--conf从默认的 0.25 提高到 0.4 观察效果口腔图像中牙齿表面反光会造成高置信度误检适当提高阈值能过滤掉部分假阴性——注意这里是假阳性漏检的龋坏区域往往置信度较低阈值太高反而漏诊。如果发现预测掩膜边缘有毛刺或者出现了面积很小的孤立噪声点在 predict.py 里做一步形态学开运算就能显著改善import cv2 import numpy as np # result.masks.data 是 [N, H, W] 的二进制掩膜 mask result.masks.data.cpu().numpy()[0] # 过滤小面积区域 if mask.sum() 50: continue # 形态学开运算先腐蚀再膨胀消除细小的边缘噪声 kernel cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (3, 3)) mask cv2.morphologyEx(mask.astype(np.uint8), cv2.MORPH_OPEN, kernel)这里的阈值 50 是像素数量对 640×640 图像来说小于 50 像素的连通域通常不是真实病变。MORPH_OPEN的核大小如果超过 5×5会把细长的邻面龋也一并抹掉所以 3×3 是安全值。4.3 TTA 在医学分割里要克制YOLOv8 的 predict.py 支持--augment开启测试时增强原理是原图 水平翻转 缩放多尺度推理后融合结果。对牙科图像水平翻转是安全的但不要开启 mosaic 类增强因为拼接会破坏牙齿的解剖结构。实测中开启--augment后 mAP50 能提升 1~2 个百分点但推理时间变成原来的 3 倍。如果项目只做离线诊断报告可以用如果做实时辅助诊断建议关掉改用提高 imgsz 到 960 来获得更精细的掩膜。5. ui.py图形界面与部署加速把分割模型变成可交付的工具ui.py的意义在于让整个系统不依赖命令行。牙科医生不会去跑 python 脚本所以一个简单的图形界面就能把训练好的模型变成可操作的工具。5.1 PyQt5 界面框架最常见的实现是用 PyQt5 搭一个窗口按钮触发模型推理。核心结构大概长这样import sys from PyQt5.QtWidgets import QApplication, QMainWindow, QPushButton, QLabel, QFileDialog from ultralytics import YOLO class ToothSegUI(QMainWindow): def __init__(self): super().__init__() self.model YOLO(runs/segment/train/exp/weights/best.pt) self.setWindowTitle(龋齿分割系统) # 省略布局代码 def open_image(self): path, _ QFileDialog.getOpenFileName(self, 选择图像, , Image Files (*.png *.jpg)) result self.model.predict(sourcepath, imgsz960, conf0.25) # 将 result[0].plot() 显示到 QLabel plotted result[0].plot() self.label.setPixmap(...)关键点imgsz960是推理端的常用设置因为 UI 单张推理对速度不敏感优先保质量。同时要在closeEvent里显式释放模型显存否则连续打开多张图后显存会持续上升。5.2 ONNX 导出与 TensorRT 加速如果后续要把模型集成到 Web 服务或嵌入式设备建议先导出 ONNXyolo export modelruns/segment/train/exp/weights/best.pt formatonnx opset12 simplifyTrue imgsz640opset 版本根据部署环境的 PyTorch 版本选择PyTorch 1.x 用 12PyTorch 2.x 用 16 以上。simplifyTrue会去掉一些冗余算子让 TensorRT 能更好做层融合。导出后在 NVIDIA GPU 上跑 TensorRT 加速trtexec --onnxbest.onnx --fp16 --saveEnginebest.trtFP16 模型推理速度比 PyTorch 原模型快 2~3 倍精度损失对龋齿分割来说通常可忽略。5.3 一个实用技巧双分辨率策略很多龋齿病灶在 640×640 输入下只有十几像素分割掩膜很容易碎。操作上可以同时保留 640 和 960 两个模型预览界面用 640 保持拖动流畅点击“生成报告”时用 960 重新推理一次取得更完整的边界。如果只有一个模型最简单的折中是推理时把rectFalse关掉强制模型用正方形输入避免图像变形导致掩膜失真。本文还有配套的精品资源点击获取